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

Commit a62ca2b5 authored by Tyler Gunn's avatar Tyler Gunn Committed by Android (Google) Code Review
Browse files

Merge "Call Screening and Caller ID changes"

parents cf67d916 c74e1ec6
Loading
Loading
Loading
Loading
+247 −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.server.telecom;

import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telecom.CallIdentification;
import android.telecom.CallScreeningService;
import android.telecom.Log;
import android.telecom.Logging.Session;
import android.text.TextUtils;

import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;

import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * Helper class for performing operations with {@link CallScreeningService}s.
 */
public class CallScreeningServiceHelper {
    private static final String TAG = CallScreeningServiceHelper.class.getSimpleName();

    /**
     * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
     * app.
     */
    public interface AppLabelProxy {
        String getAppLabel(String packageName);
    }

    /**
     * Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses
     * from the call screening service to be handled.
     */
    private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
        @Override
        public void allowCall(String s) throws RemoteException {
            // no-op; we don't allow this on outgoing calls.
        }

        @Override
        public void disallowCall(String s, boolean b, boolean b1, boolean b2,
                ComponentName componentName) throws RemoteException {
            // no-op; we don't allow this on outgoing calls.
        }

        @Override
        public void provideCallIdentification(String callId, CallIdentification callIdentification)
                throws RemoteException {
            Log.startSession("CSA.pCI");
            long token = Binder.clearCallingIdentity();
            try {
                synchronized (mTelecomLock) {
                    if (mCall != null && mCall.getId().equals(callId)) {
                        Log.i(TAG, "provideCallIdentification - got call ID");
                        callIdentification.setCallScreeningAppName(mAppLabelProxy.getAppLabel(
                                mPackageName));
                        callIdentification.setCallScreeningPackageName(mPackageName);
                        mFuture.complete(callIdentification);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(token);
                Log.endSession();
            }
            mFuture.complete(null);
        }
    }

    private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
    private final TelecomSystem.SyncRoot mTelecomLock;
    private final Call mCall;
    private final UserHandle mUserHandle;
    private final Context mContext;
    private final AppLabelProxy mAppLabelProxy;
    private final Session mLoggingSession;
    private CompletableFuture<CallIdentification> mFuture;
    private String mPackageName;

    public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock,
            String packageName, ParcelableCallUtils.Converter converter,
            UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) {
        mContext = context;
        mTelecomLock = telecomLock;
        mParcelableCallUtilsConverter = converter;
        mCall = call;
        mUserHandle = userHandle;
        mPackageName = packageName;
        mAppLabelProxy = appLabelProxy;
        mLoggingSession = Log.createSubsession();
    }

    /**
     * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService}
     * @return
     */
    public CompletableFuture<CallIdentification> process() {
        Log.d(this, "process");
        return bindAndGetCallIdentification();
    }

