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