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

Commit 9c05eef2 authored by Hall Liu's avatar Hall Liu
Browse files

Add logic to call an account suggestion service

Add PhoneAccountSuggestionHelper that binds to and queries a
PhoneAccountSuggestionService for a list of suggestions.

Test: dead code, will CTS later.
Bug: 111455117
Change-Id: If34474477f797f1c09ba8d1f0b967f55b56c609d
parent 5a2f6feb
Loading
Loading
Loading
Loading
+9 −8
Original line number Diff line number Diff line
@@ -148,6 +148,7 @@ public class CallsManager extends Call.ListenerBase
        void performAction();
    }

    /** An executor that starts a log session before executing a runnable */
    private class LoggedHandlerExecutor implements Executor {
        private Handler mHandler;
        private String mSessionName;
@@ -1336,15 +1337,15 @@ public class CallsManager extends Call.ListenerBase
                        return CompletableFuture.completedFuture(
                                Collections.singletonList(suggestion));
                    }
                    // todo: call onsuggestphoneaccount and bring back the list of suggestions
                    // from there. For now just map all the accounts to suggest_none
                    List<PhoneAccountSuggestion> suggestions =
                    return CompletableFuture.completedFuture(
                            potentialPhoneAccounts.stream().map(phoneAccountHandle ->
                            new PhoneAccountSuggestion(phoneAccountHandle,
                                    PhoneAccountSuggestion.REASON_NONE, false)
                            ).collect(Collectors.toList());

                    return CompletableFuture.completedFuture(suggestions);
                    ).collect(Collectors.toList()));
                    /* TODO -- turn this on after tests/debugging are done
                    return PhoneAccountSuggestionHelper.bindAndGetSuggestions(mContext,
                            finalCall.getHandle(), potentialPhoneAccounts);
                            */
                }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.cOCSS"));


+209 −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.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.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.telecom.Log;
import android.telecom.Logging.Session;
import android.telecom.PhoneAccountHandle;
import android.telecom.PhoneAccountSuggestion;
import android.telecom.PhoneAccountSuggestionService;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;