    public CompletableFuture<CallIdentification> bindAndGetCallIdentification() {
        Log.d(this, "bindAndGetCallIdentification");
        if (mPackageName == null) {
            return CompletableFuture.completedFuture(null);
        }

        mFuture = new CompletableFuture<>();

        ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                ICallScreeningService screeningService =
                        ICallScreeningService.Stub.asInterface(service);
                Log.continueSession(mLoggingSession, "CSSH.oSC");
                try {
                    try {
                        screeningService.screenCall(new CallScreeningAdapter(),
                                mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall));
                    } catch (RemoteException e) {
                        Log.w(CallScreeningServiceHelper.this,
                                "Cancelling call id due to remote exception");
                        mFuture.complete(null);
                    }
                } finally {
                    Log.endSession();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                // No locking needed -- CompletableFuture only lets one thread call complete.
                Log.continueSession(mLoggingSession, "CSSH.oSD");
                try {
                    if (!mFuture.isDone()) {
                        Log.w(CallScreeningServiceHelper.this,
                                "Cancelling outgoing call screen due to service disconnect.");
                    }
                    mFuture.complete(null);
                } finally {
                    Log.endSession();
                }
            }
        };

        if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) {
            Log.i(this, "bindAndGetCallIdentification - bind failed");
            Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName);
            mFuture.complete(null);
        }

        // Set up a timeout so that we're not waiting forever for the caller ID information.
        Handler handler = new Handler();
        handler.postDelayed(() -> {
                    // No locking needed -- CompletableFuture only lets one thread call complete.
                    Log.continueSession(mLoggingSession, "CSSH.timeout");
                    try {
                        if (!mFuture.isDone()) {
                            Log.w(TAG, "Cancelling call id process due to timeout");
                        }
                        mFuture.complete(null);
                    } finally {
                        Log.endSession();
                    }
                },
                Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
        return mFuture;
    }

    /**
     * Binds to a {@link CallScreeningService}.
     * @param context The current context.
     * @param userHandle User to bind as.
     * @param packageName Package name of the {@link CallScreeningService}.
     * @param serviceConnection The {@link ServiceConnection} to be notified of binding.
     * @return {@code true} if binding succeeds, {@code false} otherwise.
     */
    public static boolean bindCallScreeningService(Context context, UserHandle userHandle,
            String packageName, ServiceConnection serviceConnection) {
        if (TextUtils.isEmpty(packageName)) {
            Log.i(TAG, "PackageName is empty. Not performing call screening.");
            return false;
        }

        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
                .setPackage(packageName);
        List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser(
                intent, 0, userHandle.getIdentifier());
        if (entries.isEmpty()) {
            Log.i(TAG, packageName + " has no call screening service defined.");
            return false;
        }

        ResolveInfo entry = entries.get(0);
        if (entry.serviceInfo == null) {
            Log.w(TAG, packageName + " call screening service has invalid service info");
            return false;
        }

        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
                Manifest.permission.BIND_SCREENING_SERVICE)) {
            Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
                    entry.serviceInfo.packageName);
            return false;
        }

        ComponentName componentName =
                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
        intent.setComponent(componentName);
        if (context.bindServiceAsUser(
                intent,
                serviceConnection,
                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
                UserHandle.CURRENT)) {
            Log.d(TAG, "bindService, found service, waiting for it to connect");
            return true;
        }

        return false;
    }
}
+43 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.telecom.Log;
import android.telecom.Logging.Runnable;
import android.telecom.Logging.Session;
import android.text.TextUtils;
import android.util.Pair;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CallerInfo;
@@ -37,6 +38,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class CallerInfoLookupHelper {
    public interface OnQueryCompleteListener {
@@ -77,6 +79,47 @@ public class CallerInfoLookupHelper {
        mLock = lock;
    }

    /**
     * Generates a CompletableFuture which performs a contacts lookup asynchronously.  The future
     * returns a {@link Pair} containing the original handle which is being looked up and any
     * {@link CallerInfo} which was found.
     * @param handle
     * @return {@link CompletableFuture} to perform the contacts lookup.
     */
    public CompletableFuture<Pair<Uri, CallerInfo>> startLookup(final Uri handle) {
        // Create the returned future and
        final CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();

        final String number = handle.getSchemeSpecificPart();
        if (TextUtils.isEmpty(number)) {
            // Nothing to do here, just finish.
            Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - no number; end early");
            callerInfoFuture.complete(new Pair<>(handle, null));
            return callerInfoFuture;
        }

        // Setup a query complete listener which will get the results of the contacts lookup.
        OnQueryCompleteListener listener = new OnQueryCompleteListener() {
            @Override
            public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
                Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - found info for %s",
                        Log.piiHandle(handle));
                // Got something, so complete the future.
                callerInfoFuture.complete(new Pair<>(handle, info));
            }

            @Override
            public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
                // No-op for now; not something this future cares about.
            }
        };

        // Start async lookup.
        startLookup(handle, listener);

        return callerInfoFuture;
    }

    public void startLookup(final Uri handle, OnQueryCompleteListener listener) {
        if (handle == null) {
            listener.onCallerInfoQueryComplete(handle, null);
+74 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.provider.BlockedNumberContract.SystemContract;
import android.provider.CallLog.Calls;
import android.provider.Settings;
import android.telecom.CallAudioState;
import android.telecom.CallIdentification;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -612,7 +613,7 @@ public class CallsManager extends Call.ListenerBase
        filters.add(new CallScreeningServiceController(mContext, this, mPhoneAccountRegistrar,
                new ParcelableCallUtils.Converter(), mLock,
                new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper,
                new CallScreeningServiceController.AppLabelProxy() {
                new CallScreeningServiceHelper.AppLabelProxy() {
                    @Override
                    public String getAppLabel(String packageName) {
                        PackageManager pm = mContext.getPackageManager();
@@ -1436,6 +1437,34 @@ public class CallsManager extends Call.ListenerBase
                            return mPendingAccountSelection;
                        }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA"));

        // Potentially perform call identification for dialed TEL scheme numbers.
        if (PhoneAccount.SCHEME_TEL.equals(handle.getScheme())) {
            // Perform an asynchronous contacts lookup in this stage; ensure post-dial digits are
            // not included.
            CompletableFuture<Pair<Uri, CallerInfo>> contactLookupFuture =
                    mCallerInfoLookupHelper.startLookup(Uri.fromParts(handle.getScheme(),
                            PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart()),
                            null));

            // Once the phone account selection stage has completed, we can handle the results from
            // that with the contacts lookup in order to determine if we should lookup bind to the
            // CallScreeningService in order for it to potentially provide caller ID.
            dialerSelectPhoneAccountFuture.thenAcceptBothAsync(contactLookupFuture,
                    (callPhoneAccountHandlePair, uriCallerInfoPair) -> {
                        Call theCall = callPhoneAccountHandlePair.first;
                        boolean isInContacts = uriCallerInfoPair.second != null
                                && uriCallerInfoPair.second.contactExists;
                        Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s",
                                isInContacts);

                        // We only want to provide a CallScreeningService with a call if its not in
                        // contacts.
                        if (!isInContacts) {
                            performCallIdentification(theCall);
                        }
            }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pCSB"));
        }

        // Finally, after all user interaction is complete, we execute this code to finish setting
        // up the outgoing call. The inner method always returns a completed future containing the
        // call that we've finished setting up.
@@ -1497,6 +1526,50 @@ public class CallsManager extends Call.ListenerBase
        return mLatestPostSelectionProcessingFuture;
    }

    /**
     * Performs call identification for an outgoing phone call.
     * @param theCall The outgoing call to perform identification.
     */
    private void performCallIdentification(Call theCall) {
        // Find the user chosen call screening app.
        String callScreeningApp =
                mRoleManagerAdapter.getDefaultCallScreeningApp();

        CompletableFuture<CallIdentification> future =
                new CallScreeningServiceHelper(mContext,
                mLock,
                callScreeningApp,
                new ParcelableCallUtils.Converter(),
                mCurrentUserHandle,
                theCall,
                new CallScreeningServiceHelper.AppLabelProxy() {
                    @Override
                    public String getAppLabel(String packageName) {
                        PackageManager pm = mContext.getPackageManager();
                        try {
                            ApplicationInfo info = pm.getApplicationInfo(
                                    packageName, 0);
                            return (String) pm.getApplicationLabel(info);
                        } catch (PackageManager.NameNotFoundException nnfe) {
                            Log.w(this, "Could not determine package name.");
                        }

                        return null;
                    }
                }).process();

        // When we are done, apply call identification to the call.
        future.thenApply(v -> {
            Log.i(CallsManager.this, "setting caller ID: %s", v);
            if (v != null) {
                synchronized (mLock) {
                    theCall.setCallIdentification(v);
                }
            }
            return null;
        });
    }

    /**
     * Finds the {@link PhoneAccountHandle}(s) which could potentially be used to place an outgoing
     * call.  Takes into account the following:
+25 −3
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.server.telecom;

import static android.telecom.Call.Details.DIRECTION_INCOMING;
import static android.telecom.Call.Details.DIRECTION_OUTGOING;
import static android.telecom.Call.Details.DIRECTION_UNKNOWN;

import android.net.Uri;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -167,6 +171,14 @@ public class ParcelableCallUtils {
        }

        ParcelableRttCall rttCall = includeRttCall ? getParcelableRttCall(call) : null;
        int callDirection;
        if (call.isIncoming()) {
            callDirection = DIRECTION_INCOMING;
        } else if (call.isUnknown()) {
            callDirection = DIRECTION_UNKNOWN;
        } else {
            callDirection = DIRECTION_OUTGOING;
        }

        return new ParcelableCall(
                call.getId(),
@@ -195,7 +207,8 @@ public class ParcelableCallUtils {
                call.getIntentExtras(),
                call.getExtras(),
                call.getCreationTimeMillis(),
                call.getCallIdentification());
                call.getCallIdentification(),
                callDirection);
    }

    /**
@@ -203,7 +216,7 @@ public class ParcelableCallUtils {
     * {@link android.telecom.CallScreeningService}.  We ONLY expose the following:
     * <ul>
     *     <li>Call Id (not exposed to public, but needed to associated calls)</li>
     *     <li>Call state</li>
     *     <li>Call directoin</li>
     *     <li>Creation time</li>
     *     <li>Connection time</li>
     *     <li>Handle (phone number)</li>
@@ -216,6 +229,14 @@ public class ParcelableCallUtils {
    public static ParcelableCall toParcelableCallForScreening(Call call) {
        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
                call.getHandle() : null;
        int callDirection;
        if (call.isIncoming()) {
            callDirection = DIRECTION_INCOMING;
        } else if (call.isUnknown()) {
            callDirection = DIRECTION_UNKNOWN;
        } else {
            callDirection = DIRECTION_OUTGOING;
        }
        return new ParcelableCall(
                call.getId(),
                getParcelableState(call, false /* supportsExternalCalls */),
@@ -243,7 +264,8 @@ public class ParcelableCallUtils {
                null, /* intentExtras */
                null, /* callExtras */
                call.getCreationTimeMillis(),
                null /* callIdentification */);
                null /* callIdentification */,
                callDirection);
    }

    private static int getParcelableState(Call call, boolean supportsExternalCalls) {
+4 −5
Original line number Diff line number Diff line
@@ -26,11 +26,10 @@ import java.util.List;
import java.util.stream.Collectors;

public class RoleManagerAdapterImpl implements RoleManagerAdapter {
    private static final String ROLE_CALL_REDIRECTION_APP = "android.app.role.PROXY_CALLING_APP";
    private static final String ROLE_CAR_MODE_DIALER = "android.app.role.CAR_MODE_DIALER_APP";
    private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING_APP";
    private static final String ROLE_CALL_COMPANION_APP =
            "android.app.role.CALL_COMPANION_APP";
    private static final String ROLE_CALL_REDIRECTION_APP = RoleManager.ROLE_PROXY_CALLING_APP;
    private static final String ROLE_CAR_MODE_DIALER = RoleManager.ROLE_CAR_MODE_DIALER_APP;
    private static final String ROLE_CALL_SCREENING = RoleManager.ROLE_CALL_SCREENING_APP;
    private static final String ROLE_CALL_COMPANION_APP = RoleManager.ROLE_CALL_COMPANION_APP;

    private String mOverrideDefaultCallRedirectionApp = null;
    private String mOverrideDefaultCallScreeningApp = null;
Loading