Home | History | Annotate | Download | only in voiceinteraction
      1 /*
      2  * Copyright (C) 2014 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.server.voiceinteraction;
     18 
     19 import android.app.ActivityManager;
     20 import android.app.ActivityManagerInternal;
     21 import android.app.ActivityManagerNative;
     22 import android.app.IActivityManager;
     23 import android.content.BroadcastReceiver;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.content.ServiceConnection;
     29 import android.content.pm.PackageManager;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.IBinder;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.os.UserHandle;
     36 import android.service.voice.IVoiceInteractionService;
     37 import android.service.voice.IVoiceInteractionSession;
     38 import android.service.voice.VoiceInteractionService;
     39 import android.service.voice.VoiceInteractionServiceInfo;
     40 import android.util.PrintWriterPrinter;
     41 import android.util.Slog;
     42 import android.view.IWindowManager;
     43 
     44 import com.android.internal.app.IVoiceInteractionSessionListener;
     45 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
     46 import com.android.internal.app.IVoiceInteractor;
     47 import com.android.server.LocalServices;
     48 
     49 import java.io.FileDescriptor;
     50 import java.io.PrintWriter;
     51 import java.util.List;
     52 
     53 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
     54     final static String TAG = "VoiceInteractionServiceManager";
     55 
     56     final static String CLOSE_REASON_VOICE_INTERACTION = "voiceinteraction";
     57 
     58     final boolean mValid;
     59 
     60     final Context mContext;
     61     final Handler mHandler;
     62     final VoiceInteractionManagerService.VoiceInteractionManagerServiceStub mServiceStub;
     63     final int mUser;
     64     final ComponentName mComponent;
     65     final IActivityManager mAm;
     66     final VoiceInteractionServiceInfo mInfo;
     67     final ComponentName mSessionComponentName;
     68     final IWindowManager mIWindowManager;
     69     boolean mBound = false;
     70     IVoiceInteractionService mService;
     71 
     72     VoiceInteractionSessionConnection mActiveSession;
     73     int mDisabledShowContext;
     74 
     75     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     76         @Override
     77         public void onReceive(Context context, Intent intent) {
     78             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
     79                 String reason = intent.getStringExtra("reason");
     80                 if (!CLOSE_REASON_VOICE_INTERACTION.equals(reason) && !"dream".equals(reason)) {
     81                     synchronized (mServiceStub) {
     82                         if (mActiveSession != null && mActiveSession.mSession != null) {
     83                             try {
     84                                 mActiveSession.mSession.closeSystemDialogs();
     85                             } catch (RemoteException e) {
     86                             }
     87                         }
     88                     }
     89                 }
     90             }
     91         }
     92     };
     93 
     94     final ServiceConnection mConnection = new ServiceConnection() {
     95         @Override
     96         public void onServiceConnected(ComponentName name, IBinder service) {
     97             synchronized (mServiceStub) {
     98                 mService = IVoiceInteractionService.Stub.asInterface(service);
     99                 try {
    100                     mService.ready();
    101                 } catch (RemoteException e) {
    102                 }
    103             }
    104         }
    105 
    106         @Override
    107         public void onServiceDisconnected(ComponentName name) {
    108             mService = null;
    109         }
    110     };
    111 
    112     VoiceInteractionManagerServiceImpl(Context context, Handler handler,
    113             VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub,
    114             int userHandle, ComponentName service) {
    115         mContext = context;
    116         mHandler = handler;
    117         mServiceStub = stub;
    118         mUser = userHandle;
    119         mComponent = service;
    120         mAm = ActivityManagerNative.getDefault();
    121         VoiceInteractionServiceInfo info;
    122         try {
    123             info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
    124         } catch (PackageManager.NameNotFoundException e) {
    125             Slog.w(TAG, "Voice interaction service not found: " + service, e);
    126             mInfo = null;
    127             mSessionComponentName = null;
    128             mIWindowManager = null;
    129             mValid = false;
    130             return;
    131         }
    132         mInfo = info;
    133         if (mInfo.getParseError() != null) {
    134             Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
    135             mSessionComponentName = null;
    136             mIWindowManager = null;
    137             mValid = false;
    138             return;
    139         }
    140         mValid = true;
    141         mSessionComponentName = new ComponentName(service.getPackageName(),
    142                 mInfo.getSessionService());
    143         mIWindowManager = IWindowManager.Stub.asInterface(
    144                 ServiceManager.getService(Context.WINDOW_SERVICE));
    145         IntentFilter filter = new IntentFilter();
    146         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    147         mContext.registerReceiver(mBroadcastReceiver, filter, null, handler);
    148     }
    149 
    150     public boolean showSessionLocked(Bundle args, int flags,
    151             IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
    152         if (mActiveSession == null) {
    153             mActiveSession = new VoiceInteractionSessionConnection(mServiceStub,
    154                     mSessionComponentName, mUser, mContext, this,
    155                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
    156         }
    157         List<IBinder> activityTokens = null;
    158         if (activityToken == null) {
    159             // Let's get top activities from all visible stacks
    160             activityTokens = LocalServices.getService(ActivityManagerInternal.class)
    161                     .getTopVisibleActivities();
    162         }
    163         return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
    164                 activityToken, activityTokens);
    165     }
    166 
    167     public boolean hideSessionLocked() {
    168         if (mActiveSession != null) {
    169             return mActiveSession.hideLocked();
    170         }
    171         return false;
    172     }
    173 
    174     public boolean deliverNewSessionLocked(IBinder token,
    175             IVoiceInteractionSession session, IVoiceInteractor interactor) {
    176         if (mActiveSession == null || token != mActiveSession.mToken) {
    177             Slog.w(TAG, "deliverNewSession does not match active session");
    178             return false;
    179         }
    180         mActiveSession.deliverNewSessionLocked(session, interactor);
    181         return true;
    182     }
    183 
    184     public int startVoiceActivityLocked(int callingPid, int callingUid, IBinder token,
    185             Intent intent, String resolvedType) {
    186         try {
    187             if (mActiveSession == null || token != mActiveSession.mToken) {
    188                 Slog.w(TAG, "startVoiceActivity does not match active session");
    189                 return ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
    190             }
    191             if (!mActiveSession.mShown) {
    192                 Slog.w(TAG, "startVoiceActivity not allowed on hidden session");
    193                 return ActivityManager.START_VOICE_HIDDEN_SESSION;
    194             }
    195             intent = new Intent(intent);
    196             intent.addCategory(Intent.CATEGORY_VOICE);
    197             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    198             return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid,
    199                     intent, resolvedType, mActiveSession.mSession, mActiveSession.mInteractor,
    200                     0, null, null, mUser);
    201         } catch (RemoteException e) {
    202             throw new IllegalStateException("Unexpected remote error", e);
    203         }
    204     }
    205 
    206     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
    207         try {
    208             if (mActiveSession == null || token != mActiveSession.mToken) {
    209                 Slog.w(TAG, "setKeepAwake does not match active session");
    210                 return;
    211             }
    212             mAm.setVoiceKeepAwake(mActiveSession.mSession, keepAwake);
    213         } catch (RemoteException e) {
    214             throw new IllegalStateException("Unexpected remote error", e);
    215         }
    216     }
    217 
    218     public void closeSystemDialogsLocked(IBinder token) {
    219         try {
    220             if (mActiveSession == null || token != mActiveSession.mToken) {
    221                 Slog.w(TAG, "closeSystemDialogs does not match active session");
    222                 return;
    223             }
    224             mAm.closeSystemDialogs(CLOSE_REASON_VOICE_INTERACTION);
    225         } catch (RemoteException e) {
    226             throw new IllegalStateException("Unexpected remote error", e);
    227         }
    228     }
    229 
    230     public void finishLocked(IBinder token, boolean finishTask) {
    231         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
    232             Slog.w(TAG, "finish does not match active session");
    233             return;
    234         }
    235         mActiveSession.cancelLocked(finishTask);
    236         mActiveSession = null;
    237     }
    238 
    239     public void setDisabledShowContextLocked(int callingUid, int flags) {
    240         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
    241         if (callingUid != activeUid) {
    242             throw new SecurityException("Calling uid " + callingUid
    243                     + " does not match active uid " + activeUid);
    244         }
    245         mDisabledShowContext = flags;
    246     }
    247 
    248     public int getDisabledShowContextLocked(int callingUid) {
    249         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
    250         if (callingUid != activeUid) {
    251             throw new SecurityException("Calling uid " + callingUid
    252                     + " does not match active uid " + activeUid);
    253         }
    254         return mDisabledShowContext;
    255     }
    256 
    257     public int getUserDisabledShowContextLocked(int callingUid) {
    258         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
    259         if (callingUid != activeUid) {
    260             throw new SecurityException("Calling uid " + callingUid
    261                     + " does not match active uid " + activeUid);
    262         }
    263         return mActiveSession != null ? mActiveSession.getUserDisabledShowContextLocked() : 0;
    264     }
    265 
    266     public boolean supportsLocalVoiceInteraction() {
    267         return mInfo.getSupportsLocalInteraction();
    268     }
    269 
    270     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
    271         if (!mValid) {
    272             pw.print("  NOT VALID: ");
    273             if (mInfo == null) {
    274                 pw.println("no info");
    275             } else {
    276                 pw.println(mInfo.getParseError());
    277             }
    278             return;
    279         }
    280         pw.print("  mUser="); pw.println(mUser);
    281         pw.print("  mComponent="); pw.println(mComponent.flattenToShortString());
    282         pw.print("  Session service="); pw.println(mInfo.getSessionService());
    283         pw.println("  Service info:");
    284         mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), "    ");
    285         pw.print("  Recognition service="); pw.println(mInfo.getRecognitionService());
    286         pw.print("  Settings activity="); pw.println(mInfo.getSettingsActivity());
    287         pw.print("  Supports assist="); pw.println(mInfo.getSupportsAssist());
    288         pw.print("  Supports launch from keyguard=");
    289         pw.println(mInfo.getSupportsLaunchFromKeyguard());
    290         if (mDisabledShowContext != 0) {
    291             pw.print("  mDisabledShowContext=");
    292             pw.println(Integer.toHexString(mDisabledShowContext));
    293         }
    294         pw.print("  mBound="); pw.print(mBound);  pw.print(" mService="); pw.println(mService);
    295         if (mActiveSession != null) {
    296             pw.println("  Active session:");
    297             mActiveSession.dump("    ", pw);
    298         }
    299     }
    300 
    301     void startLocked() {
    302         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
    303         intent.setComponent(mComponent);
    304         mBound = mContext.bindServiceAsUser(intent, mConnection,
    305                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUser));
    306         if (!mBound) {
    307             Slog.w(TAG, "Failed binding to voice interaction service " + mComponent);
    308         }
    309     }
    310 
    311     public void launchVoiceAssistFromKeyguard() {
    312         if (mService == null) {
    313             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
    314             return;
    315         }
    316         try {
    317             mService.launchVoiceAssistFromKeyguard();
    318         } catch (RemoteException e) {
    319             Slog.w(TAG, "RemoteException while calling launchVoiceAssistFromKeyguard", e);
    320         }
    321     }
    322 
    323     void shutdownLocked() {
    324         // If there is an active session, cancel it to allow it to clean up its window and other
    325         // state.
    326         if (mActiveSession != null) {
    327             mActiveSession.cancelLocked(false);
    328             mActiveSession = null;
    329         }
    330         try {
    331             if (mService != null) {
    332                 mService.shutdown();
    333             }
    334         } catch (RemoteException e) {
    335             Slog.w(TAG, "RemoteException in shutdown", e);
    336         }
    337 
    338         if (mBound) {
    339             mContext.unbindService(mConnection);
    340             mBound = false;
    341         }
    342         if (mValid) {
    343             mContext.unregisterReceiver(mBroadcastReceiver);
    344         }
    345     }
    346 
    347     void notifySoundModelsChangedLocked() {
    348         if (mService == null) {
    349             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
    350             return;
    351         }
    352         try {
    353             mService.soundModelsChanged();
    354         } catch (RemoteException e) {
    355             Slog.w(TAG, "RemoteException while calling soundModelsChanged", e);
    356         }
    357     }
    358 
    359     @Override
    360     public void sessionConnectionGone(VoiceInteractionSessionConnection connection) {
    361         synchronized (mServiceStub) {
    362             finishLocked(connection.mToken, false);
    363         }
    364     }
    365 
    366     @Override
    367     public void onSessionShown(VoiceInteractionSessionConnection connection) {
    368         mServiceStub.onSessionShown();
    369     }
    370 
    371     @Override
    372     public void onSessionHidden(VoiceInteractionSessionConnection connection) {
    373         mServiceStub.onSessionHidden();
    374     }
    375 }
    376