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

Commit 933add5d authored by Kurt Dresner's avatar Kurt Dresner
Browse files

Update Telecom to use new Projection State APIs.

Bug: 134997071
Bug: 169702986
Test: Code builds, works on device, unit tests written and pass
Change-Id: Ia5032f87ea218c754687fba39a90f983cfd8fb5d
parent e3f85be9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@
    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
+84 −32
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
@@ -40,30 +41,40 @@ public class CarModeTracker {
     * Data class holding information about apps which have requested to enter car mode.
     */
    private class CarModeApp {
        private @IntRange(from = 0) int mPriority;
        private final boolean mAutomotiveProjection;
        private final @IntRange(from = 0) int mPriority;
        private @NonNull String mPackageName;

        public CarModeApp(@NonNull String packageName) {
            this(true, 0, packageName);
        }

        public CarModeApp(int priority, @NonNull String packageName) {
            this(false, priority, packageName);
        }

        private CarModeApp(boolean automotiveProjection, int priority, @NonNull String packageName) {
            mAutomotiveProjection = automotiveProjection;
            mPriority = priority;
            mPackageName = Objects.requireNonNull(packageName);
        }

        public boolean hasSetAutomotiveProjection() {
            return mAutomotiveProjection;
        }

        /**
         * The priority at which the app requested to enter car mode.
         * Will be the same as the one specified when {@link UiModeManager#enableCarMode(int, int)}
         * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specifeid.
         * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specified.
         * @return The priority.
         */
        public int getPriority() {
            return mPriority;
        }

        public void setPriority(int priority) {
            mPriority = priority;
        }

        /**
         * @return The package name of the app which requested to enter car mode.
         * @return The package name of the app which requested to enter car mode/set projection.
         */
        public String getPackageName() {
            return mPackageName;
@@ -72,26 +83,24 @@ public class CarModeTracker {
        public void setPackageName(String packageName) {
            mPackageName = packageName;
        }
    }

    /**
     * Comparator used to maintain the car mode priority queue ordering.
     */
    private class CarModeAppComparator implements Comparator<CarModeApp> {
        @Override
        public int compare(CarModeApp o1, CarModeApp o2) {
            // highest priority takes precedence.
            return Integer.compare(o2.getPriority(), o1.getPriority());
        public String toString() {
            return String.format("[%s, %s]",
                    mAutomotiveProjection ? "PROJECTION SET" : mPriority,
                    mPackageName);
        }
    }

    /**
     * Priority list of apps which have entered or exited car mode, ordered with the highest
     * priority app at the top of the queue.  Where items have the same priority, they are ordered
     * by insertion time.
     * Priority list of apps which have entered or exited car mode, ordered first by whether the app
     * has set automotive projection, and then by highest priority.  Where items have the same
     * priority, order is arbitrary, but we only allow one item in the queue per priority.
     */
    private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2,
            new CarModeAppComparator());
            // Natural ordering of booleans is False, True. Natural ordering of ints is increasing.
            Comparator.comparing(CarModeApp::hasSetAutomotiveProjection)
                    .thenComparing(CarModeApp::getPriority)
                    .reversed());

    private final LocalLog mCarModeChangeLog = new LocalLog(20);

@@ -144,6 +153,47 @@ public class CarModeTracker {
        mCarModeApps.removeIf(c -> c.getPriority() == priority);
    }

    public void handleSetAutomotiveProjection(@NonNull String packageName) {
        Optional<CarModeApp> projectingApp = mCarModeApps.stream()
                .filter(CarModeApp::hasSetAutomotiveProjection)
                .findAny();
        // No app with automotive projection? Easy peasy, just add it.
        if (!projectingApp.isPresent()) {
            Log.i(this, "handleSetAutomotiveProjection: %s", packageName);
            mCarModeChangeLog.log("setAutomotiveProjection: packageName=" + packageName);
            mCarModeApps.add(new CarModeApp(packageName));
            return;
        }
        // Otherwise an app already has automotive projection set. Is it the same app?
        if (packageName.equals(projectingApp.get().getPackageName())) {
            Log.w(this, "handleSetAutomotiveProjection: %s already the automotive projection app",
                    packageName);
            return;
        }
        // We have a new app for automotive projection. As a shortcut just reuse the same object by
        // overwriting the package name.
        Log.i(this, "handleSetAutomotiveProjection: %s replacing %s as automotive projection app",
                packageName, projectingApp.get().getPackageName());
        mCarModeChangeLog.log("setAutomotiveProjection: " + packageName + " replaces "
                + projectingApp.get().getPackageName());
        projectingApp.get().setPackageName(packageName);
    }

    public void handleReleaseAutomotiveProjection() {
        Optional<String> projectingPackage = mCarModeApps.stream()
                .filter(CarModeApp::hasSetAutomotiveProjection)
                .map(CarModeApp::getPackageName)
                .findAny();
        if (!projectingPackage.isPresent()) {
            Log.w(this, "handleReleaseAutomotiveProjection: no current automotive projection app");
            return;
        }
        Log.i(this, "handleReleaseAutomotiveProjection: %s", projectingPackage.get());
        mCarModeChangeLog.log("releaseAutomotiveProjection: packageName="
                + projectingPackage.get());
        mCarModeApps.removeIf(CarModeApp::hasSetAutomotiveProjection);
    }

    /**
     * Force-removes a package from the car mode tracking list, no matter at which priority.
     *
@@ -151,19 +201,21 @@ public class CarModeTracker {
     * from the tracking list so they don't cause a leak.
     * @param packageName Package name of the app to force-remove
     */
    public void forceExitCarMode(@NonNull String packageName) {
        Optional<CarModeApp> forcedApp = mCarModeApps.stream()
    public void forceRemove(@NonNull String packageName) {
        // We must account for the possibility that the app has set both car mode AND projection.
        List<CarModeApp> forcedApp = mCarModeApps.stream()
                .filter(c -> c.getPackageName().equals(packageName))
                .findAny();
        if (forcedApp.isPresent()) {
            String logString = String.format("forceExitCarMode: packageName=%s, was at priority=%s",
                    packageName, forcedApp.get().getPriority());
                .collect(Collectors.toList());
        if (forcedApp.isEmpty()) {
            Log.i(this, "Package %s is not tracked.", packageName);
            return;
        }
        for (CarModeApp app : forcedApp) {
            String logString = "forceRemove: " + app;
            Log.i(this, logString);
            mCarModeChangeLog.log(logString);
            mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName));
        } else {
            Log.i(this, "Package %s is not tracked as requesting car mode", packageName);
        }
        mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName));
    }

    /**
@@ -175,7 +227,7 @@ public class CarModeTracker {
        return mCarModeApps
                .stream()
                .sorted(mCarModeApps.comparator())
                .map(cma -> cma.getPackageName())
                .map(CarModeApp::getPackageName)
                .collect(Collectors.toList());
    }

@@ -183,7 +235,7 @@ public class CarModeTracker {
        return mCarModeApps
                .stream()
                .sorted(mCarModeApps.comparator())
                .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]")
                .map(CarModeApp::toString)
                .collect(Collectors.joining(", "));
    }

@@ -216,7 +268,7 @@ public class CarModeTracker {
        pw.increaseIndent();
        for (CarModeApp app : mCarModeApps) {
            pw.print("[");
            pw.print(app.getPriority());
            pw.print(app.hasSetAutomotiveProjection() ? "PROJECTION SET" : app.getPriority());
            pw.print("] ");
            pw.println(app.getPackageName());
        }
+30 −1
Original line number Diff line number Diff line
@@ -912,9 +912,19 @@ public class InCallController extends CallsManagerListenerBase {
            InCallController.this.handleCarModeChange(priority, packageName, isCarMode);
        }

        @Override
        public void onAutomotiveProjectionStateSet(String automotiveProjectionPackage) {
            InCallController.this.handleSetAutomotiveProjection(automotiveProjectionPackage);
        }

        @Override
        public void onAutomotiveProjectionStateReleased() {
            InCallController.this.handleReleaseAutomotiveProjection();
        }

        @Override
        public void onPackageUninstalled(String packageName) {
            mCarModeTracker.forceExitCarMode(packageName);
            mCarModeTracker.forceRemove(packageName);
            updateCarModeForSwitchingConnection();
        }
    };
@@ -1962,6 +1972,25 @@ public class InCallController extends CallsManagerListenerBase {
        updateCarModeForSwitchingConnection();
    }

    public void handleSetAutomotiveProjection(@NonNull String packageName) {
        Log.i(this, "handleSetAutomotiveProjection: packageName=%s", packageName);
        if (!isCarModeInCallService(packageName)) {
            Log.i(this, "handleSetAutomotiveProjection: not a valid InCallService: packageName=%s",
                    packageName);
            return;
        }
        mCarModeTracker.handleSetAutomotiveProjection(packageName);

        updateCarModeForSwitchingConnection();
    }

    public void handleReleaseAutomotiveProjection() {
        Log.i(this, "handleReleaseAutomotiveProjection");
        mCarModeTracker.handleReleaseAutomotiveProjection();

        updateCarModeForSwitchingConnection();
    }

    public void updateCarModeForSwitchingConnection() {
        if (mInCallServiceConnection != null) {
            Log.i(this, "updateCarModeForSwitchingConnection: car mode apps: %s",
+58 −13
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.telecom;

import android.annotation.NonNull;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,7 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
 * Provides various system states to the rest of the telecom codebase.
 */
