Merge "Adds automatic switching to Guest if user starts driving with the keyguard up."
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 8799341..c35e1da 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);
+        }
+    }
+}