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