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