import com.android.internal.telecom.IPhoneAccountSuggestionCallback;
import com.android.internal.telecom.IPhoneAccountSuggestionService;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class PhoneAccountSuggestionHelper {
    private static final String TAG = PhoneAccountSuggestionHelper.class.getSimpleName();
    private static ComponentName sOverrideComponent;

    /**
     * @return A future (possible already complete) that contains a list of suggestions.
     */
    public static CompletableFuture<List<PhoneAccountSuggestion>>
    bindAndGetSuggestions(Context context, Uri handle,
            List<PhoneAccountHandle> availablePhoneAccounts) {
        // Use the default list if there's no handle
        if (handle == null) {
            return CompletableFuture.completedFuture(getDefaultSuggestions(availablePhoneAccounts));
        }
        String number = PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart());

        // Use the default list if there's no service on the device.
        ServiceInfo suggestionServiceInfo = getSuggestionServiceInfo(context);
        if (suggestionServiceInfo == null) {
            return CompletableFuture.completedFuture(getDefaultSuggestions(availablePhoneAccounts));
        }

        Intent bindIntent = new Intent();
        bindIntent.setComponent(new ComponentName(suggestionServiceInfo.packageName,
                suggestionServiceInfo.name));

        final CompletableFuture<List<PhoneAccountSuggestion>> future = new CompletableFuture<>();

        final Session logSession = Log.createSubsession();
        ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder _service) {
                Log.continueSession(logSession, "PASH.oSC");
                try {
                    IPhoneAccountSuggestionService service =
                            IPhoneAccountSuggestionService.Stub.asInterface(_service);
                    // Set up the callback to complete the future once the remote side comes
                    // back with suggestions
                    IPhoneAccountSuggestionCallback callback =
                            new IPhoneAccountSuggestionCallback.Stub() {
                                @Override
                                public void suggestPhoneAccounts(String suggestResultNumber,
                                        List<PhoneAccountSuggestion> suggestions) {
                                    if (TextUtils.equals(number, suggestResultNumber)) {
                                        if (suggestions == null) {
                                            future.complete(
                                                    getDefaultSuggestions(availablePhoneAccounts));
                                        } else {
                                            future.complete(
                                                    addDefaultsToProvidedSuggestions(
                                                            suggestions, availablePhoneAccounts));
                                        }
                                    }
                                }
                            };
                    try {
                        service.onAccountSuggestionRequest(callback, number);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Cancelling suggestion process due to remote exception");
                        future.complete(getDefaultSuggestions(availablePhoneAccounts));
                    }
                } finally {
                    Log.endSession();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                // No locking needed -- CompletableFuture only lets one thread call complete.
                Log.continueSession(logSession, "PASH.oSD");
                try {
                    if (!future.isDone()) {
                        Log.w(TAG, "Cancelling suggestion process due to service disconnect");
                    }
                    future.complete(getDefaultSuggestions(availablePhoneAccounts));
                } finally {
                    Log.endSession();
                }
            }
        };

        if (!context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE)) {
            Log.i(TAG, "Cancelling suggestion process due to bind failure.");
            future.complete(getDefaultSuggestions(availablePhoneAccounts));
        }

        // Set up a timeout so that we're not waiting forever for the suggestion service.
        Handler handler = new Handler();
        handler.postDelayed(() -> {
                    // No locking needed -- CompletableFuture only lets one thread call complete.
                    Log.continueSession(logSession, "PASH.timeout");
                    try {
                        if (!future.isDone()) {
                            Log.w(TAG, "Cancelling suggestion process due to timeout");
                        }
                        future.complete(getDefaultSuggestions(availablePhoneAccounts));
                    } finally {
                        Log.endSession();
                    }
                },
                Timeouts.getPhoneAccountSuggestionServiceTimeout(context.getContentResolver()));
        return future;
    }

    private static List<PhoneAccountSuggestion> addDefaultsToProvidedSuggestions(
            List<PhoneAccountSuggestion> providedSuggestions,
            List<PhoneAccountHandle> availableAccountHandles) {
        List<PhoneAccountHandle> handlesInSuggestions = providedSuggestions.stream()
                .map(PhoneAccountSuggestion::getPhoneAccountHandle)
                .collect(Collectors.toList());
        List<PhoneAccountHandle> handlesToFillIn = availableAccountHandles.stream()
                .filter(handle -> !handlesInSuggestions.contains(handle))
                .collect(Collectors.toList());
        List<PhoneAccountSuggestion> suggestionsToAppend = getDefaultSuggestions(handlesToFillIn);
        return Stream.concat(suggestionsToAppend.stream(), providedSuggestions.stream())
                .collect( Collectors.toList());
    }

    private static ServiceInfo getSuggestionServiceInfo(Context context) {
        PackageManager packageManager = context.getPackageManager();
        Intent queryIntent = new Intent();
        queryIntent.setAction(PhoneAccountSuggestionService.SERVICE_INTERFACE);

        List<ResolveInfo> services;
        if (sOverrideComponent == null) {
            services = packageManager.queryIntentServices(queryIntent,
                    PackageManager.MATCH_SYSTEM_ONLY);
        } else {
            Log.i(TAG, "Using override component %s", sOverrideComponent);
            queryIntent.setComponent(sOverrideComponent);
            services = packageManager.queryIntentServices(queryIntent,
                    PackageManager.MATCH_ALL);
        }

        if (services == null || services.size() == 0) {
            Log.i(TAG, "No acct suggestion services found. Using defaults.");
            return null;
        }

        if (services.size() > 1) {
            Log.w(TAG, "More than acct suggestion service found, cannot get unique service");
            return null;
        }
        return services.get(0).serviceInfo;
    }

    static void setOverrideServiceName(String flattenedComponentName) {
        try {
            sOverrideComponent = TextUtils.isEmpty(flattenedComponentName)
                    ? null : ComponentName.unflattenFromString(flattenedComponentName);
        } catch (Exception e) {
            sOverrideComponent = null;
            throw e;
        }
    }

    private static List<PhoneAccountSuggestion> getDefaultSuggestions(
            List<PhoneAccountHandle> phoneAccountHandles) {
        return phoneAccountHandles.stream().map(phoneAccountHandle ->
                new PhoneAccountSuggestion(phoneAccountHandle,
                        PhoneAccountSuggestion.REASON_NONE, false)
        ).collect(Collectors.toList());
    }
}
 No newline at end of file
+17 −0
Original line number Diff line number Diff line
@@ -1754,6 +1754,23 @@ public class TelecomServiceImpl {
                Log.endSession();
            }
        }

        @Override
        public void setTestPhoneAcctSuggestionComponent(String flattenedComponentName) {
            try {
                Log.startSession("TSI.sPASA");
                enforceModifyPermission();
                if (Binder.getCallingUid() != Process.SHELL_UID
                        && Binder.getCallingUid() != Process.ROOT_UID) {
                    throw new SecurityException("Shell-only API.");
                }
                synchronized (mLock) {
                    PhoneAccountSuggestionHelper.setOverrideServiceName(flattenedComponentName);
                }
            } finally {
                Log.endSession();
            }
        }
    };

    /**
+12 −0
Original line number Diff line number Diff line
@@ -58,6 +58,10 @@ public final class Timeouts {
        public long getCarrierCallRedirectionTimeoutMillis(ContentResolver cr) {
            return Timeouts.getCarrierCallRedirectionTimeoutMillis(cr);
        }

        public long getPhoneAccountSuggestionServiceTimeout(ContentResolver cr) {
            return Timeouts.getPhoneAccountSuggestionServiceTimeout(cr);
        }
    }

    /** A prefix to use for all keys so to not clobber the global namespace. */
@@ -151,6 +155,14 @@ public final class Timeouts {
        return get(contentResolver, "retry_bluetooth_connect_audio_backoff_millis", 500L);
    }

    /**
     * Returns the amount of time to wait for the phone account suggestion service to reply.
     */
    public static long getPhoneAccountSuggestionServiceTimeout(ContentResolver contentResolver) {
        return get(contentResolver, "phone_account_suggestion_service_timeout",
                5000L /* 5 seconds */);
    }

    /**
     * Returns the amount of time to wait for the call screening service to allow or disallow a
     * call.