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