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