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