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