Loading src/com/android/server/telecom/CarModeTracker.java 0 → 100644 +209 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.server.telecom; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.UiModeManager; import android.telecom.Log; import android.util.LocalLog; import com.android.internal.util.IndentingPrintWriter; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.PriorityQueue; import java.util.stream.Collectors; /** * Tracks the package names of apps which enter end exit car mode. */ 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 @NonNull String mPackageName; public CarModeApp(int priority, @NonNull String packageName) { mPriority = priority; mPackageName = Objects.requireNonNull(packageName); } /** * 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. * @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. */ public String getPackageName() { return mPackageName; } 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()); } } /** * 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. */ private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2, new CarModeAppComparator()); private final LocalLog mCarModeChangeLog = new LocalLog(20); /** * Handles a request to enter car mode by a package name. * @param priority The priority at which car mode is entered. * @param packageName The package name of the app entering car mode. */ public void handleEnterCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) { if (mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) { Log.w(this, "handleEnterCarMode: already in car mode at priority %d (apps: %s)", priority, getCarModePriorityString()); return; } if (mCarModeApps.stream().anyMatch(c -> c.getPackageName().equals(packageName))) { Log.w(this, "handleEnterCarMode: %s is already in car mode (apps: %s)", packageName, getCarModePriorityString()); return; } Log.i(this, "handleEnterCarMode: packageName=%s, priority=%d", packageName, priority); mCarModeChangeLog.log("enterCarMode: packageName=" + packageName + ", priority=" + priority); mCarModeApps.add(new CarModeApp(priority, packageName)); } /** * Handles a request to exist car mode at a priority level. * @param priority The priority level. * @param packageName The packagename of the app requesting the change. */ public void handleExitCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) { if (!mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) { Log.w(this, "handleExitCarMode: not in car mode at priority %d (apps=%s)", priority, getCarModePriorityString()); return; } if (priority != UiModeManager.DEFAULT_PRIORITY && !mCarModeApps.stream().anyMatch( c -> c.getPackageName().equals(packageName) && c.getPriority() == priority)) { Log.w(this, "handleExitCarMode: %s didn't enter car mode at priority %d (apps=%s)", packageName, priority, getCarModePriorityString()); return; } Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority); mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority=" + priority); mCarModeApps.removeIf(c -> c.getPriority() == priority); } /** * Retrieves a list of the apps which are currently in car mode, ordered by priority such that * the highest priority app is first. * @return List of apps in car mode. */ public @NonNull List<String> getCarModeApps() { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) .map(cma -> cma.getPackageName()) .collect(Collectors.toList()); } private @NonNull String getCarModePriorityString() { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]") .collect(Collectors.joining(", ")); } /** * Gets the app which is currently in car mode. This is the highest priority app which has * entered car mode. * @return The app which is in car mode. */ public @Nullable String getCurrentCarModePackage() { CarModeApp app = mCarModeApps.peek(); return app == null ? null : app.getPackageName(); } /** * @return {@code true} if the device is in car mode, {@code false} otherwise. */ public boolean isInCarMode() { return !mCarModeApps.isEmpty(); } /** * Dumps the state of the car mode tracker to the specified print writer. * @param pw */ public void dump(IndentingPrintWriter pw) { pw.println("CarModeTracker:"); pw.increaseIndent(); pw.println("Current car mode apps:"); pw.increaseIndent(); for (CarModeApp app : mCarModeApps) { pw.print("["); pw.print(app.getPriority()); pw.print("] "); pw.println(app.getPackageName()); } pw.decreaseIndent(); pw.println("Car mode history:"); pw.increaseIndent(); mCarModeChangeLog.dump(pw); pw.decreaseIndent(); pw.decreaseIndent(); } } src/com/android/server/telecom/InCallController.java +147 −49 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom; import android.Manifest; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; Loading Loading @@ -48,6 +49,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.SystemStateHelper.SystemStateListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; Loading @@ -55,6 +57,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it Loading Loading @@ -256,9 +259,13 @@ public class InCallController extends CallsManagerListenerBase { @Override public void disconnect() { if (mIsConnected) { Log.i(InCallController.this, "ICSBC#disconnect: unbinding; %s", mInCallServiceInfo); mContext.unbindService(mServiceConnection); mIsConnected = false; } else { Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s", mInCallServiceInfo); Log.addEvent(null, LogUtils.Events.INFO, "Already disconnected, ignoring request."); } } Loading @@ -270,9 +277,13 @@ public class InCallController extends CallsManagerListenerBase { @Override public void dump(IndentingPrintWriter pw) { pw.append("BindingConnection ["); pw.append(mIsConnected ? "" : "not ").append("connected, "); pw.append(mIsBound ? "" : "not ").append("bound]\n"); pw.print("BindingConnection ["); pw.print(mIsConnected ? "" : "not "); pw.print("connected, "); pw.print(mIsBound ? "" : "not "); pw.print("bound, "); pw.print(mInCallServiceInfo); pw.println("\n"); } protected void onConnected(IBinder service) { Loading Loading @@ -436,7 +447,7 @@ public class InCallController extends CallsManagerListenerBase { */ private class CarSwappingInCallServiceConnection extends InCallServiceConnection { private final InCallServiceConnection mDialerConnection; private final InCallServiceConnection mCarModeConnection; private InCallServiceConnection mCarModeConnection; private InCallServiceConnection mCurrentConnection; private boolean mIsCarMode = false; private boolean mIsConnected = false; Loading @@ -449,8 +460,13 @@ public class InCallController extends CallsManagerListenerBase { mCurrentConnection = getCurrentConnection(); } public synchronized void setCarMode(boolean isCarMode) { Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode); /** * Called when we move to a state where calls are present on the device. Chooses the * {@link InCallService} to which we should connect. * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise. */ public synchronized void chooseInitialInCallService(boolean isCarMode) { Log.i(this, "chooseInitialInCallService: " + mIsCarMode + " => " + isCarMode); if (isCarMode != mIsCarMode) { mIsCarMode = isCarMode; InCallServiceConnection newConnection = getCurrentConnection(); Loading @@ -465,6 +481,63 @@ public class InCallController extends CallsManagerListenerBase { } } /** * Invoked when {@link CarModeTracker} has determined that the device is no longer in car * mode (i.e. has no car mode {@link InCallService}). * * Switches back to the default dialer app. */ public synchronized void disableCarMode() { mIsCarMode = false; if (mIsConnected) { mCurrentConnection.disconnect(); } mCurrentConnection = mDialerConnection; int result = mDialerConnection.connect(null); mIsConnected = result == CONNECTION_SUCCEEDED; } /** * Changes the active {@link InCallService} to a car mode app. Called whenever the device * changes to car mode or the currently active car mode app changes. * @param packageName The package name of the car mode app. */ public synchronized void changeCarModeApp(String packageName) { Log.i(this, "changeCarModeApp: isCarModeNow=" + mIsCarMode); InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null : mCurrentConnection.getInfo(); InCallServiceInfo carModeConnectionInfo = getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) { Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => " + carModeConnectionInfo); if (mIsConnected) { mCurrentConnection.disconnect(); } if (carModeConnectionInfo != null) { // Valid car mode app. mCarModeConnection = mCurrentConnection = new InCallServiceBindingConnection(carModeConnectionInfo); mIsCarMode = true; } else { // Invalid car mode app; don't expect this but should handle it gracefully. mCarModeConnection = null; mIsCarMode = false; mCurrentConnection = mDialerConnection; } int result = mCurrentConnection.connect(null); mIsConnected = result == CONNECTION_SUCCEEDED; } else { Log.i(this, "changeCarModeApp: unchanged; " + currentConnectionInfo + " => " + carModeConnectionInfo); } } @Override public int connect(Call call) { if (mIsConnected) { Loading @@ -484,6 +557,7 @@ public class InCallController extends CallsManagerListenerBase { @Override public void disconnect() { if (mIsConnected) { Log.i(InCallController.this, "CSICSC: disconnect %s", mCurrentConnection); mCurrentConnection.disconnect(); mIsConnected = false; } else { Loading Loading @@ -699,18 +773,9 @@ public class InCallController extends CallsManagerListenerBase { } }; private final SystemStateListener mSystemStateListener = new SystemStateListener() { @Override public void onCarModeChanged(boolean isCarMode) { if (mInCallServiceConnection != null) { mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); } } @Override public void onCarModeChanged(int priority, String packageName, boolean isCarMode) { } }; private final SystemStateListener mSystemStateListener = (priority, packageName, isCarMode) -> InCallController.this.handleCarModeChange( priority, packageName, isCarMode); private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; Loading Loading @@ -739,10 +804,12 @@ public class InCallController extends CallsManagerListenerBase { // The future will complete with true if binding succeeds, false if it timed out. private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true); private final CarModeTracker mCarModeTracker; public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper) { EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker) { mContext = context; mLock = lock; mCallsManager = callsManager; Loading @@ -750,7 +817,7 @@ public class InCallController extends CallsManagerListenerBase { mTimeoutsAdapter = timeoutsAdapter; mDefaultDialerCache = defaultDialerCache; mEmergencyCallHelper = emergencyCallHelper; mCarModeTracker = carModeTracker; mSystemStateHelper.addListener(mSystemStateListener); } Loading Loading @@ -1113,7 +1180,7 @@ public class InCallController extends CallsManagerListenerBase { systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall()); InCallServiceConnection carModeInCall = null; InCallServiceInfo carModeComponentInfo = getCarModeComponent(); InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent(); if (carModeComponentInfo != null && !carModeComponentInfo.getComponentName().equals( mDefaultDialerCache.getSystemDialerComponent())) { Loading @@ -1124,7 +1191,7 @@ public class InCallController extends CallsManagerListenerBase { new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); } mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI()); // Actually try binding to the UI InCallService. If the response if (mInCallServiceConnection.connect(call) == Loading Loading @@ -1171,11 +1238,9 @@ public class InCallController extends CallsManagerListenerBase { return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI); } private InCallServiceInfo getCarModeComponent() { // The signatures of getInCallServiceComponent differ in the types of the first parameter, // and passing in null is inherently ambiguous. (If no car mode component found) String defaultCarMode = mCallsManager.getRoleManagerAdapter().getCarModeDialerApp(); return getInCallServiceComponent(defaultCarMode, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); private InCallServiceInfo getCurrentCarModeComponent() { return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(), IN_CALL_SERVICE_TYPE_CAR_MODE_UI); } private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { Loading Loading @@ -1257,7 +1322,7 @@ public class InCallController extends CallsManagerListenerBase { } private boolean shouldUseCarModeUI() { return mSystemStateHelper.isCarMode(); return mCarModeTracker.isInCarMode(); } /** Loading Loading @@ -1285,27 +1350,25 @@ public class InCallController extends CallsManagerListenerBase { // Check to see if the service holds permissions or metadata for third party apps. boolean isUIService = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI); boolean isThirdPartyCompanionApp = packageManager.checkPermission( Manifest.permission.CALL_COMPANION_APP, serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED && !isUIService; // Check to see if the service is a car-mode UI type by checking that it has the // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the // car-mode UI metadata. boolean hasControlInCallPermission = packageManager.checkPermission( // We check the permission grant on all of the packages contained in the InCallService's // same UID to see if any of them have been granted the permission. This accomodates the // CTS tests, which have some shared UID stuff going on in order to work. It also still // obeys the permission model since a single APK typically normally only has a single UID. String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid); boolean hasControlInCallPermission = Arrays.stream(uidPackages).anyMatch( p -> packageManager.checkPermission( Manifest.permission.CONTROL_INCALL_EXPERIENCE, serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; p) == PackageManager.PERMISSION_GRANTED); boolean isCarModeUIService = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean( TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false); if (isCarModeUIService) { // ThirdPartyInCallService shouldn't be used when role manager hasn't assigned any car // mode role holders, i.e. packageName is null. if (hasControlInCallPermission || (isThirdPartyCompanionApp && packageName != null)) { if (isCarModeUIService && hasControlInCallPermission) { return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } } // Check to see that it is the default dialer package boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, Loading @@ -1317,15 +1380,9 @@ public class InCallController extends CallsManagerListenerBase { // Also allow any in-call service that has the control-experience permission (to ensure // that it is a system app) and doesn't claim to show any UI. if (!isUIService && !isCarModeUIService) { if (hasControlInCallPermission && !isThirdPartyCompanionApp) { if (!isUIService && !isCarModeUIService && hasControlInCallPermission) { return IN_CALL_SERVICE_TYPE_NON_UI; } // Third party companion alls without CONTROL_INCALL_EXPERIENCE permission. if (!hasControlInCallPermission && isThirdPartyCompanionApp) { return IN_CALL_SERVICE_TYPE_COMPANION; } } // Anything else that remains, we will not bind to. Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", Loading Loading @@ -1518,6 +1575,8 @@ public class InCallController extends CallsManagerListenerBase { mInCallServiceConnection.dump(pw); } pw.decreaseIndent(); mCarModeTracker.dump(pw); } /** Loading Loading @@ -1599,4 +1658,43 @@ public class InCallController extends CallsManagerListenerBase { public Handler getHandler() { return mHandler; } /** * Determines if the specified package is a valid car mode {@link InCallService}. * @param packageName The package name to check. * @return {@code true} if the package has a valid car mode {@link InCallService} defined, * {@code false} otherwise. */ private boolean isCarModeInCallService(@NonNull String packageName) { InCallServiceInfo info = getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } public void handleCarModeChange(int priority, String packageName, boolean isCarMode) { Log.i(this, "handleCarModeChange: packageName=%s, priority=%d, isCarMode=%b", packageName, priority, isCarMode); if (!isCarModeInCallService(packageName)) { Log.i(this, "handleCarModeChange: not a valid InCallService; packageName=%s", packageName); return; } if (isCarMode) { mCarModeTracker.handleEnterCarMode(priority, packageName); } else { mCarModeTracker.handleExitCarMode(priority, packageName); } if (mInCallServiceConnection != null) { Log.i(this, "handleCarModeChange: car mode apps: %s", mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", "))); if (shouldUseCarModeUI()) { mInCallServiceConnection.changeCarModeApp( mCarModeTracker.getCurrentCarModePackage()); } else { mInCallServiceConnection.disableCarMode(); } } } } src/com/android/server/telecom/RoleManagerAdapter.java +0 −12 Original line number Diff line number Diff line Loading @@ -101,18 +101,6 @@ public interface RoleManagerAdapter { */ void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); /** * @return Package name of the car more app or {@code null} if there are no apps that match. */ String getCarModeDialerApp(); /** * Override the automotive app with another value. Used for testing purposes only. * @param packageName Package name of the automotive app. Where * {@code null}, the override is removed. */ void setTestAutoModeApp(String packageName); /** * Using role manager needs to know the current user handle. Need to make sure the role manager * adapter can pass this to role manager. As it changes, we'll pass it in. Loading src/com/android/server/telecom/RoleManagerAdapterImpl.java +0 −28 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { private String mOverrideDefaultCallRedirectionApp = null; private String mOverrideDefaultCallScreeningApp = null; private String mOverrideDefaultCarModeApp = null; private String mOverrideDefaultDialerApp = null; private List<String> mOverrideCallCompanionApps = new ArrayList<>(); private Context mContext; Loading Loading @@ -111,19 +110,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { } } @Override public String getCarModeDialerApp() { if (mOverrideDefaultCarModeApp != null) { return mOverrideDefaultCarModeApp; } return getRoleManagerCarModeDialerApp(); } @Override public void setTestAutoModeApp(String packageName) { mOverrideDefaultCarModeApp = packageName; } @Override public void setCurrentUserHandle(UserHandle currentUserHandle) { mCurrentUserHandle = currentUserHandle; Loading @@ -147,11 +133,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { return roleHolders.get(0); } // TODO in R: query and return car mode apps private String getRoleManagerCarModeDialerApp() { return null; } // TODO in R: Use companion app manager private List<String> getRoleManagerCallCompanionApps() { return new ArrayList<>(); Loading Loading @@ -213,15 +194,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { } pw.println(); pw.print("DefaultCarModeDialerApp: "); if (mOverrideDefaultCarModeApp != null) { pw.print("(override "); pw.print(mOverrideDefaultCarModeApp); pw.print(") "); pw.print(getRoleManagerCarModeDialerApp()); } pw.println(); pw.print("DefaultCallCompanionApps: "); if (mOverrideCallCompanionApps != null) { pw.print("(override "); Loading src/com/android/server/telecom/SystemStateHelper.java +5 −34 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/server/telecom/CarModeTracker.java 0 → 100644 +209 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.server.telecom; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.UiModeManager; import android.telecom.Log; import android.util.LocalLog; import com.android.internal.util.IndentingPrintWriter; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.PriorityQueue; import java.util.stream.Collectors; /** * Tracks the package names of apps which enter end exit car mode. */ 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 @NonNull String mPackageName; public CarModeApp(int priority, @NonNull String packageName) { mPriority = priority; mPackageName = Objects.requireNonNull(packageName); } /** * 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. * @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. */ public String getPackageName() { return mPackageName; } 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()); } } /** * 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. */ private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2, new CarModeAppComparator()); private final LocalLog mCarModeChangeLog = new LocalLog(20); /** * Handles a request to enter car mode by a package name. * @param priority The priority at which car mode is entered. * @param packageName The package name of the app entering car mode. */ public void handleEnterCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) { if (mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) { Log.w(this, "handleEnterCarMode: already in car mode at priority %d (apps: %s)", priority, getCarModePriorityString()); return; } if (mCarModeApps.stream().anyMatch(c -> c.getPackageName().equals(packageName))) { Log.w(this, "handleEnterCarMode: %s is already in car mode (apps: %s)", packageName, getCarModePriorityString()); return; } Log.i(this, "handleEnterCarMode: packageName=%s, priority=%d", packageName, priority); mCarModeChangeLog.log("enterCarMode: packageName=" + packageName + ", priority=" + priority); mCarModeApps.add(new CarModeApp(priority, packageName)); } /** * Handles a request to exist car mode at a priority level. * @param priority The priority level. * @param packageName The packagename of the app requesting the change. */ public void handleExitCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) { if (!mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) { Log.w(this, "handleExitCarMode: not in car mode at priority %d (apps=%s)", priority, getCarModePriorityString()); return; } if (priority != UiModeManager.DEFAULT_PRIORITY && !mCarModeApps.stream().anyMatch( c -> c.getPackageName().equals(packageName) && c.getPriority() == priority)) { Log.w(this, "handleExitCarMode: %s didn't enter car mode at priority %d (apps=%s)", packageName, priority, getCarModePriorityString()); return; } Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority); mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority=" + priority); mCarModeApps.removeIf(c -> c.getPriority() == priority); } /** * Retrieves a list of the apps which are currently in car mode, ordered by priority such that * the highest priority app is first. * @return List of apps in car mode. */ public @NonNull List<String> getCarModeApps() { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) .map(cma -> cma.getPackageName()) .collect(Collectors.toList()); } private @NonNull String getCarModePriorityString() { return mCarModeApps .stream() .sorted(mCarModeApps.comparator()) .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]") .collect(Collectors.joining(", ")); } /** * Gets the app which is currently in car mode. This is the highest priority app which has * entered car mode. * @return The app which is in car mode. */ public @Nullable String getCurrentCarModePackage() { CarModeApp app = mCarModeApps.peek(); return app == null ? null : app.getPackageName(); } /** * @return {@code true} if the device is in car mode, {@code false} otherwise. */ public boolean isInCarMode() { return !mCarModeApps.isEmpty(); } /** * Dumps the state of the car mode tracker to the specified print writer. * @param pw */ public void dump(IndentingPrintWriter pw) { pw.println("CarModeTracker:"); pw.increaseIndent(); pw.println("Current car mode apps:"); pw.increaseIndent(); for (CarModeApp app : mCarModeApps) { pw.print("["); pw.print(app.getPriority()); pw.print("] "); pw.println(app.getPackageName()); } pw.decreaseIndent(); pw.println("Car mode history:"); pw.increaseIndent(); mCarModeChangeLog.dump(pw); pw.decreaseIndent(); pw.decreaseIndent(); } }
src/com/android/server/telecom/InCallController.java +147 −49 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.telecom; import android.Manifest; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; Loading Loading @@ -48,6 +49,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.telecom.SystemStateHelper.SystemStateListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; Loading @@ -55,6 +57,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it Loading Loading @@ -256,9 +259,13 @@ public class InCallController extends CallsManagerListenerBase { @Override public void disconnect() { if (mIsConnected) { Log.i(InCallController.this, "ICSBC#disconnect: unbinding; %s", mInCallServiceInfo); mContext.unbindService(mServiceConnection); mIsConnected = false; } else { Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s", mInCallServiceInfo); Log.addEvent(null, LogUtils.Events.INFO, "Already disconnected, ignoring request."); } } Loading @@ -270,9 +277,13 @@ public class InCallController extends CallsManagerListenerBase { @Override public void dump(IndentingPrintWriter pw) { pw.append("BindingConnection ["); pw.append(mIsConnected ? "" : "not ").append("connected, "); pw.append(mIsBound ? "" : "not ").append("bound]\n"); pw.print("BindingConnection ["); pw.print(mIsConnected ? "" : "not "); pw.print("connected, "); pw.print(mIsBound ? "" : "not "); pw.print("bound, "); pw.print(mInCallServiceInfo); pw.println("\n"); } protected void onConnected(IBinder service) { Loading Loading @@ -436,7 +447,7 @@ public class InCallController extends CallsManagerListenerBase { */ private class CarSwappingInCallServiceConnection extends InCallServiceConnection { private final InCallServiceConnection mDialerConnection; private final InCallServiceConnection mCarModeConnection; private InCallServiceConnection mCarModeConnection; private InCallServiceConnection mCurrentConnection; private boolean mIsCarMode = false; private boolean mIsConnected = false; Loading @@ -449,8 +460,13 @@ public class InCallController extends CallsManagerListenerBase { mCurrentConnection = getCurrentConnection(); } public synchronized void setCarMode(boolean isCarMode) { Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode); /** * Called when we move to a state where calls are present on the device. Chooses the * {@link InCallService} to which we should connect. * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise. */ public synchronized void chooseInitialInCallService(boolean isCarMode) { Log.i(this, "chooseInitialInCallService: " + mIsCarMode + " => " + isCarMode); if (isCarMode != mIsCarMode) { mIsCarMode = isCarMode; InCallServiceConnection newConnection = getCurrentConnection(); Loading @@ -465,6 +481,63 @@ public class InCallController extends CallsManagerListenerBase { } } /** * Invoked when {@link CarModeTracker} has determined that the device is no longer in car * mode (i.e. has no car mode {@link InCallService}). * * Switches back to the default dialer app. */ public synchronized void disableCarMode() { mIsCarMode = false; if (mIsConnected) { mCurrentConnection.disconnect(); } mCurrentConnection = mDialerConnection; int result = mDialerConnection.connect(null); mIsConnected = result == CONNECTION_SUCCEEDED; } /** * Changes the active {@link InCallService} to a car mode app. Called whenever the device * changes to car mode or the currently active car mode app changes. * @param packageName The package name of the car mode app. */ public synchronized void changeCarModeApp(String packageName) { Log.i(this, "changeCarModeApp: isCarModeNow=" + mIsCarMode); InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null : mCurrentConnection.getInfo(); InCallServiceInfo carModeConnectionInfo = getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) { Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => " + carModeConnectionInfo); if (mIsConnected) { mCurrentConnection.disconnect(); } if (carModeConnectionInfo != null) { // Valid car mode app. mCarModeConnection = mCurrentConnection = new InCallServiceBindingConnection(carModeConnectionInfo); mIsCarMode = true; } else { // Invalid car mode app; don't expect this but should handle it gracefully. mCarModeConnection = null; mIsCarMode = false; mCurrentConnection = mDialerConnection; } int result = mCurrentConnection.connect(null); mIsConnected = result == CONNECTION_SUCCEEDED; } else { Log.i(this, "changeCarModeApp: unchanged; " + currentConnectionInfo + " => " + carModeConnectionInfo); } } @Override public int connect(Call call) { if (mIsConnected) { Loading @@ -484,6 +557,7 @@ public class InCallController extends CallsManagerListenerBase { @Override public void disconnect() { if (mIsConnected) { Log.i(InCallController.this, "CSICSC: disconnect %s", mCurrentConnection); mCurrentConnection.disconnect(); mIsConnected = false; } else { Loading Loading @@ -699,18 +773,9 @@ public class InCallController extends CallsManagerListenerBase { } }; private final SystemStateListener mSystemStateListener = new SystemStateListener() { @Override public void onCarModeChanged(boolean isCarMode) { if (mInCallServiceConnection != null) { mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); } } @Override public void onCarModeChanged(int priority, String packageName, boolean isCarMode) { } }; private final SystemStateListener mSystemStateListener = (priority, packageName, isCarMode) -> InCallController.this.handleCarModeChange( priority, packageName, isCarMode); private static final int IN_CALL_SERVICE_TYPE_INVALID = 0; private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1; Loading Loading @@ -739,10 +804,12 @@ public class InCallController extends CallsManagerListenerBase { // The future will complete with true if binding succeeds, false if it timed out. private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true); private final CarModeTracker mCarModeTracker; public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager, SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper) { EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker) { mContext = context; mLock = lock; mCallsManager = callsManager; Loading @@ -750,7 +817,7 @@ public class InCallController extends CallsManagerListenerBase { mTimeoutsAdapter = timeoutsAdapter; mDefaultDialerCache = defaultDialerCache; mEmergencyCallHelper = emergencyCallHelper; mCarModeTracker = carModeTracker; mSystemStateHelper.addListener(mSystemStateListener); } Loading Loading @@ -1113,7 +1180,7 @@ public class InCallController extends CallsManagerListenerBase { systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall()); InCallServiceConnection carModeInCall = null; InCallServiceInfo carModeComponentInfo = getCarModeComponent(); InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent(); if (carModeComponentInfo != null && !carModeComponentInfo.getComponentName().equals( mDefaultDialerCache.getSystemDialerComponent())) { Loading @@ -1124,7 +1191,7 @@ public class InCallController extends CallsManagerListenerBase { new CarSwappingInCallServiceConnection(systemInCall, carModeInCall); } mInCallServiceConnection.setCarMode(shouldUseCarModeUI()); mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI()); // Actually try binding to the UI InCallService. If the response if (mInCallServiceConnection.connect(call) == Loading Loading @@ -1171,11 +1238,9 @@ public class InCallController extends CallsManagerListenerBase { return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI); } private InCallServiceInfo getCarModeComponent() { // The signatures of getInCallServiceComponent differ in the types of the first parameter, // and passing in null is inherently ambiguous. (If no car mode component found) String defaultCarMode = mCallsManager.getRoleManagerAdapter().getCarModeDialerApp(); return getInCallServiceComponent(defaultCarMode, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); private InCallServiceInfo getCurrentCarModeComponent() { return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(), IN_CALL_SERVICE_TYPE_CAR_MODE_UI); } private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) { Loading Loading @@ -1257,7 +1322,7 @@ public class InCallController extends CallsManagerListenerBase { } private boolean shouldUseCarModeUI() { return mSystemStateHelper.isCarMode(); return mCarModeTracker.isInCarMode(); } /** Loading Loading @@ -1285,27 +1350,25 @@ public class InCallController extends CallsManagerListenerBase { // Check to see if the service holds permissions or metadata for third party apps. boolean isUIService = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI); boolean isThirdPartyCompanionApp = packageManager.checkPermission( Manifest.permission.CALL_COMPANION_APP, serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED && !isUIService; // Check to see if the service is a car-mode UI type by checking that it has the // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the // car-mode UI metadata. boolean hasControlInCallPermission = packageManager.checkPermission( // We check the permission grant on all of the packages contained in the InCallService's // same UID to see if any of them have been granted the permission. This accomodates the // CTS tests, which have some shared UID stuff going on in order to work. It also still // obeys the permission model since a single APK typically normally only has a single UID. String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid); boolean hasControlInCallPermission = Arrays.stream(uidPackages).anyMatch( p -> packageManager.checkPermission( Manifest.permission.CONTROL_INCALL_EXPERIENCE, serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED; p) == PackageManager.PERMISSION_GRANTED); boolean isCarModeUIService = serviceInfo.metaData != null && serviceInfo.metaData.getBoolean( TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false); if (isCarModeUIService) { // ThirdPartyInCallService shouldn't be used when role manager hasn't assigned any car // mode role holders, i.e. packageName is null. if (hasControlInCallPermission || (isThirdPartyCompanionApp && packageName != null)) { if (isCarModeUIService && hasControlInCallPermission) { return IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } } // Check to see that it is the default dialer package boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName, Loading @@ -1317,15 +1380,9 @@ public class InCallController extends CallsManagerListenerBase { // Also allow any in-call service that has the control-experience permission (to ensure // that it is a system app) and doesn't claim to show any UI. if (!isUIService && !isCarModeUIService) { if (hasControlInCallPermission && !isThirdPartyCompanionApp) { if (!isUIService && !isCarModeUIService && hasControlInCallPermission) { return IN_CALL_SERVICE_TYPE_NON_UI; } // Third party companion alls without CONTROL_INCALL_EXPERIENCE permission. if (!hasControlInCallPermission && isThirdPartyCompanionApp) { return IN_CALL_SERVICE_TYPE_COMPANION; } } // Anything else that remains, we will not bind to. Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b", Loading Loading @@ -1518,6 +1575,8 @@ public class InCallController extends CallsManagerListenerBase { mInCallServiceConnection.dump(pw); } pw.decreaseIndent(); mCarModeTracker.dump(pw); } /** Loading Loading @@ -1599,4 +1658,43 @@ public class InCallController extends CallsManagerListenerBase { public Handler getHandler() { return mHandler; } /** * Determines if the specified package is a valid car mode {@link InCallService}. * @param packageName The package name to check. * @return {@code true} if the package has a valid car mode {@link InCallService} defined, * {@code false} otherwise. */ private boolean isCarModeInCallService(@NonNull String packageName) { InCallServiceInfo info = getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI); return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI; } public void handleCarModeChange(int priority, String packageName, boolean isCarMode) { Log.i(this, "handleCarModeChange: packageName=%s, priority=%d, isCarMode=%b", packageName, priority, isCarMode); if (!isCarModeInCallService(packageName)) { Log.i(this, "handleCarModeChange: not a valid InCallService; packageName=%s", packageName); return; } if (isCarMode) { mCarModeTracker.handleEnterCarMode(priority, packageName); } else { mCarModeTracker.handleExitCarMode(priority, packageName); } if (mInCallServiceConnection != null) { Log.i(this, "handleCarModeChange: car mode apps: %s", mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", "))); if (shouldUseCarModeUI()) { mInCallServiceConnection.changeCarModeApp( mCarModeTracker.getCurrentCarModePackage()); } else { mInCallServiceConnection.disableCarMode(); } } } }
src/com/android/server/telecom/RoleManagerAdapter.java +0 −12 Original line number Diff line number Diff line Loading @@ -101,18 +101,6 @@ public interface RoleManagerAdapter { */ void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded); /** * @return Package name of the car more app or {@code null} if there are no apps that match. */ String getCarModeDialerApp(); /** * Override the automotive app with another value. Used for testing purposes only. * @param packageName Package name of the automotive app. Where * {@code null}, the override is removed. */ void setTestAutoModeApp(String packageName); /** * Using role manager needs to know the current user handle. Need to make sure the role manager * adapter can pass this to role manager. As it changes, we'll pass it in. Loading
src/com/android/server/telecom/RoleManagerAdapterImpl.java +0 −28 Original line number Diff line number Diff line Loading @@ -38,7 +38,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { private String mOverrideDefaultCallRedirectionApp = null; private String mOverrideDefaultCallScreeningApp = null; private String mOverrideDefaultCarModeApp = null; private String mOverrideDefaultDialerApp = null; private List<String> mOverrideCallCompanionApps = new ArrayList<>(); private Context mContext; Loading Loading @@ -111,19 +110,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { } } @Override public String getCarModeDialerApp() { if (mOverrideDefaultCarModeApp != null) { return mOverrideDefaultCarModeApp; } return getRoleManagerCarModeDialerApp(); } @Override public void setTestAutoModeApp(String packageName) { mOverrideDefaultCarModeApp = packageName; } @Override public void setCurrentUserHandle(UserHandle currentUserHandle) { mCurrentUserHandle = currentUserHandle; Loading @@ -147,11 +133,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { return roleHolders.get(0); } // TODO in R: query and return car mode apps private String getRoleManagerCarModeDialerApp() { return null; } // TODO in R: Use companion app manager private List<String> getRoleManagerCallCompanionApps() { return new ArrayList<>(); Loading Loading @@ -213,15 +194,6 @@ public class RoleManagerAdapterImpl implements RoleManagerAdapter { } pw.println(); pw.print("DefaultCarModeDialerApp: "); if (mOverrideDefaultCarModeApp != null) { pw.print("(override "); pw.print(mOverrideDefaultCarModeApp); pw.print(") "); pw.print(getRoleManagerCarModeDialerApp()); } pw.println(); pw.print("DefaultCallCompanionApps: "); if (mOverrideCallCompanionApps != null) { pw.print("(override "); Loading
src/com/android/server/telecom/SystemStateHelper.java +5 −34 File changed.Preview size limit exceeded, changes collapsed. Show changes