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