| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.phone.vvm; |
| |
| import android.annotation.Nullable; |
| import android.app.Service; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.content.pm.ComponentInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.PersistableBundle; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.VisualVoicemailService; |
| import android.telephony.VisualVoicemailSms; |
| import android.text.TextUtils; |
| |
| import com.android.internal.telephony.util.TelephonyUtils; |
| import com.android.phone.Assert; |
| import com.android.phone.R; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Queue; |
| |
| /** |
| * Service to manage tasks issued to the {@link VisualVoicemailService}. This service will bind to |
| * the default dialer on a visual voicemail event if it implements the VisualVoicemailService. The |
| * service will hold all resource for the VisualVoicemailService until {@link |
| * VisualVoicemailService.VisualVoicemailTask#finish()} has been called on all issued tasks. |
| * |
| * If the service is already running it will be reused for new events. The service will stop itself |
| * after all events are handled. |
| */ |
| public class RemoteVvmTaskManager extends Service { |
| |
| private static final String TAG = "RemoteVvmTaskManager"; |
| |
| private static final String ACTION_START_CELL_SERVICE_CONNECTED = |
| "ACTION_START_CELL_SERVICE_CONNECTED"; |
| private static final String ACTION_START_SMS_RECEIVED = "ACTION_START_SMS_RECEIVED"; |
| private static final String ACTION_START_SIM_REMOVED = "ACTION_START_SIM_REMOVED"; |
| |
| // TODO(b/35766990): Remove after VisualVoicemailService API is stabilized. |
| private static final String ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT = |
| "com.android.phone.vvm.ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT"; |
| private static final String EXTRA_WHAT = "what"; |
| |
| private static final String EXTRA_TARGET_PACKAGE = "target_package"; |
| |
| // TODO(twyen): track task individually to have time outs. |
| private int mTaskReferenceCount; |
| |
| private RemoteServiceConnection mConnection; |
| |
| /** |
| * Handles incoming messages from the VisualVoicemailService. |
| */ |
| private Messenger mMessenger; |
| |
| static void startCellServiceConnected(Context context, |
| PhoneAccountHandle phoneAccountHandle) { |
| Intent intent = new Intent(ACTION_START_CELL_SERVICE_CONNECTED, null, context, |
| RemoteVvmTaskManager.class); |
| intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); |
| context.startService(intent); |
| } |
| |
| static void startSmsReceived(Context context, VisualVoicemailSms sms, |
| String targetPackage) { |
| Intent intent = new Intent(ACTION_START_SMS_RECEIVED, null, context, |
| RemoteVvmTaskManager.class); |
| intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, |
| sms.getPhoneAccountHandle()); |
| intent.putExtra(VisualVoicemailService.DATA_SMS, sms); |
| intent.putExtra(EXTRA_TARGET_PACKAGE, targetPackage); |
| context.startService(intent); |
| } |
| |
| static void startSimRemoved(Context context, PhoneAccountHandle phoneAccountHandle) { |
| Intent intent = new Intent(ACTION_START_SIM_REMOVED, null, context, |
| RemoteVvmTaskManager.class); |
| intent.putExtra(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); |
| context.startService(intent); |
| } |
| |
| static boolean hasRemoteService(Context context, int subId, String targetPackage) { |
| return getRemotePackage(context, subId, targetPackage) != null; |
| } |
| |
| /** |
| * Return the {@link ComponentName} of the {@link VisualVoicemailService} which is active (the |
| * current default dialer), or {@code null} if no implementation is found. |
| */ |
| @Nullable |
| public static ComponentName getRemotePackage(Context context, int subId) { |
| return getRemotePackage(context, subId, null); |
| } |
| |
| /** |
| * Return the {@link ComponentName} of the {@link VisualVoicemailService} which is active (the |
| * current default dialer), or {@code null} if no implementation is found. |
| * |
| * @param targetPackage the package that should be the active VisualVociemailService |
| */ |
| @Nullable |
| public static ComponentName getRemotePackage(Context context, int subId, |
| @Nullable String targetPackage) { |
| ComponentName broadcastPackage = getBroadcastPackage(context); |
| if (broadcastPackage != null) { |
| return broadcastPackage; |
| } |
| |
| Intent bindIntent = newBindIntent(context); |
| |
| TelecomManager telecomManager = context.getSystemService(TelecomManager.class); |
| List<String> packages = new ArrayList<>(); |
| packages.add(telecomManager.getDefaultDialerPackage()); |
| // TODO(b/73136824): Check permissions in the calling function and avoid relying on the |
| // binder caller's permissions to access the carrier config. |
| PersistableBundle carrierConfig = context |
| .getSystemService(CarrierConfigManager.class).getConfigForSubId(subId); |
| packages.add( |
| carrierConfig |
| .getString(CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING)); |
| String[] vvmPackages = carrierConfig |
| .getStringArray(CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY); |
| if (vvmPackages != null && vvmPackages.length > 0) { |
| for (String packageName : vvmPackages) { |
| packages.add(packageName); |
| } |
| } |
| packages.add(context.getResources().getString(R.string.system_visual_voicemail_client)); |
| packages.add(telecomManager.getSystemDialerPackage()); |
| |
| for (String packageName : packages) { |
| if (TextUtils.isEmpty(packageName)) { |
| continue; |
| } |
| bindIntent.setPackage(packageName); |
| ResolveInfo info = context.getPackageManager().resolveService(bindIntent, 0); |
| if (info == null) { |
| continue; |
| } |
| if (info.serviceInfo == null) { |
| VvmLog.w(TAG, |
| "Component " + TelephonyUtils.getComponentInfo(info) |
| + " is not a service, ignoring"); |
| continue; |
| } |
| if (!android.Manifest.permission.BIND_VISUAL_VOICEMAIL_SERVICE |
| .equals(info.serviceInfo.permission)) { |
| VvmLog.w(TAG, "package " + info.serviceInfo.packageName |
| + " does not enforce BIND_VISUAL_VOICEMAIL_SERVICE, ignoring"); |
| continue; |
| } |
| if (targetPackage != null && !TextUtils.equals(packageName, targetPackage)) { |
| VvmLog.w(TAG, "target package " + targetPackage |
| + " is no longer the active VisualVoicemailService, ignoring"); |
| continue; |
| } |
| ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(info); |
| return new ComponentName(componentInfo.packageName, componentInfo.name); |
| |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static ComponentName getBroadcastPackage(Context context) { |
| Intent broadcastIntent = new Intent(ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT); |
| broadcastIntent.setPackage( |
| context.getSystemService(TelecomManager.class).getDefaultDialerPackage()); |
| List<ResolveInfo> info = context.getPackageManager() |
| .queryBroadcastReceivers(broadcastIntent, PackageManager.MATCH_ALL); |
| if (info == null) { |
| return null; |
| } |
| if (info.isEmpty()) { |
| return null; |
| } |
| ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(info.get(0)); |
| return new ComponentName(componentInfo.packageName, componentInfo.name); |
| } |
| |
| @Override |
| public void onCreate() { |
| Assert.isMainThread(); |
| mMessenger = new Messenger(new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| Assert.isMainThread(); |
| switch (msg.what) { |
| case VisualVoicemailService.MSG_TASK_ENDED: |
| mTaskReferenceCount--; |
| checkReference(); |
| break; |
| default: |
| VvmLog.wtf(TAG, "unexpected message " + msg.what); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public int onStartCommand(@Nullable Intent intent, int flags, int startId) { |
| Assert.isMainThread(); |
| mTaskReferenceCount++; |
| |
| if (intent == null) { |
| VvmLog.i(TAG, "received intent is null"); |
| checkReference(); |
| return START_NOT_STICKY; |
| } |
| PhoneAccountHandle phoneAccountHandle = intent.getExtras() |
| .getParcelable(VisualVoicemailService.DATA_PHONE_ACCOUNT_HANDLE); |
| int subId = PhoneAccountHandleConverter.toSubId(phoneAccountHandle); |
| UserHandle userHandle = phoneAccountHandle.getUserHandle(); |
| ComponentName remotePackage = getRemotePackage(this, subId, |
| intent.getStringExtra(EXTRA_TARGET_PACKAGE)); |
| if (remotePackage == null) { |
| VvmLog.i(TAG, "No service to handle " + intent.getAction() + ", ignoring"); |
| checkReference(); |
| return START_NOT_STICKY; |
| } |
| |
| switch (intent.getAction()) { |
| case ACTION_START_CELL_SERVICE_CONNECTED: |
| send(remotePackage, VisualVoicemailService.MSG_ON_CELL_SERVICE_CONNECTED, |
| intent.getExtras(), userHandle); |
| break; |
| case ACTION_START_SMS_RECEIVED: |
| send(remotePackage, VisualVoicemailService.MSG_ON_SMS_RECEIVED, intent.getExtras(), |
| userHandle); |
| break; |
| case ACTION_START_SIM_REMOVED: |
| send(remotePackage, VisualVoicemailService.MSG_ON_SIM_REMOVED, intent.getExtras(), |
| userHandle); |
| break; |
| default: |
| Assert.fail("Unexpected action +" + intent.getAction()); |
| break; |
| } |
| // Don't rerun service if processed is killed. |
| return START_NOT_STICKY; |
| } |
| |
| @Override |
| @Nullable |
| public IBinder onBind(Intent intent) { |
| return null; |
| } |
| |
| private int getTaskId() { |
| // TODO(twyen): generate unique IDs. Reference counting is used now so it doesn't matter. |
| return 1; |
| } |
| |
| /** |
| * Class for interacting with the main interface of the service. |
| */ |
| private class RemoteServiceConnection implements ServiceConnection { |
| |
| private final Queue<Message> mTaskQueue = new LinkedList<>(); |
| |
| private boolean mConnected; |
| |
| /** |
| * A handler in the VisualVoicemailService |
| */ |
| private Messenger mRemoteMessenger; |
| |
| public void enqueue(Message message) { |
| mTaskQueue.add(message); |
| if (mConnected) { |
| runQueue(); |
| } |
| } |
| |
| public boolean isConnected() { |
| return mConnected; |
| } |
| |
| public void onServiceConnected(ComponentName className, |
| IBinder service) { |
| mRemoteMessenger = new Messenger(service); |
| mConnected = true; |
| runQueue(); |
| } |
| |
| public void onServiceDisconnected(ComponentName className) { |
| mConnection = null; |
| mConnected = false; |
| mRemoteMessenger = null; |
| VvmLog.e(TAG, "Service disconnected, " + mTaskReferenceCount + " tasks dropped."); |
| mTaskReferenceCount = 0; |
| checkReference(); |
| } |
| |
| private void runQueue() { |
| Assert.isMainThread(); |
| Message message = mTaskQueue.poll(); |
| while (message != null) { |
| message.replyTo = mMessenger; |
| message.arg1 = getTaskId(); |
| |
| try { |
| mRemoteMessenger.send(message); |
| } catch (RemoteException e) { |
| VvmLog.e(TAG, "Error sending message to remote service", e); |
| } |
| message = mTaskQueue.poll(); |
| } |
| } |
| } |
| |
| private void send(ComponentName remotePackage, int what, Bundle extras, UserHandle userHandle) { |
| Assert.isMainThread(); |
| |
| if (getBroadcastPackage(this) != null) { |
| /* |
| * Temporarily use a broadcast to notify dialer VVM events instead of using the |
| * VisualVoicemailService. |
| * b/35766990 The VisualVoicemailService is undergoing API changes. The dialer is in |
| * a different repository so it can not be updated in sync with android SDK. It is also |
| * hard to make a manifest service to work in the intermittent state. |
| */ |
| VvmLog.i(TAG, "sending broadcast " + what + " to " + remotePackage); |
| Intent intent = new Intent(ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT); |
| intent.putExtras(extras); |
| intent.putExtra(EXTRA_WHAT, what); |
| intent.setComponent(remotePackage); |
| sendBroadcastAsUser(intent, userHandle); |
| return; |
| } |
| |
| Message message = Message.obtain(); |
| message.what = what; |
| message.setData(new Bundle(extras)); |
| if (mConnection == null) { |
| mConnection = new RemoteServiceConnection(); |
| } |
| mConnection.enqueue(message); |
| |
| if (!mConnection.isConnected()) { |
| Intent intent = newBindIntent(this); |
| intent.setComponent(remotePackage); |
| VvmLog.i(TAG, "Binding to " + intent.getComponent()); |
| bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, userHandle); |
| } |
| } |
| |
| private void checkReference() { |
| if (mConnection == null) { |
| return; |
| } |
| if (mTaskReferenceCount == 0) { |
| unbindService(mConnection); |
| mConnection = null; |
| } |
| } |
| |
| private static Intent newBindIntent(Context context) { |
| Intent intent = new Intent(); |
| intent.setAction(VisualVoicemailService.SERVICE_INTERFACE); |
| return intent; |
| } |
| } |