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

Commit 472f4f19 authored by Tyler Gunn's avatar Tyler Gunn Committed by android-build-merger
Browse files

Merge "Support for multiple car-mode InCallServices." am: b18634cb

am: 0ee3b391

Change-Id: Ibd41d663abdbeaf73cd88382b14056a9f19615bb
parents 150f15f6 0ee3b391
Loading
Loading
Loading
Loading
+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();
    }
}
+147 −49
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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
@@ -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.");
            }
        }
@@ -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) {
@@ -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;
@@ -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();
@@ -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) {
@@ -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 {
@@ -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;
@@ -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;
@@ -750,7 +817,7 @@ public class InCallController extends CallsManagerListenerBase {
        mTimeoutsAdapter = timeoutsAdapter;
        mDefaultDialerCache = defaultDialerCache;
        mEmergencyCallHelper = emergencyCallHelper;

        mCarModeTracker = carModeTracker;
        mSystemStateHelper.addListener(mSystemStateListener);
    }

@@ -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())) {
@@ -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) ==
@@ -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) {
@@ -1257,7 +1322,7 @@ public class InCallController extends CallsManagerListenerBase {
    }

    private boolean shouldUseCarModeUI() {
        return mSystemStateHelper.isCarMode();
        return mCarModeTracker.isInCarMode();
    }

    /**
@@ -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,
@@ -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",
@@ -1518,6 +1575,8 @@ public class InCallController extends CallsManagerListenerBase {
            mInCallServiceConnection.dump(pw);
        }
        pw.decreaseIndent();

        mCarModeTracker.dump(pw);
    }

    /**
@@ -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();
            }
        }
    }
}
+0 −12
Original line number Diff line number Diff line
@@ -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.
+0 −28
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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<>();
@@ -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 ");
+5 −34

File changed.

Preview size limit exceeded, changes collapsed.

Loading