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