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