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