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