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