public class SystemStateHelper {
public class SystemStateHelper implements UiModeManager.OnProjectionStateChangeListener {
    public interface SystemStateListener {
        /**
         * Listener method to inform interested parties when a package name requests to enter or
@@ -50,6 +51,19 @@ public class SystemStateHelper {
         */
        void onCarModeChanged(int priority, String packageName, boolean isCarMode);

        /**
         * Listener method to inform interested parties when a package has set automotive projection
         * state.
         * @param automotiveProjectionPackage the package that set automotive projection.
         */
        void onAutomotiveProjectionStateSet(String automotiveProjectionPackage);

        /**
         * Listener method to inform interested parties when automotive projection state has been
         * cleared.
         */
        void onAutomotiveProjectionStateReleased();

        /**
         * Notifies when a package has been uninstalled.
         * @param packageName the package name of the uninstalled package
@@ -99,8 +113,18 @@ public class SystemStateHelper {
        }
    };

    @Override
    public void onProjectionStateChanged(int activeProjectionTypes,
            @NonNull Set<String> projectingPackages) {
        if (projectingPackages.isEmpty()) {
            onReleaseAutomotiveProjection();
        } else {
            onSetAutomotiveProjection(projectingPackages.iterator().next());
        }
    }

    private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
    private boolean mIsCarMode;
    private boolean mIsCarModeOrProjectionActive;

    public SystemStateHelper(Context context) {
        mContext = context;
@@ -116,7 +140,9 @@ public class SystemStateHelper {
        Log.i(this, "Registering broadcast receiver: %s", intentFilter1);
        Log.i(this, "Registering broadcast receiver: %s", intentFilter2);

        mIsCarMode = getSystemCarMode();
        mContext.getSystemService(UiModeManager.class).addOnProjectionStateChangeListener(
                UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, mContext.getMainExecutor(), this);
        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
    }

    public void addListener(SystemStateListener listener) {
@@ -129,8 +155,8 @@ public class SystemStateHelper {
        return mListeners.remove(listener);
    }

    public boolean isCarMode() {
        return mIsCarMode;
    public boolean isCarModeOrProjectionActive() {
        return mIsCarModeOrProjectionActive;
    }

    public boolean isDeviceAtEar() {
@@ -215,7 +241,7 @@ public class SystemStateHelper {

    private void onEnterCarMode(int priority, String packageName) {
        Log.i(this, "Entering carmode");
        mIsCarMode = getSystemCarMode();
        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
        for (SystemStateListener listener : mListeners) {
            listener.onCarModeChanged(priority, packageName, true /* isCarMode */);
        }
@@ -223,25 +249,44 @@ public class SystemStateHelper {

    private void onExitCarMode(int priority, String packageName) {
        Log.i(this, "Exiting carmode");
        mIsCarMode = getSystemCarMode();
        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
        for (SystemStateListener listener : mListeners) {
            listener.onCarModeChanged(priority, packageName, false /* isCarMode */);
        }
    }

    private void onSetAutomotiveProjection(String packageName) {
        Log.i(this, "Automotive projection set.");
        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
        for (SystemStateListener listener : mListeners) {
            listener.onAutomotiveProjectionStateSet(packageName);
        }

    }

    private void onReleaseAutomotiveProjection() {
        Log.i(this, "Automotive projection released.");
        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
        for (SystemStateListener listener : mListeners) {
            listener.onAutomotiveProjectionStateReleased();
        }
    }

    /**
     * Checks the system for the current car mode.
     * Checks the system for the current car projection state.
     *
     * @return True if in car mode, false otherwise.
     * @return True if projection is active, false otherwise.
     */
    private boolean getSystemCarMode() {
        UiModeManager uiModeManager =
                (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
    private boolean getSystemCarModeOrProjectionState() {
        UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);

        if (uiModeManager != null) {
            return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
            return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
                    || (uiModeManager.getActiveProjectionTypes()
                            & UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0;
        }

        Log.w(this, "Got null UiModeManager, returning false.");
        return false;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -294,7 +294,7 @@ public class CallRedirectionProcessor implements CallRedirectionCallback {
         * The current rule to decide whether the implemented {@link CallRedirectionService} should
         * allow interactive responses with users is only based on whether it is in car mode.
         */
        mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarMode();
        mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarModeOrProjectionActive();
        mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper(
                context, callsManager, phoneAccountRegistrar);
        mProcessedDestinationUri = mCallRedirectionProcessorHelper.formatNumberForRedirection(
Loading