VoiceInteractionSessionConnection.java revision c200f44c46b7d6ddb104c0f09f2a4c679e218d0b
1/*
2 * Copyright (C) 2015 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.AppOpsManager;
22import android.app.IActivityManager;
23import android.app.assist.AssistContent;
24import android.app.assist.AssistStructure;
25import android.content.ClipData;
26import android.content.ComponentName;
27import android.content.ContentProvider;
28import android.content.Context;
29import android.content.Intent;
30import android.content.ServiceConnection;
31import android.graphics.Bitmap;
32import android.net.Uri;
33import android.os.Binder;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.os.UserHandle;
40import android.provider.Settings;
41import android.service.voice.IVoiceInteractionSession;
42import android.service.voice.IVoiceInteractionSessionService;
43import android.service.voice.VoiceInteractionService;
44import android.service.voice.VoiceInteractionSession;
45import android.util.Slog;
46import android.view.IWindowManager;
47import android.view.WindowManager;
48import com.android.internal.app.IAssistScreenshotReceiver;
49import com.android.internal.app.IVoiceInteractionSessionShowCallback;
50import com.android.internal.app.IVoiceInteractor;
51import com.android.internal.os.IResultReceiver;
52import com.android.server.LocalServices;
53import com.android.server.statusbar.StatusBarManagerInternal;
54
55import java.io.PrintWriter;
56import java.util.ArrayList;
57
58final class VoiceInteractionSessionConnection implements ServiceConnection {
59    final static String TAG = "VoiceInteractionServiceManager";
60
61    final IBinder mToken = new Binder();
62    final Object mLock;
63    final ComponentName mSessionComponentName;
64    final Intent mBindIntent;
65    final int mUser;
66    final Context mContext;
67    final Callback mCallback;
68    final int mCallingUid;
69    final Handler mHandler;
70    final IActivityManager mAm;
71    final IWindowManager mIWindowManager;
72    final AppOpsManager mAppOps;
73    final IBinder mPermissionOwner;
74    boolean mShown;
75    Bundle mShowArgs;
76    int mShowFlags;
77    boolean mBound;
78    boolean mFullyBound;
79    boolean mCanceled;
80    IVoiceInteractionSessionService mService;
81    IVoiceInteractionSession mSession;
82    IVoiceInteractor mInteractor;
83    boolean mHaveAssistData;
84    Bundle mAssistData;
85    boolean mHaveScreenshot;
86    Bitmap mScreenshot;
87    ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
88
89    IVoiceInteractionSessionShowCallback mShowCallback =
90            new IVoiceInteractionSessionShowCallback.Stub() {
91        @Override
92        public void onFailed() throws RemoteException {
93            synchronized (mLock) {
94                notifyPendingShowCallbacksFailedLocked();
95            }
96        }
97
98        @Override
99        public void onShown() throws RemoteException {
100            synchronized (mLock) {
101                // TODO: Figure out whether this is good enough or whether we need to hook into
102                // Window manager to actually wait for the window to be drawn.
103                notifyPendingShowCallbacksShownLocked();
104            }
105        }
106    };
107
108    public interface Callback {
109        public void sessionConnectionGone(VoiceInteractionSessionConnection connection);
110    }
111
112    final ServiceConnection mFullConnection = new ServiceConnection() {
113        @Override
114        public void onServiceConnected(ComponentName name, IBinder service) {
115        }
116        @Override
117        public void onServiceDisconnected(ComponentName name) {
118        }
119    };
120
121    final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
122        @Override
123        public void send(int resultCode, Bundle resultData) throws RemoteException {
124            synchronized (mLock) {
125                if (mShown) {
126                    mHaveAssistData = true;
127                    mAssistData = resultData;
128                    deliverSessionDataLocked();
129                }
130            }
131        }
132    };
133
134    final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
135        @Override
136        public void send(Bitmap screenshot) throws RemoteException {
137            synchronized (mLock) {
138                if (mShown) {
139                    mHaveScreenshot = true;
140                    mScreenshot = screenshot;
141                    deliverSessionDataLocked();
142                }
143            }
144        }
145    };
146
147    public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
148            Context context, Callback callback, int callingUid, Handler handler) {
149        mLock = lock;
150        mSessionComponentName = component;
151        mUser = user;
152        mContext = context;
153        mCallback = callback;
154        mCallingUid = callingUid;
155        mHandler = handler;
156        mAm = ActivityManagerNative.getDefault();
157        mIWindowManager = IWindowManager.Stub.asInterface(
158                ServiceManager.getService(Context.WINDOW_SERVICE));
159        mAppOps = context.getSystemService(AppOpsManager.class);
160        IBinder permOwner = null;
161        try {
162            permOwner = mAm.newUriPermissionOwner("voicesession:"
163                    + component.flattenToShortString());
164        } catch (RemoteException e) {
165            Slog.w("voicesession", "AM dead", e);
166        }
167        mPermissionOwner = permOwner;
168        mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
169        mBindIntent.setComponent(mSessionComponentName);
170        mBound = mContext.bindServiceAsUser(mBindIntent, this,
171                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
172                        | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
173        if (mBound) {
174            try {
175                mIWindowManager.addWindowToken(mToken,
176                        WindowManager.LayoutParams.TYPE_VOICE_INTERACTION);
177            } catch (RemoteException e) {
178                Slog.w(TAG, "Failed adding window token", e);
179            }
180        } else {
181            Slog.w(TAG, "Failed binding to voice interaction session service "
182                    + mSessionComponentName);
183        }
184    }
185
186    public boolean showLocked(Bundle args, int flags,
187            IVoiceInteractionSessionShowCallback showCallback) {
188        if (mBound) {
189            if (!mFullyBound) {
190                mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
191                        Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
192                                | Context.BIND_FOREGROUND_SERVICE,
193                        new UserHandle(mUser));
194            }
195            mShown = true;
196            boolean isScreenCaptureAllowed = true;
197            try {
198                isScreenCaptureAllowed = mAm.isScreenCaptureAllowedOnCurrentActivity();
199            } catch (RemoteException e) {
200            }
201            boolean allDataEnabled = (Settings.Secure.getIntForUser(mContext.getContentResolver(),
202                    Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) != 0)
203                    && isScreenCaptureAllowed;
204            mShowArgs = args;
205            mShowFlags = flags;
206            mHaveAssistData = false;
207            boolean needDisclosure = false;
208            if ((flags& VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
209                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
210                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
211                        && allDataEnabled) {
212                    try {
213                        needDisclosure = true;
214                        mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
215                                mAssistReceiver);
216                    } catch (RemoteException e) {
217                    }
218                } else {
219                    mHaveAssistData = true;
220                    mAssistData = null;
221                }
222            } else {
223                mAssistData = null;
224            }
225            mHaveScreenshot = false;
226            if ((flags& VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
227                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
228                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
229                        && allDataEnabled) {
230                    try {
231                        needDisclosure = true;
232                        mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
233                    } catch (RemoteException e) {
234                    }
235                } else {
236                    mHaveScreenshot = true;
237                    mScreenshot = null;
238                }
239            } else {
240                mScreenshot = null;
241            }
242            if (needDisclosure) {
243                mHandler.post(mShowAssistDisclosureRunnable);
244            }
245            if (mSession != null) {
246                try {
247                    mSession.show(mShowArgs, mShowFlags, showCallback);
248                    mShowArgs = null;
249                    mShowFlags = 0;
250                } catch (RemoteException e) {
251                }
252                deliverSessionDataLocked();
253            } else if (showCallback != null) {
254                mPendingShowCallbacks.add(showCallback);
255            }
256            return true;
257        }
258        if (showCallback != null) {
259            try {
260                showCallback.onFailed();
261            } catch (RemoteException e) {
262            }
263        }
264        return false;
265    }
266
267    void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
268        if (!"content".equals(uri.getScheme())) {
269            return;
270        }
271        long ident = Binder.clearCallingIdentity();
272        try {
273            // This will throw SecurityException for us.
274            mAm.checkGrantUriPermission(srcUid, null, ContentProvider.getUriWithoutUserId(uri),
275                    mode, ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(srcUid)));
276            // No security exception, do the grant.
277            int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
278            uri = ContentProvider.getUriWithoutUserId(uri);
279            mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
280                    uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
281        } catch (RemoteException e) {
282        } catch (SecurityException e) {
283            Slog.w(TAG, "Can't propagate permission", e);
284        } finally {
285            Binder.restoreCallingIdentity(ident);
286        }
287
288    }
289
290    void grantClipDataItemPermission(ClipData.Item item, int mode, int srcUid, int destUid,
291            String destPkg) {
292        if (item.getUri() != null) {
293            grantUriPermission(item.getUri(), mode, srcUid, destUid, destPkg);
294        }
295        Intent intent = item.getIntent();
296        if (intent != null && intent.getData() != null) {
297            grantUriPermission(intent.getData(), mode, srcUid, destUid, destPkg);
298        }
299    }
300
301    void grantClipDataPermissions(ClipData data, int mode, int srcUid, int destUid,
302            String destPkg) {
303        final int N = data.getItemCount();
304        for (int i=0; i<N; i++) {
305            grantClipDataItemPermission(data.getItemAt(i), mode, srcUid, destUid, destPkg);
306        }
307    }
308
309    void deliverSessionDataLocked() {
310        if (mSession == null) {
311            return;
312        }
313        if (mHaveAssistData) {
314            Bundle assistData;
315            AssistStructure structure;
316            AssistContent content;
317            if (mAssistData != null) {
318                assistData = mAssistData.getBundle("data");
319                structure = mAssistData.getParcelable("structure");
320                content = mAssistData.getParcelable("content");
321                int uid = mAssistData.getInt(Intent.EXTRA_ASSIST_UID, -1);
322                if (uid >= 0 && content != null) {
323                    Intent intent = content.getIntent();
324                    if (intent != null) {
325                        ClipData data = intent.getClipData();
326                        if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
327                            grantClipDataPermissions(data, intent.getFlags(), uid,
328                                    mCallingUid, mSessionComponentName.getPackageName());
329                        }
330                    }
331                    ClipData data = content.getClipData();
332                    if (data != null) {
333                        grantClipDataPermissions(data,
334                                Intent.FLAG_GRANT_READ_URI_PERMISSION,
335                                uid, mCallingUid, mSessionComponentName.getPackageName());
336                    }
337                }
338            } else {
339                assistData = null;
340                structure = null;
341                content = null;
342            }
343            try {
344                mSession.handleAssist(assistData, structure, content);
345            } catch (RemoteException e) {
346            }
347            mAssistData = null;
348            mHaveAssistData = false;
349        }
350        if (mHaveScreenshot) {
351            try {
352                mSession.handleScreenshot(mScreenshot);
353            } catch (RemoteException e) {
354            }
355            mScreenshot = null;
356            mHaveScreenshot = false;
357        }
358    }
359
360    public boolean hideLocked() {
361        if (mBound) {
362            if (mShown) {
363                mShown = false;
364                mShowArgs = null;
365                mShowFlags = 0;
366                mHaveAssistData = false;
367                mAssistData = null;
368                if (mSession != null) {
369                    try {
370                        mSession.hide();
371                    } catch (RemoteException e) {
372                    }
373                }
374                try {
375                    mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
376                            Intent.FLAG_GRANT_READ_URI_PERMISSION
377                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
378                            mUser);
379                } catch (RemoteException e) {
380                }
381                if (mSession != null) {
382                    try {
383                        mAm.finishVoiceTask(mSession);
384                    } catch (RemoteException e) {
385                    }
386                }
387            }
388            if (mFullyBound) {
389                mContext.unbindService(mFullConnection);
390                mFullyBound = false;
391            }
392            return true;
393        }
394        return false;
395    }
396
397    public void cancelLocked() {
398        hideLocked();
399        mCanceled = true;
400        if (mBound) {
401            if (mSession != null) {
402                try {
403                    mSession.destroy();
404                } catch (RemoteException e) {
405                    Slog.w(TAG, "Voice interation session already dead");
406                }
407            }
408            if (mSession != null) {
409                try {
410                    mAm.finishVoiceTask(mSession);
411                } catch (RemoteException e) {
412                }
413            }
414            mContext.unbindService(this);
415            try {
416                mIWindowManager.removeWindowToken(mToken);
417            } catch (RemoteException e) {
418                Slog.w(TAG, "Failed removing window token", e);
419            }
420            mBound = false;
421            mService = null;
422            mSession = null;
423            mInteractor = null;
424        }
425        if (mFullyBound) {
426            mContext.unbindService(mFullConnection);
427            mFullyBound = false;
428        }
429    }
430
431    public boolean deliverNewSessionLocked(IVoiceInteractionSession session,
432            IVoiceInteractor interactor) {
433        mSession = session;
434        mInteractor = interactor;
435        if (mShown) {
436            try {
437                session.show(mShowArgs, mShowFlags, mShowCallback);
438                mShowArgs = null;
439                mShowFlags = 0;
440            } catch (RemoteException e) {
441            }
442            deliverSessionDataLocked();
443        }
444        return true;
445    }
446
447    private void notifyPendingShowCallbacksShownLocked() {
448        for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
449            try {
450                mPendingShowCallbacks.get(i).onShown();
451            } catch (RemoteException e) {
452            }
453        }
454        mPendingShowCallbacks.clear();
455    }
456
457    private void notifyPendingShowCallbacksFailedLocked() {
458        for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
459            try {
460                mPendingShowCallbacks.get(i).onFailed();
461            } catch (RemoteException e) {
462            }
463        }
464        mPendingShowCallbacks.clear();
465    }
466
467    @Override
468    public void onServiceConnected(ComponentName name, IBinder service) {
469        synchronized (mLock) {
470            mService = IVoiceInteractionSessionService.Stub.asInterface(service);
471            if (!mCanceled) {
472                try {
473                    mService.newSession(mToken, mShowArgs, mShowFlags);
474                } catch (RemoteException e) {
475                    Slog.w(TAG, "Failed adding window token", e);
476                }
477            }
478        }
479    }
480
481    @Override
482    public void onServiceDisconnected(ComponentName name) {
483        mCallback.sessionConnectionGone(this);
484        mService = null;
485    }
486
487    public void dump(String prefix, PrintWriter pw) {
488        pw.print(prefix); pw.print("mToken="); pw.println(mToken);
489        pw.print(prefix); pw.print("mShown="); pw.println(mShown);
490        pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs);
491        pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags));
492        pw.print(prefix); pw.print("mBound="); pw.println(mBound);
493        if (mBound) {
494            pw.print(prefix); pw.print("mService="); pw.println(mService);
495            pw.print(prefix); pw.print("mSession="); pw.println(mSession);
496            pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
497        }
498        pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
499        if (mHaveAssistData) {
500            pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
501        }
502    }
503
504    private Runnable mShowAssistDisclosureRunnable = new Runnable() {
505        @Override
506        public void run() {
507            StatusBarManagerInternal statusBarInternal = LocalServices.getService(
508                    StatusBarManagerInternal.class);
509            if (statusBarInternal != null) {
510                statusBarInternal.showAssistDisclosure();
511            }
512        }
513    };
514};
515