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