• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.phone.vvm;
18 
19 import android.annotation.Nullable;
20 import android.app.Service;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.content.pm.ComponentInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Message;
32 import android.os.Messenger;
33 import android.os.PersistableBundle;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.telecom.PhoneAccountHandle;
37 import android.telecom.TelecomManager;
38 import android.telephony.CarrierConfigManager;
39 import android.telephony.VisualVoicemailService;
40 import android.telephony.VisualVoicemailSms;
41 import android.text.TextUtils;
42 
43 import com.android.internal.telephony.util.TelephonyUtils;
44 import com.android.phone.Assert;
45 import com.android.phone.R;
46 
47 import java.util.ArrayList;
48 import java.util.LinkedList;
49 import java.util.List;
50 import java.util.Queue;
51 
52 /**
53  * Service to manage tasks issued to the {@link VisualVoicemailService}. This service will bind to
54  * the default dialer on a visual voicemail event if it implements the VisualVoicemailService. The
55  * service will hold all resource for the VisualVoicemailService until {@link
56  * VisualVoicemailService.VisualVoicemailTask#finish()} has been called on all issued tasks.
57  *
58  * If the service is already running it will be reused for new events. The service will stop itself
59  * after all events are handled.
60  */
61 public class RemoteVvmTaskManager extends Service {
62 
63     private static final String TAG = "RemoteVvmTaskManager";
64 
65     private static final String ACTION_START_CELL_SERVICE_CONNECTED =
66             "ACTION_START_CELL_SERVICE_CONNECTED";
67     private static final String ACTION_START_SMS_RECEIVED = "ACTION_START_SMS_RECEIVED";
68     private static final String ACTION_START_SIM_REMOVED = "ACTION_START_SIM_REMOVED";
69 
70     // TODO(b/35766990): Remove after VisualVoicemailService API is stabilized.
71     private static final String ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT =
72             "com.android.phone.vvm.ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT";
73     private static final String EXTRA_WHAT = "what";
74 
75     private static final String EXTRA_TARGET_PACKAGE = "target_package";
76 
77     // TODO(twyen): track task individually to have time outs.
78     private int mTaskReferenceCount;
79 
80     private RemoteServiceConnection mConnection;
81 
82     /**
83      * Handles incoming messages from the VisualVoicemailService.
84      */
85     private Messenger mMessenger;
86 
startCellServiceConnected(Context context, PhoneAccountHandle phoneAccountHandle)87     static void startCellServiceConnected(Context context,
88             PhoneAccountHandle phoneAccountHandle) {
89         Intent intent = new Intent(ACTION_START_CELL_SERVICE_CONNECTED, null, context,
90                 RemoteVvmTaskManager.class);
91         intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
92         context.startService(intent);
93     }
94 
startSmsReceived(Context context, VisualVoicemailSms sms, String targetPackage)95     static void startSmsReceived(Context context, VisualVoicemailSms sms,
96             String targetPackage) {
97         Intent intent = new Intent(ACTION_START_SMS_RECEIVED, null, context,
98                 RemoteVvmTaskManager.class);
99         intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE,
100                 sms.getPhoneAccountHandle());
101         intent.putExtra(VisualVoicemailService.DATA_SMS, sms);
102         intent.putExtra(EXTRA_TARGET_PACKAGE, targetPackage);
103         context.startService(intent);
104     }
105 
startSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle)106     static void startSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle) {
107         Intent intent = new Intent(ACTION_START_SIM_REMOVED, null, context,
108                 RemoteVvmTaskManager.class);
109         intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
110         context.startService(intent);
111     }
112 
hasRemoteService(Context context, int subId, String targetPackage)113     static boolean hasRemoteService(Context context, int subId, String targetPackage) {
114         return getRemotePackage(context, subId, targetPackage) != null;
115     }
116 
117     /**
118      * Return the {@link ComponentName} of the {@link VisualVoicemailService} which is active (the
119      * current default dialer), or {@code null} if no implementation is found.
120      */
121     @Nullable
getRemotePackage(Context context, int subId)122     public static ComponentName getRemotePackage(Context context, int subId) {
123         return getRemotePackage(context, subId, null);
124     }
125 
126     /**
127      * Return the {@link ComponentName} of the {@link VisualVoicemailService} which is active (the
128      * current default dialer), or {@code null} if no implementation is found.
129      *
130      * @param targetPackage the package that should be the active VisualVociemailService
131      */
132     @Nullable
getRemotePackage(Context context, int subId, @Nullable String targetPackage)133     public static ComponentName getRemotePackage(Context context, int subId,
134             @Nullable String targetPackage) {
135         ComponentName broadcastPackage = getBroadcastPackage(context);
136         if (broadcastPackage != null) {
137             return broadcastPackage;
138         }
139 
140         Intent bindIntent = newBindIntent(context);
141 
142         TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
143         List<String> packages = new ArrayList<>();
144         packages.add(telecomManager.getDefaultDialerPackage());
145         // TODO(b/73136824): Check permissions in the calling function and avoid relying on the
146         // binder caller's permissions to access the carrier config.
147         PersistableBundle carrierConfig = context
148                 .getSystemService(CarrierConfigManager.class).getConfigForSubId(subId);
149         packages.add(
150                 carrierConfig
151                         .getString(CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING));
152         String[] vvmPackages = carrierConfig
153                 .getStringArray(CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY);
154         if (vvmPackages != null && vvmPackages.length > 0) {
155             for (String packageName : vvmPackages) {
156                 packages.add(packageName);
157             }
158         }
159         packages.add(context.getResources().getString(R.string.system_visual_voicemail_client));
160 
161         for (String packageName : packages) {
162             if (TextUtils.isEmpty(packageName)) {
163                 continue;
164             }
165             bindIntent.setPackage(packageName);
166             ResolveInfo info = context.getPackageManager().resolveService(bindIntent, 0);
167             if (info == null) {
168                 continue;
169             }
170             if (info.serviceInfo == null) {
171                 VvmLog.w(TAG,
172                         "getRemotePackage: Component " + TelephonyUtils.getComponentInfo(info)
173                             + " is not a service, ignoring");
174                 continue;
175             }
176             if (!android.Manifest.permission.BIND_VISUAL_VOICEMAIL_SERVICE
177                     .equals(info.serviceInfo.permission)) {
178                 VvmLog.w(TAG, "getRemotePackage: package " + info.serviceInfo.packageName
179                         + " does not enforce BIND_VISUAL_VOICEMAIL_SERVICE, ignoring");
180                 continue;
181             }
182             if (targetPackage != null && !TextUtils.equals(packageName, targetPackage)) {
183                 VvmLog.w(TAG, "getRemotePackage: target package " + targetPackage
184                         + " is no longer the active VisualVoicemailService, ignoring");
185                 continue;
186             }
187             ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(info);
188             VvmLog.i(TAG, "getRemotePackage: found package " + targetPackage);
189             return new ComponentName(componentInfo.packageName, componentInfo.name);
190 
191         }
192         return null;
193     }
194 
195     @Nullable
getBroadcastPackage(Context context)196     private static ComponentName getBroadcastPackage(Context context) {
197         Intent broadcastIntent = new Intent(ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT);
198         broadcastIntent.setPackage(
199                 context.getSystemService(TelecomManager.class).getDefaultDialerPackage());
200         List<ResolveInfo> info = context.getPackageManager()
201                 .queryBroadcastReceivers(broadcastIntent, PackageManager.MATCH_ALL);
202         if (info == null) {
203             return null;
204         }
205         if (info.isEmpty()) {
206             return null;
207         }
208         ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(info.get(0));
209         VvmLog.i(TAG, "getBroadcastPackage: found package " + componentInfo.packageName);
210         return new ComponentName(componentInfo.packageName, componentInfo.name);
211     }
212 
213     @Override
onCreate()214     public void onCreate() {
215         Assert.isMainThread();
216         mMessenger = new Messenger(new Handler() {
217             @Override
218             public void handleMessage(Message msg) {
219                 Assert.isMainThread();
220                 switch (msg.what) {
221                     case VisualVoicemailService.MSG_TASK_ENDED:
222                         mTaskReferenceCount--;
223                         checkReference();
224                         break;
225                     default:
226                         VvmLog.wtf(TAG, "unexpected message " + msg.what);
227                 }
228             }
229         });
230     }
231 
232     @Override
onStartCommand(@ullable Intent intent, int flags, int startId)233     public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
234         Assert.isMainThread();
235         mTaskReferenceCount++;
236 
237         if (intent == null) {
238             VvmLog.i(TAG, "onStartCommand: received intent is null");
239             checkReference();
240             return START_NOT_STICKY;
241         }
242         PhoneAccountHandle phoneAccountHandle = intent.getExtras()
243                 .getParcelable(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE);
244         int subId = PhoneAccountHandleConverter.toSubId(phoneAccountHandle);
245         UserHandle userHandle = phoneAccountHandle.getUserHandle();
246         ComponentName remotePackage = getRemotePackage(this, subId,
247                 intent.getStringExtra(EXTRA_TARGET_PACKAGE));
248         if (remotePackage == null) {
249             VvmLog.i(TAG, "onStartCommand: No service to handle "
250                     + intent.getAction() + ", ignoring");
251             checkReference();
252             return START_NOT_STICKY;
253         }
254 
255         switch (intent.getAction()) {
256             case ACTION_START_CELL_SERVICE_CONNECTED:
257                 send(remotePackage, VisualVoicemailService.MSG_ON_CELL_SERVICE_CONNECTED,
258                         intent.getExtras(), userHandle);
259                 break;
260             case ACTION_START_SMS_RECEIVED:
261                 send(remotePackage, VisualVoicemailService.MSG_ON_SMS_RECEIVED, intent.getExtras(),
262                         userHandle);
263                 break;
264             case ACTION_START_SIM_REMOVED:
265                 send(remotePackage, VisualVoicemailService.MSG_ON_SIM_REMOVED, intent.getExtras(),
266                         userHandle);
267                 break;
268             default:
269                 Assert.fail("Unexpected action +" + intent.getAction());
270                 break;
271         }
272         // Don't rerun service if processed is killed.
273         return START_NOT_STICKY;
274     }
275 
276     @Override
277     @Nullable
onBind(Intent intent)278     public IBinder onBind(Intent intent) {
279         return null;
280     }
281 
getTaskId()282     private int getTaskId() {
283         // TODO(twyen): generate unique IDs. Reference counting is used now so it doesn't matter.
284         return 1;
285     }
286 
287     /**
288      * Class for interacting with the main interface of the service.
289      */
290     private class RemoteServiceConnection implements ServiceConnection {
291 
292         private final Queue<Message> mTaskQueue = new LinkedList<>();
293 
294         private boolean mConnected;
295 
296         /**
297          * A handler in the VisualVoicemailService
298          */
299         private Messenger mRemoteMessenger;
300 
enqueue(Message message)301         public void enqueue(Message message) {
302             mTaskQueue.add(message);
303             if (mConnected) {
304                 runQueue();
305             }
306         }
307 
isConnected()308         public boolean isConnected() {
309             return mConnected;
310         }
311 
onServiceConnected(ComponentName className, IBinder service)312         public void onServiceConnected(ComponentName className,
313                 IBinder service) {
314             VvmLog.i(TAG, "onServiceConnected: " + className);
315             mRemoteMessenger = new Messenger(service);
316             mConnected = true;
317             runQueue();
318         }
319 
onServiceDisconnected(ComponentName className)320         public void onServiceDisconnected(ComponentName className) {
321             mConnection = null;
322             mConnected = false;
323             mRemoteMessenger = null;
324             VvmLog.e(TAG, "onServiceDisconnected: remoteService disconnected, "
325                     + mTaskReferenceCount + " tasks dropped.");
326             mTaskReferenceCount = 0;
327             checkReference();
328         }
329 
runQueue()330         private void runQueue() {
331             Assert.isMainThread();
332             Message message = mTaskQueue.poll();
333             while (message != null) {
334                 message.replyTo = mMessenger;
335                 message.arg1 = getTaskId();
336 
337                 try {
338                     mRemoteMessenger.send(message);
339                 } catch (RemoteException e) {
340                     VvmLog.e(TAG, "runQueue: Error sending message to remote service", e);
341                 }
342                 message = mTaskQueue.poll();
343             }
344         }
345     }
346 
send(ComponentName remotePackage, int what, Bundle extras, UserHandle userHandle)347     private void send(ComponentName remotePackage, int what, Bundle extras, UserHandle userHandle) {
348         Assert.isMainThread();
349 
350         if (getBroadcastPackage(this) != null) {
351             /*
352              * Temporarily use a broadcast to notify dialer VVM events instead of using the
353              * VisualVoicemailService.
354              * b/35766990 The VisualVoicemailService is undergoing API changes. The dialer is in
355              * a different repository so it can not be updated in sync with android SDK. It is also
356              * hard to make a manifest service to work in the intermittent state.
357              */
358             VvmLog.i(TAG, "send: sending broadcast " + what + " to " + remotePackage);
359             Intent intent = new Intent(ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT);
360             intent.putExtras(extras);
361             intent.putExtra(EXTRA_WHAT, what);
362             intent.setComponent(remotePackage);
363             sendBroadcastAsUser(intent, userHandle);
364             return;
365         }
366 
367         Message message = Message.obtain();
368         message.what = what;
369         message.setData(new Bundle(extras));
370         if (mConnection == null) {
371             mConnection = new RemoteServiceConnection();
372         }
373         mConnection.enqueue(message);
374 
375         if (!mConnection.isConnected()) {
376             Intent intent = newBindIntent(this);
377             intent.setComponent(remotePackage);
378             VvmLog.i(TAG, "send: Binding to " + intent.getComponent());
379             bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, userHandle);
380         }
381     }
382 
checkReference()383     private void checkReference() {
384         if (mConnection == null) {
385             return;
386         }
387         if (mTaskReferenceCount == 0) {
388             unbindService(mConnection);
389             mConnection = null;
390         }
391     }
392 
newBindIntent(Context context)393     private static Intent newBindIntent(Context context) {
394         Intent intent = new Intent();
395         intent.setAction(VisualVoicemailService.SERVICE_INTERFACE);
396         return intent;
397     }
398 }
399