VoiceInteractionSessionConnection.java revision fc3ec4c5e689e4d64f0c24d42a3b4b31ee4621c4
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.AppOpsManager;
21import android.app.IActivityManager;
22import android.app.assist.AssistContent;
23import android.app.assist.AssistStructure;
24import android.content.ClipData;
25import android.content.ComponentName;
26import android.content.ContentProvider;
27import android.content.Context;
28import android.content.Intent;
29import android.content.ServiceConnection;
30import android.graphics.Bitmap;
31import android.net.Uri;
32import android.os.Binder;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.os.UserHandle;
39import android.provider.Settings;
40import android.service.voice.IVoiceInteractionSession;
41import android.service.voice.IVoiceInteractionSessionService;
42import android.service.voice.VoiceInteractionService;
43import android.service.voice.VoiceInteractionSession;
44import android.util.Slog;
45import android.view.IWindowManager;
46import android.view.WindowManager;
47
48import com.android.internal.app.AssistUtils;
49import com.android.internal.app.IAssistScreenshotReceiver;
50import com.android.internal.app.IVoiceInteractionSessionShowCallback;
51import com.android.internal.app.IVoiceInteractor;
52import com.android.internal.logging.MetricsLogger;
53import com.android.internal.os.IResultReceiver;
54import com.android.server.LocalServices;
55import com.android.server.statusbar.StatusBarManagerInternal;
56
57import java.io.PrintWriter;
58import java.util.ArrayList;
59import java.util.List;
60
61import static android.view.Display.DEFAULT_DISPLAY;
62import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
63
64final class VoiceInteractionSessionConnection implements ServiceConnection {
65
66    final static String TAG = "VoiceInteractionServiceManager";
67
68    private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
69    private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
70
71    final IBinder mToken = new Binder();
72    final Object mLock;
73    final ComponentName mSessionComponentName;
74    final Intent mBindIntent;
75    final int mUser;
76    final Context mContext;
77    final Callback mCallback;
78    final int mCallingUid;
79    final Handler mHandler;
80    final IActivityManager mAm;
81    final IWindowManager mIWindowManager;
82    final AppOpsManager mAppOps;
83    final IBinder mPermissionOwner;
84    boolean mShown;
85    Bundle mShowArgs;
86    int mShowFlags;
87    boolean mBound;
88    boolean mFullyBound;
89    boolean mCanceled;
90    IVoiceInteractionSessionService mService;
91    IVoiceInteractionSession mSession;
92    IVoiceInteractor mInteractor;
93    boolean mHaveAssistData;
94    int mPendingAssistDataCount;
95    ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
96    boolean mHaveScreenshot;
97    Bitmap mScreenshot;
98    ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
99
100    static class AssistDataForActivity {
101        int activityIndex;
102        int activityCount;
103        Bundle data;
104
105        public AssistDataForActivity(Bundle data) {
106            this.data = data;
107            Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
108            if (receiverExtras != null) {
109                activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
110                activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
111            }
112        }
113    }
114
115    IVoiceInteractionSessionShowCallback mShowCallback =
116            new IVoiceInteractionSessionShowCallback.Stub() {
117        @Override
118        public void onFailed() throws RemoteException {
119            synchronized (mLock) {
120                notifyPendingShowCallbacksFailedLocked();
121            }
122        }
123
124        @Override
125        public void onShown() throws RemoteException {
126            synchronized (mLock) {
127                // TODO: Figure out whether this is good enough or whether we need to hook into
128                // Window manager to actually wait for the window to be drawn.
129                notifyPendingShowCallbacksShownLocked();
130            }
131        }
132    };
133
134    public interface Callback {
135        public void sessionConnectionGone(VoiceInteractionSessionConnection connection);
136        public void onSessionShown(VoiceInteractionSessionConnection connection);
137        public void onSessionHidden(VoiceInteractionSessionConnection connection);
138    }
139
140    final ServiceConnection mFullConnection = new ServiceConnection() {
141        @Override
142        public void onServiceConnected(ComponentName name, IBinder service) {
143        }
144        @Override
145        public void onServiceDisconnected(ComponentName name) {
146        }
147    };
148
149    final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
150        @Override
151        public void send(int resultCode, Bundle resultData) throws RemoteException {
152            synchronized (mLock) {
153                if (mShown) {
154                    mHaveAssistData = true;
155                    mAssistData.add(new AssistDataForActivity(resultData));
156                    deliverSessionDataLocked();
157                }
158            }
159        }
160    };
161
162    final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
163        @Override
164        public void send(Bitmap screenshot) throws RemoteException {
165            synchronized (mLock) {
166                if (mShown) {
167                    mHaveScreenshot = true;
168                    mScreenshot = screenshot;
169                    deliverSessionDataLocked();
170                }
171            }
172        }
173    };
174
175    public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
176            Context context, Callback callback, int callingUid, Handler handler) {
177        mLock = lock;
178        mSessionComponentName = component;
179        mUser = user;
180        mContext = context;
181        mCallback = callback;
182        mCallingUid = callingUid;
183        mHandler = handler;
184        mAm = ActivityManager.getService();
185        mIWindowManager = IWindowManager.Stub.asInterface(
186                ServiceManager.getService(Context.WINDOW_SERVICE));
187        mAppOps = context.getSystemService(AppOpsManager.class);
188        IBinder permOwner = null;
189        try {
190            permOwner = mAm.newUriPermissionOwner("voicesession:"
191                    + component.flattenToShortString());
192        } catch (RemoteException e) {
193            Slog.w("voicesession", "AM dead", e);
194        }
195        mPermissionOwner = permOwner;
196        mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
197        mBindIntent.setComponent(mSessionComponentName);
198        mBound = mContext.bindServiceAsUser(mBindIntent, this,
199                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
200                        | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
201        if (mBound) {
202            try {
203                mIWindowManager.addWindowToken(mToken, TYPE_VOICE_INTERACTION, DEFAULT_DISPLAY);
204            } catch (RemoteException e) {
205                Slog.w(TAG, "Failed adding window token", e);
206            }
207        } else {
208            Slog.w(TAG, "Failed binding to voice interaction session service "
209                    + mSessionComponentName);
210        }
211    }
212
213    public int getUserDisabledShowContextLocked() {
214        int flags = 0;
215        if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
216                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) == 0) {
217            flags |= VoiceInteractionSession.SHOW_WITH_ASSIST;
218        }
219        if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
220                Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1, mUser) == 0) {
221            flags |= VoiceInteractionSession.SHOW_WITH_SCREENSHOT;
222        }
223        return flags;
224    }
225
226    public boolean showLocked(Bundle args, int flags, int disabledContext,
227            IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
228            List<IBinder> topActivities) {
229        if (mBound) {
230            if (!mFullyBound) {
231                mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
232                        Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
233                                | Context.BIND_FOREGROUND_SERVICE,
234                        new UserHandle(mUser));
235            }
236            mShown = true;
237            boolean isAssistDataAllowed = true;
238            try {
239                isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
240            } catch (RemoteException e) {
241            }
242            disabledContext |= getUserDisabledShowContextLocked();
243            boolean structureEnabled = isAssistDataAllowed
244                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
245            boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
246                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
247            mShowArgs = args;
248            mShowFlags = flags;
249            mHaveAssistData = false;
250            mPendingAssistDataCount = 0;
251            boolean needDisclosure = false;
252            if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
253                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
254                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
255                        && structureEnabled) {
256                    mAssistData.clear();
257                    final int count = activityToken != null ? 1 : topActivities.size();
258                    for (int i = 0; i < count; 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                mPendingShowCallbacks.clear();
457                if (mSession != null) {
458                    try {
459                        mSession.hide();
460                    } catch (RemoteException e) {
461                    }
462                }
463                try {
464                    mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
465                            Intent.FLAG_GRANT_READ_URI_PERMISSION
466                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
467                            mUser);
468                } catch (RemoteException e) {
469                }
470                if (mSession != null) {
471                    try {
472                        mAm.finishVoiceTask(mSession);
473                    } catch (RemoteException e) {
474                    }
475                }
476                mCallback.onSessionHidden(this);
477            }
478            if (mFullyBound) {
479                mContext.unbindService(mFullConnection);
480                mFullyBound = false;
481            }
482            return true;
483        }
484        return false;
485    }
486
487    public void cancelLocked(boolean finishTask) {
488        hideLocked();
489        mCanceled = true;
490        if (mBound) {
491            if (mSession != null) {
492                try {
493                    mSession.destroy();
494                } catch (RemoteException e) {
495                    Slog.w(TAG, "Voice interation session already dead");
496                }
497            }
498            if (finishTask && mSession != null) {
499                try {
500                    mAm.finishVoiceTask(mSession);
501                } catch (RemoteException e) {
502                }
503            }
504            mContext.unbindService(this);
505            try {
506                mIWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY);
507            } catch (RemoteException e) {
508                Slog.w(TAG, "Failed removing window token", e);
509            }
510            mBound = false;
511            mService = null;
512            mSession = null;
513            mInteractor = null;
514        }
515        if (mFullyBound) {
516            mContext.unbindService(mFullConnection);
517            mFullyBound = false;
518        }
519    }
520
521    public boolean deliverNewSessionLocked(IVoiceInteractionSession session,
522            IVoiceInteractor interactor) {
523        mSession = session;
524        mInteractor = interactor;
525        if (mShown) {
526            try {
527                session.show(mShowArgs, mShowFlags, mShowCallback);
528                mShowArgs = null;
529                mShowFlags = 0;
530            } catch (RemoteException e) {
531            }
532            deliverSessionDataLocked();
533        }
534        return true;
535    }
536
537    private void notifyPendingShowCallbacksShownLocked() {
538        for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
539            try {
540                mPendingShowCallbacks.get(i).onShown();
541            } catch (RemoteException e) {
542            }
543        }
544        mPendingShowCallbacks.clear();
545    }
546
547    private void notifyPendingShowCallbacksFailedLocked() {
548        for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
549            try {
550                mPendingShowCallbacks.get(i).onFailed();
551            } catch (RemoteException e) {
552            }
553        }
554        mPendingShowCallbacks.clear();
555    }
556
557    @Override
558    public void onServiceConnected(ComponentName name, IBinder service) {
559        synchronized (mLock) {
560            mService = IVoiceInteractionSessionService.Stub.asInterface(service);
561            if (!mCanceled) {
562                try {
563                    mService.newSession(mToken, mShowArgs, mShowFlags);
564                } catch (RemoteException e) {
565                    Slog.w(TAG, "Failed adding window token", e);
566                }
567            }
568        }
569    }
570
571    @Override
572    public void onServiceDisconnected(ComponentName name) {
573        mCallback.sessionConnectionGone(this);
574        synchronized (mLock) {
575            mService = null;
576        }
577    }
578
579    public void dump(String prefix, PrintWriter pw) {
580        pw.print(prefix); pw.print("mToken="); pw.println(mToken);
581        pw.print(prefix); pw.print("mShown="); pw.println(mShown);
582        pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs);
583        pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags));
584        pw.print(prefix); pw.print("mBound="); pw.println(mBound);
585        if (mBound) {
586            pw.print(prefix); pw.print("mService="); pw.println(mService);
587            pw.print(prefix); pw.print("mSession="); pw.println(mSession);
588            pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
589        }
590        pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
591        if (mHaveAssistData) {
592            pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
593        }
594    }
595
596    private Runnable mShowAssistDisclosureRunnable = new Runnable() {
597        @Override
598        public void run() {
599            StatusBarManagerInternal statusBarInternal = LocalServices.getService(
600                    StatusBarManagerInternal.class);
601            if (statusBarInternal != null) {
602                statusBarInternal.showAssistDisclosure();
603            }
604        }
605    };
606};
607