Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit edba98c1 authored by jovanak's avatar jovanak
Browse files

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.
parent 0d541559
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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" />

+5 −0
Original line number Diff line number Diff line
@@ -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>
+27 −0
Original line number Diff line number Diff line
@@ -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 @@ public class CarStatusBar extends StatusBar implements
    private DeviceProvisionedController mDeviceProvisionedController;
    private boolean mDeviceIsProvisioned = true;
    private HvacController mHvacController;
    private DrivingStateHelper mDrivingStateHelper;
    private SwitchToGuestTimer mSwitchToGuestTimer;

    @Override
    public void start() {
@@ -111,6 +114,12 @@ public class CarStatusBar extends StatusBar implements
                        }
                    });
        }

        // Register a listener for driving state changes.
        mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
        mDrivingStateHelper.connectToCarService();

        mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
    }

    /**
@@ -205,6 +214,7 @@ public class CarStatusBar extends StatusBar implements
        mCarBatteryController.stopListening();
        mConnectedDeviceSignalController.stopListening();
        mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackListener);
        mDrivingStateHelper.disconnectFromCarService();

        if (mNavigationBarWindow != null) {
            mWindowManager.removeViewImmediate(mNavigationBarWindow);
@@ -476,6 +486,20 @@ public class CarStatusBar extends StatusBar implements
        }
    }

    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 @@ public class CarStatusBar extends StatusBar implements
    @Override
    public void onStateChanged(int newState) {
        super.onStateChanged(newState);

        startSwitchToGuestTimerIfDrivingOnKeyguard();

        if (mFullscreenUserSwitcher == null) {
            return; // Not using the full screen user switcher.
        }
+127 −0
Original line number Diff line number Diff line
/*
 * 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
+116 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}