VoiceInteractionSessionConnection.java revision a133f0b522f66b45d4105f6d514a8a17120833ad
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 structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
202                    Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) != 0
203                    && isScreenCaptureAllowed;
204            boolean screenshotEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
205                    Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1, mUser) != 0
206                    && isScreenCaptureAllowed;
207            mShowArgs = args;
208            mShowFlags = flags;
209            mHaveAssistData = false;
210            boolean needDisclosure = false;
211            if ((flags& VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
212                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
213                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
214                        && structureEnabled) {
215                    try {
216                        needDisclosure = true;
217                        mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
218                                mAssistReceiver);
219                    } catch (RemoteException e) {
220                    }
221                } else {
222                    mHaveAssistData = true;
223                    mAssistData = null;
224                }
225            } else {
226                mAssistData = null;
227            }
228            mHaveScreenshot = false;
229            if ((flags& VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
230                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
231                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
232                        && screenshotEnabled) {
233                    try {
234                        needDisclosure = true;
235                        mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
236                    } catch (RemoteException e) {
237                    }
238                } else {
239                    mHaveScreenshot = true;
240                    mScreenshot = null;
241                }
242            } else {
243                mScreenshot = null;
244            }
245            if (needDisclosure) {
246                mHandler.post(mShowAssistDisclosureRunnable);
247            }
248            if (mSession != null) {
249                try {
250                    mSession.show(mShowArgs, mShowFlags, showCallback);
251                    mShowArgs = null;
252                    mShowFlags = 0;
253                } catch (RemoteException e) {
254                }
255                deliverSessionDataLocked();
256            } else if (showCallback != null) {
257                mPendingShowCallbacks.add(showCallback);
258            }
259            return true;
260        }
261        if (showCallback != null) {
262            try {
263                showCallback.onFailed();
264            } catch (RemoteException e) {
265            }
266        }
267        return false;
268    }
269
270    void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
271        if (!"content".equals(uri.getScheme())) {
272            return;
273        }
274        long ident = Binder.clearCallingIdentity();
275        try {
276            // This will throw SecurityException for us.
277            mAm.checkGrantUriPermission(srcUid, null, ContentProvider.getUriWithoutUserId(uri),
278                    mode, ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(srcUid)));
279            // No security exception, do the grant.
280            int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
281            uri = ContentProvider.getUriWithoutUserId(uri);
282            mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
283                    uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
284        } catch (RemoteException e) {
285        } catch (SecurityException e) {
286            Slog.w(TAG, "Can't propagate permission", e);
287        } finally {
288            Binder.restoreCallingIdentity(ident);
289        }
290
291    }
292
293    void grantClipDataItemPermission(ClipData.Item item, int mode, int srcUid, int destUid,
294            String destPkg) {
295        if (item.getUri() != null) {
296            grantUriPermission(item.getUri(), mode, srcUid, destUid, destPkg);
297        }
298        Intent intent = item.getIntent();
299        if (intent != null && intent.getData() != null) {
300            grantUriPermission(intent.getData(), mode, srcUid, destUid, destPkg);
301        }
302    }
303
304    void grantClipDataPermissions(ClipData data, int mode, int srcUid, int destUid,
305            String destPkg) {
306        final int N = data.getItemCount();
307        for (int i=0; i<N; i++) {
308            grantClipDataItemPermission(data.getItemAt(i), mode, srcUid, destUid, destPkg);
309        }
310    }
311
312    void deliverSessionDataLocked() {
313        if (mSession == null) {
314            return;
315        }
316        if (mHaveAssistData) {
317            Bundle assistData;
318            AssistStructure structure;
319            AssistContent content;
320            if (mAssistData != null) {
321                assistData = mAssistData.getBundle("data");
322                structure = mAssistData.getParcelable("structure");
323                content = mAssistData.getParcelable("content");
324                int uid = mAssistData.getInt(Intent.EXTRA_ASSIST_UID, -1);
325                if (uid >= 0 && content != null) {
326                    Intent intent = content.getIntent();
327                    if (intent != null) {
328                        ClipData data = intent.getClipData();
329                        if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
330                            grantClipDataPermissions(data, intent.getFlags(), uid,
331                                    mCallingUid, mSessionComponentName.getPackageName());
332                        }
333                    }
334                    ClipData data = content.getClipData();
335                    if (data != null) {
336                        grantClipDataPermissions(data,
337                                Intent.FLAG_GRANT_READ_URI_PERMISSION,
338                                uid, mCallingUid, mSessionComponentName.getPackageName());
339                    }
340                }
341            } else {
342                assistData = null;
343                structure = null;
344                content = null;
345            }
346            try {
347                mSession.handleAssist(assistData, structure, content);
348            } catch (RemoteException e) {
349            }
350            mAssistData = null;
351            mHaveAssistData = false;
352        }
353        if (mHaveScreenshot) {
354            try {
355                mSession.handleScreenshot(mScreenshot);
356            } catch (RemoteException e) {
357            }
358            mScreenshot = null;
359            mHaveScreenshot = false;
360        }
361    }
362
363    public boolean hideLocked() {
364        if (mBound) {
365            if (mShown) {
366                mShown = false;
367                mShowArgs = null;
368                mShowFlags = 0;
369                mHaveAssistData = false;
370                mAssistData = null;
371                if (mSession != null) {
372                    try {
373                        mSession.hide();
374                    } catch (RemoteException e) {
375                    }
376                }
377                try {
378                    mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
379                            Intent.FLAG_GRANT_READ_URI_PERMISSION
380                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
381                            mUser);
382                } catch (RemoteException e) {
383                }
384                if (mSession != null) {
385                    try {
386                        mAm.finishVoiceTask(mSession);
387                    } catch (RemoteException e) {
388                    }
389                }
390            }
391            if (mFullyBound) {
392                mContext.unbindService(mFullConnection);
393                mFullyBound = false;
394            }
395            return true;
396        }
397        return false;
398    }
399
400    public void cancelLocked() {
401        hideLocked();
402        mCanceled = true;
403        if (mBound) {
404            if (mSession != null) {
405                try {
406                    mSession.destroy();
407                } catch (RemoteException e) {
408                    Slog.w(TAG, "Voice interation session already dead");
409                }
410            }
411            if (mSession != null) {
412                try {
413                    mAm.finishVoiceTask(mSession);
414                } catch (RemoteException e) {
415                }
416            }
417            mContext.unbindService(this);
418            try {
419                mIWindowManager.removeWindowToken(mToken);
420            } catch (RemoteException e) {
421                Slog.w(TAG, "Failed removing window token", e);
422            }
423            mBound = false;
424            mService = null;
425            mSession = null;
426            mInteractor = null;
427        }
428        if (mFullyBound) {
429            mContext.unbindService(mFullConnection);
430            mFullyBound = false;
431        }
432    }
433
434    public boolean deliverNewSessionLocked(IVoiceInteractionSession session,
435            IVoiceInteractor interactor) {
436        mSession = session;
437        mInteractor = interactor;
438        if (mShown) {
439            try {
440                session.show(mShowArgs, mShowFlags, mShowCallback);
441                mShowArgs = null;
442                mShowFlags = 0;
443            } catch (RemoteException e) {
444            }
445            deliverSessionDataLocked();
446        }
447        return true;
448    }
449
450    private void notifyPendingShowCallbacksShownLocked() {
451        for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
452            try {
453                mPendingShowCallbacks.get(i).onShown();
454            } catch (RemoteException e) {
455            }
456        }
457        mPendingShowCallbacks.clear();
458    }
459
460    private void notifyPendingShowCallbacksFailedLocked() {
461        for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
462            try {
463                mPendingShowCallbacks.get(i).onFailed();
464            } catch (RemoteException e) {
465            }
466        }
467        mPendingShowCallbacks.clear();
468    }
469
470    @Override
471    public void onServiceConnected(ComponentName name, IBinder service) {
472        synchronized (mLock) {
473            mService = IVoiceInteractionSessionService.Stub.asInterface(service);
474            if (!mCanceled) {
475                try {
476                    mService.newSession(mToken, mShowArgs, mShowFlags);
477                } catch (RemoteException e) {
478                    Slog.w(TAG, "Failed adding window token", e);
479                }
480            }
481        }
482    }
483
484    @Override
485    public void onServiceDisconnected(ComponentName name) {
486        mCallback.sessionConnectionGone(this);
487        mService = null;
488    }
489
490    public void dump(String prefix, PrintWriter pw) {
491        pw.print(prefix); pw.print("mToken="); pw.println(mToken);
492        pw.print(prefix); pw.print("mShown="); pw.println(mShown);
493        pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs);
494        pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags));
495        pw.print(prefix); pw.print("mBound="); pw.println(mBound);
496        if (mBound) {
497            pw.print(prefix); pw.print("mService="); pw.println(mService);
498            pw.print(prefix); pw.print("mSession="); pw.println(mSession);
499            pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
500        }
501        pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
502        if (mHaveAssistData) {
503            pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
504        }
505    }
506
507    private Runnable mShowAssistDisclosureRunnable = new Runnable() {
508        @Override
509        public void run() {
510            StatusBarManagerInternal statusBarInternal = LocalServices.getService(
511                    StatusBarManagerInternal.class);
512            if (statusBarInternal != null) {
513                statusBarInternal.showAssistDisclosure();
514            }
515        }
516    };
517};
518