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