• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.pm.ServiceInfo;
26 import android.net.Uri;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.telecom.Log;
32 import android.telecom.Logging.Session;
33 import android.telecom.PhoneAccountHandle;
34 import android.telecom.PhoneAccountSuggestion;
35 import android.telecom.PhoneAccountSuggestionService;
36 import android.telephony.PhoneNumberUtils;
37 import android.text.TextUtils;
38 
39 import com.android.internal.telecom.IPhoneAccountSuggestionCallback;
40 import com.android.internal.telecom.IPhoneAccountSuggestionService;
41 
42 import java.util.List;
43 import java.util.concurrent.CompletableFuture;
44 import java.util.stream.Collectors;
45 import java.util.stream.Stream;
46 
47 public class PhoneAccountSuggestionHelper {
48     private static final String TAG = PhoneAccountSuggestionHelper.class.getSimpleName();
49     private static ComponentName sOverrideComponent;
50     private static UserHandle sOverrideUserHandle;
51 
52     /**
53      * @return A future (possible already complete) that contains a list of suggestions.
54      */
55     public static CompletableFuture<List<PhoneAccountSuggestion>>
bindAndGetSuggestions(Context context, Uri handle, List<PhoneAccountHandle> availablePhoneAccounts)56     bindAndGetSuggestions(Context context, Uri handle,
57             List<PhoneAccountHandle> availablePhoneAccounts) {
58         Context userContext;
59         if (sOverrideUserHandle != null) {
60             userContext = context.createContextAsUser(sOverrideUserHandle, 0);
61             Log.i(TAG, "bindAndGetSuggestions created context as user;  userContext=%s",
62                     userContext);
63         } else {
64             userContext = context;
65         }
66 
67         // Use the default list if there's no handle
68         if (handle == null) {
69             return CompletableFuture.completedFuture(getDefaultSuggestions(availablePhoneAccounts));
70         }
71         String number = PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart());
72 
73         // Use the default list if there's no service on the device.
74         ServiceInfo suggestionServiceInfo = getSuggestionServiceInfo(userContext);
75         if (suggestionServiceInfo == null) {
76             return CompletableFuture.completedFuture(getDefaultSuggestions(availablePhoneAccounts));
77         }
78 
79         Intent bindIntent = new Intent();
80         bindIntent.setComponent(new ComponentName(suggestionServiceInfo.packageName,
81                 suggestionServiceInfo.name));
82 
83         final CompletableFuture<List<PhoneAccountSuggestion>> future = new CompletableFuture<>();
84 
85         final Session logSession = Log.createSubsession();
86         ServiceConnection serviceConnection = new ServiceConnection() {
87             @Override
88             public void onServiceConnected(ComponentName name, IBinder _service) {
89                 Log.continueSession(logSession, "PASH.oSC");
90                 try {
91                     IPhoneAccountSuggestionService service =
92                             IPhoneAccountSuggestionService.Stub.asInterface(_service);
93                     // Set up the callback to complete the future once the remote side comes
94                     // back with suggestions
95                     IPhoneAccountSuggestionCallback callback =
96                             new IPhoneAccountSuggestionCallback.Stub() {
97                                 @Override
98                                 public void suggestPhoneAccounts(String suggestResultNumber,
99                                         List<PhoneAccountSuggestion> suggestions) {
100                                     if (TextUtils.equals(number, suggestResultNumber)) {
101                                         if (suggestions == null) {
102                                             future.complete(
103                                                     getDefaultSuggestions(availablePhoneAccounts));
104                                         } else {
105                                             future.complete(
106                                                     addDefaultsToProvidedSuggestions(
107                                                             suggestions, availablePhoneAccounts));
108                                         }
109                                     }
110                                 }
111                             };
112                     try {
113                         service.onAccountSuggestionRequest(callback, number);
114                     } catch (RemoteException e) {
115                         Log.w(TAG, "Cancelling suggestion process due to remote exception");
116                         future.complete(getDefaultSuggestions(availablePhoneAccounts));
117                     }
118                 } finally {
119                     Log.endSession();
120                 }
121             }
122 
123             @Override
124             public void onServiceDisconnected(ComponentName name) {
125                 // No locking needed -- CompletableFuture only lets one thread call complete.
126                 Log.continueSession(logSession, "PASH.oSD");
127                 try {
128                     if (!future.isDone()) {
129                         Log.w(TAG, "Cancelling suggestion process due to service disconnect");
130                     }
131                     future.complete(getDefaultSuggestions(availablePhoneAccounts));
132                 } finally {
133                     Log.endSession();
134                 }
135             }
136         };
137 
138         if (!userContext.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE)) {
139             Log.i(TAG, "Cancelling suggestion process due to bind failure.");
140             future.complete(getDefaultSuggestions(availablePhoneAccounts));
141         }
142 
143         // Set up a timeout so that we're not waiting forever for the suggestion service.
144         Handler handler = new Handler();
145         handler.postDelayed(() -> {
146                     // No locking needed -- CompletableFuture only lets one thread call complete.
147                     Log.continueSession(logSession, "PASH.timeout");
148                     try {
149                         if (!future.isDone()) {
150                             Log.w(TAG, "Cancelling suggestion process due to timeout");
151                         }
152                         future.complete(getDefaultSuggestions(availablePhoneAccounts));
153                     } finally {
154                         Log.endSession();
155                     }
156                 },
157                 Timeouts.getPhoneAccountSuggestionServiceTimeout(userContext.getContentResolver()));
158         return future;
159     }
160 
addDefaultsToProvidedSuggestions( List<PhoneAccountSuggestion> providedSuggestions, List<PhoneAccountHandle> availableAccountHandles)161     private static List<PhoneAccountSuggestion> addDefaultsToProvidedSuggestions(
162             List<PhoneAccountSuggestion> providedSuggestions,
163             List<PhoneAccountHandle> availableAccountHandles) {
164         List<PhoneAccountHandle> handlesInSuggestions = providedSuggestions.stream()
165                 .map(PhoneAccountSuggestion::getPhoneAccountHandle)
166                 .collect(Collectors.toList());
167         List<PhoneAccountHandle> handlesToFillIn = availableAccountHandles.stream()
168                 .filter(handle -> !handlesInSuggestions.contains(handle))
169                 .collect(Collectors.toList());
170         List<PhoneAccountSuggestion> suggestionsToAppend = getDefaultSuggestions(handlesToFillIn);
171         return Stream.concat(suggestionsToAppend.stream(), providedSuggestions.stream())
172                 .collect( Collectors.toList());
173     }
174 
getSuggestionServiceInfo(Context context)175     private static ServiceInfo getSuggestionServiceInfo(Context context) {
176         Context userContext;
177         if (sOverrideUserHandle != null) {
178             userContext = context.createContextAsUser(sOverrideUserHandle, 0);
179             Log.i(TAG, "getSuggestionServiceInfo: Created context as user; userContext= %s",
180                     userContext);
181         } else {
182             userContext = context;
183         }
184 
185         PackageManager packageManager = userContext.getPackageManager();
186 
187         Intent queryIntent = new Intent();
188         queryIntent.setAction(PhoneAccountSuggestionService.SERVICE_INTERFACE);
189 
190         if (packageManager == null) {
191             Log.i(TAG, "getSuggestionServiceInfo: PackageManager is null. Using defaults.");
192             return null;
193         }
194 
195         List<ResolveInfo> services;
196         if (sOverrideComponent == null) {
197             services = packageManager.queryIntentServices(queryIntent,
198                     PackageManager.MATCH_SYSTEM_ONLY);
199         } else {
200             Log.i(TAG, "Using override component %s", sOverrideComponent);
201             queryIntent.setComponent(sOverrideComponent);
202             services = packageManager.queryIntentServices(queryIntent,
203                     PackageManager.MATCH_ALL);
204         }
205 
206         if (services == null || services.size() == 0) {
207             Log.i(TAG, "No acct suggestion services found. Using defaults.");
208             return null;
209         }
210 
211         if (services.size() > 1) {
212             Log.w(TAG, "More than acct suggestion service found, cannot get unique service");
213             return null;
214         }
215         return services.get(0).serviceInfo;
216     }
217 
setOverrideServiceName(String flattenedComponentName)218     static void setOverrideServiceName(String flattenedComponentName) {
219         try {
220             sOverrideComponent = TextUtils.isEmpty(flattenedComponentName)
221                     ? null : ComponentName.unflattenFromString(flattenedComponentName);
222         } catch (Exception e) {
223             sOverrideComponent = null;
224             throw e;
225         }
226     }
227 
setOverrideUserHandle(UserHandle userHandle)228     static void setOverrideUserHandle(UserHandle userHandle) {
229         try {
230             sOverrideUserHandle = userHandle;
231         } catch (Exception e) {
232             sOverrideUserHandle = null;
233             throw e;
234         }
235     }
236 
getDefaultSuggestions( List<PhoneAccountHandle> phoneAccountHandles)237     private static List<PhoneAccountSuggestion> getDefaultSuggestions(
238             List<PhoneAccountHandle> phoneAccountHandles) {
239         return phoneAccountHandles.stream().map(phoneAccountHandle ->
240                 new PhoneAccountSuggestion(phoneAccountHandle,
241                         PhoneAccountSuggestion.REASON_NONE, false)
242         ).collect(Collectors.toList());
243     }
244 }