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