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