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         packages.add(telecomManager.getSystemDialerPackage());
161 
162         for (String packageName : packages) {
163             if (TextUtils.isEmpty(packageName)) {
164                 continue;
165             }
166             bindIntent.setPackage(packageName);
167             ResolveInfo info = context.getPackageManager().resolveService(bindIntent, 0);
168             if (info == null) {
169                 continue;
170             }
171             if (info.serviceInfo == null) {
172                 VvmLog.w(TAG,
173                         "Component " + TelephonyUtils.getComponentInfo(info)
174                             + " is not a service, ignoring");
175                 continue;
176             }
177             if (!android.Manifest.permission.BIND_VISUAL_VOICEMAIL_SERVICE
178                     .equals(info.serviceInfo.permission)) {
179                 VvmLog.w(TAG, "package " + info.serviceInfo.packageName
180                         + " does not enforce BIND_VISUAL_VOICEMAIL_SERVICE, ignoring");
181                 continue;
182             }
183             if (targetPackage != null && !TextUtils.equals(packageName, targetPackage)) {
184                 VvmLog.w(TAG, "target package " + targetPackage
185                         + " is no longer the active VisualVoicemailService, ignoring");
186                 continue;
187             }
188             ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(info);
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         return new ComponentName(componentInfo.packageName, componentInfo.name);
210     }
211 
212     @Override
onCreate()213     public void onCreate() {
214         Assert.isMainThread();
215         mMessenger = new Messenger(new Handler() {
216             @Override
217             public void handleMessage(Message msg) {
218                 Assert.isMainThread();
219                 switch (msg.what) {
220                     case VisualVoicemailService.MSG_TASK_ENDED:
221                         mTaskReferenceCount--;
222                         checkReference();
223                         break;
224                     default:
225                         VvmLog.wtf(TAG, "unexpected message " + msg.what);
226                 }
227             }
228         });
229     }
230 
231     @Override
onStartCommand(@ullable Intent intent, int flags, int startId)232     public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
233         Assert.isMainThread();
234         mTaskReferenceCount++;
235 
236         if (intent == null) {
237             VvmLog.i(TAG, "received intent is null");
238             checkReference();
239             return START_NOT_STICKY;
240         }
241         PhoneAccountHandle phoneAccountHandle = intent.getExtras()
242                 .getParcelable(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE);
243         int subId = PhoneAccountHandleConverter.toSubId(phoneAccountHandle);
244         UserHandle userHandle = phoneAccountHandle.getUserHandle();
245         ComponentName remotePackage = getRemotePackage(this, subId,
246                 intent.getStringExtra(EXTRA_TARGET_PACKAGE));
247         if (remotePackage == null) {
248             VvmLog.i(TAG, "No service to handle " + intent.getAction() + ", ignoring");
249             checkReference();
250             return START_NOT_STICKY;
251         }
252 
253         switch (intent.getAction()) {
254             case ACTION_START_CELL_SERVICE_CONNECTED:
255                 send(remotePackage, VisualVoicemailService.MSG_ON_CELL_SERVICE_CONNECTED,
256                         intent.getExtras(), userHandle);
257                 break;
258             case ACTION_START_SMS_RECEIVED:
259                 send(remotePackage, VisualVoicemailService.MSG_ON_SMS_RECEIVED, intent.getExtras(),
260                         userHandle);
261                 break;
262             case ACTION_START_SIM_REMOVED:
263                 send(remotePackage, VisualVoicemailService.MSG_ON_SIM_REMOVED, intent.getExtras(),
264                         userHandle);
265                 break;
266             default:
267                 Assert.fail("Unexpected action +" + intent.getAction());
268                 break;
269         }
270         // Don't rerun service if processed is killed.
271         return START_NOT_STICKY;
272     }
273 
274     @Override
275     @Nullable
onBind(Intent intent)276     public IBinder onBind(Intent intent) {
277         return null;
278     }
279 
getTaskId()280     private int getTaskId() {
281         // TODO(twyen): generate unique IDs. Reference counting is used now so it doesn't matter.
282         return 1;
283     }
284 
285     /**
286      * Class for interacting with the main interface of the service.
287      */
288     private class RemoteServiceConnection implements ServiceConnection {
289 
290         private final Queue<Message> mTaskQueue = new LinkedList<>();
291 
292         private boolean mConnected;
293 
294         /**
295          * A handler in the VisualVoicemailService
296          */
297         private Messenger mRemoteMessenger;
298 
enqueue(Message message)299         public void enqueue(Message message) {
300             mTaskQueue.add(message);
301             if (mConnected) {
302                 runQueue();
303             }
304         }
305 
isConnected()306         public boolean isConnected() {
307             return mConnected;
308         }
309 
onServiceConnected(ComponentName className, IBinder service)310         public void onServiceConnected(ComponentName className,
311                 IBinder service) {
312             mRemoteMessenger = new Messenger(service);
313             mConnected = true;
314             runQueue();
315         }
316 
onServiceDisconnected(ComponentName className)317         public void onServiceDisconnected(ComponentName className) {
318             mConnection = null;
319             mConnected = false;
320             mRemoteMessenger = null;
321             VvmLog.e(TAG, "Service disconnected, " + mTaskReferenceCount + " tasks dropped.");
322             mTaskReferenceCount = 0;
323             checkReference();
324         }
325 
runQueue()326         private void runQueue() {
327             Assert.isMainThread();
328             Message message = mTaskQueue.poll();
329             while (message != null) {
330                 message.replyTo = mMessenger;
331                 message.arg1 = getTaskId();
332 
333                 try {
334                     mRemoteMessenger.send(message);
335                 } catch (RemoteException e) {
336                     VvmLog.e(TAG, "Error sending message to remote service", e);
337                 }
338                 message = mTaskQueue.poll();
339             }
340         }
341     }
342 
send(ComponentName remotePackage, int what, Bundle extras, UserHandle userHandle)343     private void send(ComponentName remotePackage, int what, Bundle extras, UserHandle userHandle) {
344         Assert.isMainThread();
345 
346         if (getBroadcastPackage(this) != null) {
347             /*
348              * Temporarily use a broadcast to notify dialer VVM events instead of using the
349              * VisualVoicemailService.
350              * b/35766990 The VisualVoicemailService is undergoing API changes. The dialer is in
351              * a different repository so it can not be updated in sync with android SDK. It is also
352              * hard to make a manifest service to work in the intermittent state.
353              */
354             VvmLog.i(TAG, "sending broadcast " + what + " to " + remotePackage);
355             Intent intent = new Intent(ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT);
356             intent.putExtras(extras);
357             intent.putExtra(EXTRA_WHAT, what);
358             intent.setComponent(remotePackage);
359             sendBroadcastAsUser(intent, userHandle);
360             return;
361         }
362 
363         Message message = Message.obtain();
364         message.what = what;
365         message.setData(new Bundle(extras));
366         if (mConnection == null) {
367             mConnection = new RemoteServiceConnection();
368         }
369         mConnection.enqueue(message);
370 
371         if (!mConnection.isConnected()) {
372             Intent intent = newBindIntent(this);
373             intent.setComponent(remotePackage);
374             VvmLog.i(TAG, "Binding to " + intent.getComponent());
375             bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, userHandle);
376         }
377     }
378 
checkReference()379     private void checkReference() {
380         if (mConnection == null) {
381             return;
382         }
383         if (mTaskReferenceCount == 0) {
384             unbindService(mConnection);
385             mConnection = null;
386         }
387     }
388 
newBindIntent(Context context)389     private static Intent newBindIntent(Context context) {
390         Intent intent = new Intent();
391         intent.setAction(VisualVoicemailService.SERVICE_INTERFACE);
392         return intent;
393     }
394 }
395