Adds automatic switching to Guest if user starts driving with the keyguard
up.
driving_on_keyguard_timeout_ms controlls the number of milliseconds we wait,
before switching to Guest. If this number is negative, feature is disabled.
Change-Id: Ic1357362a97cb14a4f221d53e17a30cd3fefc5ea
Fixes: 110228676
Test: manual testing on mojave and emulator. Toggling driving state and keyguard, and observing the timer logs and switching.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3725940..fef9ae8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -209,6 +209,9 @@
<!-- to read and change hvac values in a car -->
<uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
+ <!-- to be able to detect the driving state in a car-->
+ <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE" />
+
<!-- Permission necessary to change car audio volume through CarAudioManager -->
<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
diff --git a/packages/SystemUI/res/values/integers_car.xml b/packages/SystemUI/res/values/integers_car.xml
index 7513fd4..fc3623c 100644
--- a/packages/SystemUI/res/values/integers_car.xml
+++ b/packages/SystemUI/res/values/integers_car.xml
@@ -19,4 +19,9 @@
<integer name="car_user_switcher_anim_cascade_delay_ms">27</integer>
<!-- Full screen user switcher column number TODO: move to support library-->
<integer name="user_fullscreen_switcher_num_col">3</integer>
+
+ <!-- Number of milliseconds user can spend driving with the keyguard up. After that, we switch to Guest. -->
+ <!-- If the number is negative, the feature is disabled.
+ If it's zero, we switch to guest immediately as we start driving. -->
+ <integer name="driving_on_keyguard_timeout_ms">30000</integer>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 24665ea..7be0eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.car;
import android.app.ActivityTaskManager;
+import android.car.drivingstate.CarDrivingStateEvent;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.util.Log;
@@ -82,6 +83,8 @@
private DeviceProvisionedController mDeviceProvisionedController;
private boolean mDeviceIsProvisioned = true;
private HvacController mHvacController;
+ private DrivingStateHelper mDrivingStateHelper;
+ private SwitchToGuestTimer mSwitchToGuestTimer;
@Override
public void start() {
@@ -111,6 +114,12 @@
}
});
}
+
+ // Register a listener for driving state changes.
+ mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
+ mDrivingStateHelper.connectToCarService();
+
+ mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
}
/**
@@ -205,6 +214,7 @@
mCarBatteryController.stopListening();
mConnectedDeviceSignalController.stopListening();
mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackListener);
+ mDrivingStateHelper.disconnectFromCarService();
if (mNavigationBarWindow != null) {
mWindowManager.removeViewImmediate(mNavigationBarWindow);
@@ -476,6 +486,20 @@
}
}
+ private void onDrivingStateChanged(CarDrivingStateEvent notUsed) {
+ // Check if we need to start the timer every time driving state changes.
+ startSwitchToGuestTimerIfDrivingOnKeyguard();
+ }
+
+ private void startSwitchToGuestTimerIfDrivingOnKeyguard() {
+ if (mDrivingStateHelper.isCurrentlyDriving() && mState != StatusBarState.SHADE) {
+ // We're driving while keyguard is up.
+ mSwitchToGuestTimer.start();
+ } else {
+ mSwitchToGuestTimer.cancel();
+ }
+ }
+
@Override
protected void createUserSwitcher() {
UserSwitcherController userSwitcherController =
@@ -491,6 +515,9 @@
@Override
public void onStateChanged(int newState) {
super.onStateChanged(newState);
+
+ startSwitchToGuestTimerIfDrivingOnKeyguard();
+
if (mFullscreenUserSwitcher == null) {
return; // Not using the full screen user switcher.
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
new file mode 100644
index 0000000..47941bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/DrivingStateHelper.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarDrivingStateEvent;
+import android.car.drivingstate.CarDrivingStateManager;
+import android.car.drivingstate.CarDrivingStateManager.CarDrivingStateEventListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Helper class for connecting to the {@link CarDrivingStateManager} and listening for driving state
+ * changes.
+ */
+public class DrivingStateHelper {
+ public static final String TAG = "DrivingStateHelper";
+
+ private final Context mContext;
+ private CarDrivingStateManager mDrivingStateManager;
+ private Car mCar;
+ private CarDrivingStateEventListener mDrivingStateHandler;
+
+ public DrivingStateHelper(Context context,
+ @NonNull CarDrivingStateEventListener drivingStateHandler) {
+ mContext = context;
+ mDrivingStateHandler = drivingStateHandler;
+ }
+
+ /**
+ * Queries {@link CarDrivingStateManager} for current driving state. Returns {@code true} if car
+ * is idling or moving, {@code false} otherwise.
+ */
+ public boolean isCurrentlyDriving() {
+ try {
+ CarDrivingStateEvent currentState = mDrivingStateManager.getCurrentCarDrivingState();
+ if (currentState != null) {
+ return currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING
+ || currentState.eventValue == CarDrivingStateEvent.DRIVING_STATE_MOVING;
+ }
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Cannot determine current driving state. Car not connected", e);
+ }
+
+ return false; // Default to false.
+ }
+
+ /**
+ * Establishes connection with the Car service.
+ */
+ public void connectToCarService() {
+ mCar = Car.createCar(mContext, mCarConnectionListener);
+ if (mCar != null) {
+ mCar.connect();
+ }
+ }
+
+ /**
+ * Disconnects from Car service and cleans up listeners.
+ */
+ public void disconnectFromCarService() {
+ if (mCar != null) {
+ mCar.disconnect();
+ }
+ }
+
+ private final ServiceConnection mCarConnectionListener =
+ new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ logD("Car Service connected");
+ try {
+ mDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
+ Car.CAR_DRIVING_STATE_SERVICE);
+ if (mDrivingStateManager != null) {
+ mDrivingStateManager.registerListener(mDrivingStateHandler);
+ mDrivingStateHandler.onDrivingStateChanged(
+ mDrivingStateManager.getCurrentCarDrivingState());
+ } else {
+ Log.e(TAG, "CarDrivingStateService service not available");
+ }
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ destroyDrivingStateManager();
+ }
+ };
+
+ private void destroyDrivingStateManager() {
+ try {
+ if (mDrivingStateManager != null) {
+ mDrivingStateManager.unregisterListener();
+ }
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Error unregistering listeners", e);
+ }
+ }
+
+ private void logD(String message) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java
new file mode 100644
index 0000000..27a5d4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/SwitchToGuestTimer.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.car;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.os.CountDownTimer;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+import androidx.annotation.GuardedBy;
+
+/**
+ * Wrapper for a countdown timer that switches to Guest if the user has been driving with
+ * the keyguard up for configurable number of seconds.
+ */
+public class SwitchToGuestTimer {
+ private static final String TAG = "SwitchToGuestTimer";
+
+ // After how many ms CountdownTimer.onTick gets triggered.
+ private static final int COUNTDOWN_INTERVAL_MS = 1000;
+
+ private final CarUserManagerHelper mCarUserManagerHelper;
+ private final Object mTimerLock;
+ private final String mGuestName;
+ private final int mTimeoutMs;
+ private final boolean mEnabled;
+
+ @GuardedBy("mTimerLock")
+ private CountDownTimer mSwitchToGuestTimer;
+
+ public SwitchToGuestTimer(Context context) {
+ mCarUserManagerHelper = new CarUserManagerHelper(context);
+ mGuestName = context.getResources().getString(R.string.car_guest);
+ mTimeoutMs = context.getResources().getInteger(R.integer.driving_on_keyguard_timeout_ms);
+
+ // Lock prevents multiple timers being started.
+ mTimerLock = new Object();
+
+ // If milliseconds to switch is a negative number, the feature is disabled.
+ mEnabled = mTimeoutMs >= 0;
+ }
+
+ /**
+ * Starts the timer if it's not already running.
+ */
+ public void start() {
+ if (!mEnabled) {
+ logD("Switching to guest after driving on keyguard is disabled.");
+ return;
+ }
+
+ synchronized (mTimerLock) {
+ if (mSwitchToGuestTimer != null) {
+ logD("Timer is already running.");
+ return;
+ }
+
+ mSwitchToGuestTimer = new CountDownTimer(mTimeoutMs, COUNTDOWN_INTERVAL_MS) {
+ @Override
+ public void onTick(long msUntilFinished) {
+ logD("Ms until switching to guest: " + Long.toString(msUntilFinished));
+ }
+
+ @Override
+ public void onFinish() {
+ mCarUserManagerHelper.startNewGuestSession(mGuestName);
+ cancel();
+ }
+ };
+
+ logI("Starting timer");
+ mSwitchToGuestTimer.start();
+ }
+ }
+
+ /**
+ * Cancels the running timer.
+ */
+ public void cancel() {
+ synchronized (mTimerLock) {
+ if (mSwitchToGuestTimer != null) {
+ logI("Cancelling timer");
+ mSwitchToGuestTimer.cancel();
+ mSwitchToGuestTimer = null;
+ }
+ }
+ }
+
+ private void logD(String message) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, message);
+ }
+ }
+
+ private void logI(String message) {
+ if (Log.isLoggable(TAG, Log.INFO)) {
+ Log.i(TAG, message);
+ }
+ }
+}