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