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