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