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