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