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.AppOpsManager; 21 import android.app.IActivityManager; 22 import android.app.assist.AssistContent; 23 import android.app.assist.AssistStructure; 24 import android.content.ClipData; 25 import android.content.ComponentName; 26 import android.content.ContentProvider; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.graphics.Bitmap; 31 import android.net.Uri; 32 import android.os.Binder; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.provider.Settings; 40 import android.service.voice.IVoiceInteractionSession; 41 import android.service.voice.IVoiceInteractionSessionService; 42 import android.service.voice.VoiceInteractionService; 43 import android.service.voice.VoiceInteractionSession; 44 import android.util.Slog; 45 import android.view.IWindowManager; 46 import android.view.WindowManager; 47 48 import com.android.internal.app.AssistUtils; 49 import com.android.internal.app.IAssistScreenshotReceiver; 50 import com.android.internal.app.IVoiceInteractionSessionShowCallback; 51 import com.android.internal.app.IVoiceInteractor; 52 import com.android.internal.logging.MetricsLogger; 53 import com.android.internal.os.IResultReceiver; 54 import com.android.server.LocalServices; 55 import com.android.server.statusbar.StatusBarManagerInternal; 56 57 import java.io.PrintWriter; 58 import java.util.ArrayList; 59 import java.util.List; 60 61 import static android.view.Display.DEFAULT_DISPLAY; 62 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; 63 64 final class VoiceInteractionSessionConnection implements ServiceConnection { 65 66 final static String TAG = "VoiceInteractionServiceManager"; 67 68 private static final String KEY_RECEIVER_EXTRA_COUNT = "count"; 69 private static final String KEY_RECEIVER_EXTRA_INDEX = "index"; 70 71 final IBinder mToken = new Binder(); 72 final Object mLock; 73 final ComponentName mSessionComponentName; 74 final Intent mBindIntent; 75 final int mUser; 76 final Context mContext; 77 final Callback mCallback; 78 final int mCallingUid; 79 final Handler mHandler; 80 final IActivityManager mAm; 81 final IWindowManager mIWindowManager; 82 final AppOpsManager mAppOps; 83 final IBinder mPermissionOwner; 84 boolean mShown; 85 Bundle mShowArgs; 86 int mShowFlags; 87 boolean mBound; 88 boolean mFullyBound; 89 boolean mCanceled; 90 IVoiceInteractionSessionService mService; 91 IVoiceInteractionSession mSession; 92 IVoiceInteractor mInteractor; 93 boolean mHaveAssistData; 94 int mPendingAssistDataCount; 95 ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>(); 96 boolean mHaveScreenshot; 97 Bitmap mScreenshot; 98 ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); 99 100 static class AssistDataForActivity { 101 int activityIndex; 102 int activityCount; 103 Bundle data; 104 105 public AssistDataForActivity(Bundle data) { 106 this.data = data; 107 Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS); 108 if (receiverExtras != null) { 109 activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX); 110 activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT); 111 } 112 } 113 } 114 115 IVoiceInteractionSessionShowCallback mShowCallback = 116 new IVoiceInteractionSessionShowCallback.Stub() { 117 @Override 118 public void onFailed() throws RemoteException { 119 synchronized (mLock) { 120 notifyPendingShowCallbacksFailedLocked(); 121 } 122 } 123 124 @Override 125 public void onShown() throws RemoteException { 126 synchronized (mLock) { 127 // TODO: Figure out whether this is good enough or whether we need to hook into 128 // Window manager to actually wait for the window to be drawn. 129 notifyPendingShowCallbacksShownLocked(); 130 } 131 } 132 }; 133 134 public interface Callback { 135 public void sessionConnectionGone(VoiceInteractionSessionConnection connection); 136 public void onSessionShown(VoiceInteractionSessionConnection connection); 137 public void onSessionHidden(VoiceInteractionSessionConnection connection); 138 } 139 140 final ServiceConnection mFullConnection = new ServiceConnection() { 141 @Override 142 public void onServiceConnected(ComponentName name, IBinder service) { 143 } 144 @Override 145 public void onServiceDisconnected(ComponentName name) { 146 } 147 }; 148 149 final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { 150 @Override 151 public void send(int resultCode, Bundle resultData) throws RemoteException { 152 synchronized (mLock) { 153 if (mShown) { 154 mHaveAssistData = true; 155 mAssistData.add(new AssistDataForActivity(resultData)); 156 deliverSessionDataLocked(); 157 } 158 } 159 } 160 }; 161 162 final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() { 163 @Override 164 public void send(Bitmap screenshot) throws RemoteException { 165 synchronized (mLock) { 166 if (mShown) { 167 mHaveScreenshot = true; 168 mScreenshot = screenshot; 169 deliverSessionDataLocked(); 170 } 171 } 172 } 173 }; 174 175 public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user, 176 Context context, Callback callback, int callingUid, Handler handler) { 177 mLock = lock; 178 mSessionComponentName = component; 179 mUser = user; 180 mContext = context; 181 mCallback = callback; 182 mCallingUid = callingUid; 183 mHandler = handler; 184 mAm = ActivityManager.getService(); 185 mIWindowManager = IWindowManager.Stub.asInterface( 186 ServiceManager.getService(Context.WINDOW_SERVICE)); 187 mAppOps = context.getSystemService(AppOpsManager.class); 188 IBinder permOwner = null; 189 try { 190 permOwner = mAm.newUriPermissionOwner("voicesession:" 191 + component.flattenToShortString()); 192 } catch (RemoteException e) { 193 Slog.w("voicesession", "AM dead", e); 194 } 195 mPermissionOwner = permOwner; 196 mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); 197 mBindIntent.setComponent(mSessionComponentName); 198 mBound = mContext.bindServiceAsUser(mBindIntent, this, 199 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY 200 | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser)); 201 if (mBound) { 202 try { 203 mIWindowManager.addWindowToken(mToken, TYPE_VOICE_INTERACTION, DEFAULT_DISPLAY); 204 } catch (RemoteException e) { 205 Slog.w(TAG, "Failed adding window token", e); 206 } 207 } else { 208 Slog.w(TAG, "Failed binding to voice interaction session service " 209 + mSessionComponentName); 210 } 211 } 212 213 public int getUserDisabledShowContextLocked() { 214 int flags = 0; 215 if (Settings.Secure.getIntForUser(mContext.getContentResolver(), 216 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, mUser) == 0) { 217 flags |= VoiceInteractionSession.SHOW_WITH_ASSIST; 218 } 219 if (Settings.Secure.getIntForUser(mContext.getContentResolver(), 220 Settings.Secure.ASSIST_SCREENSHOT_ENABLED, 1, mUser) == 0) { 221 flags |= VoiceInteractionSession.SHOW_WITH_SCREENSHOT; 222 } 223 return flags; 224 } 225 226 public boolean showLocked(Bundle args, int flags, int disabledContext, 227 IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken, 228 List<IBinder> topActivities) { 229 if (mBound) { 230 if (!mFullyBound) { 231 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection, 232 Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY 233 | Context.BIND_FOREGROUND_SERVICE, 234 new UserHandle(mUser)); 235 } 236 mShown = true; 237 boolean isAssistDataAllowed = true; 238 try { 239 isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity(); 240 } catch (RemoteException e) { 241 } 242 disabledContext |= getUserDisabledShowContextLocked(); 243 boolean structureEnabled = isAssistDataAllowed 244 && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0; 245 boolean screenshotEnabled = isAssistDataAllowed && structureEnabled 246 && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0; 247 mShowArgs = args; 248 mShowFlags = flags; 249 mHaveAssistData = false; 250 mPendingAssistDataCount = 0; 251 boolean needDisclosure = false; 252 if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) { 253 if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid, 254 mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED 255 && structureEnabled) { 256 mAssistData.clear(); 257 final int count = activityToken != null ? 1 : topActivities.size(); 258 for (int i = 0; i < count; 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 mPendingShowCallbacks.clear(); 457 if (mSession != null) { 458 try { 459 mSession.hide(); 460 } catch (RemoteException e) { 461 } 462 } 463 try { 464 mAm.revokeUriPermissionFromOwner(mPermissionOwner, null, 465 Intent.FLAG_GRANT_READ_URI_PERMISSION 466 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION, 467 mUser); 468 } catch (RemoteException e) { 469 } 470 if (mSession != null) { 471 try { 472 mAm.finishVoiceTask(mSession); 473 } catch (RemoteException e) { 474 } 475 } 476 mCallback.onSessionHidden(this); 477 } 478 if (mFullyBound) { 479 mContext.unbindService(mFullConnection); 480 mFullyBound = false; 481 } 482 return true; 483 } 484 return false; 485 } 486 487 public void cancelLocked(boolean finishTask) { 488 hideLocked(); 489 mCanceled = true; 490 if (mBound) { 491 if (mSession != null) { 492 try { 493 mSession.destroy(); 494 } catch (RemoteException e) { 495 Slog.w(TAG, "Voice interation session already dead"); 496 } 497 } 498 if (finishTask && mSession != null) { 499 try { 500 mAm.finishVoiceTask(mSession); 501 } catch (RemoteException e) { 502 } 503 } 504 mContext.unbindService(this); 505 try { 506 mIWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY); 507 } catch (RemoteException e) { 508 Slog.w(TAG, "Failed removing window token", e); 509 } 510 mBound = false; 511 mService = null; 512 mSession = null; 513 mInteractor = null; 514 } 515 if (mFullyBound) { 516 mContext.unbindService(mFullConnection); 517 mFullyBound = false; 518 } 519 } 520 521 public boolean deliverNewSessionLocked(IVoiceInteractionSession session, 522 IVoiceInteractor interactor) { 523 mSession = session; 524 mInteractor = interactor; 525 if (mShown) { 526 try { 527 session.show(mShowArgs, mShowFlags, mShowCallback); 528 mShowArgs = null; 529 mShowFlags = 0; 530 } catch (RemoteException e) { 531 } 532 deliverSessionDataLocked(); 533 } 534 return true; 535 } 536 537 private void notifyPendingShowCallbacksShownLocked() { 538 for (int i = 0; i < mPendingShowCallbacks.size(); i++) { 539 try { 540 mPendingShowCallbacks.get(i).onShown(); 541 } catch (RemoteException e) { 542 } 543 } 544 mPendingShowCallbacks.clear(); 545 } 546 547 private void notifyPendingShowCallbacksFailedLocked() { 548 for (int i = 0; i < mPendingShowCallbacks.size(); i++) { 549 try { 550 mPendingShowCallbacks.get(i).onFailed(); 551 } catch (RemoteException e) { 552 } 553 } 554 mPendingShowCallbacks.clear(); 555 } 556 557 @Override 558 public void onServiceConnected(ComponentName name, IBinder service) { 559 synchronized (mLock) { 560 mService = IVoiceInteractionSessionService.Stub.asInterface(service); 561 if (!mCanceled) { 562 try { 563 mService.newSession(mToken, mShowArgs, mShowFlags); 564 } catch (RemoteException e) { 565 Slog.w(TAG, "Failed adding window token", e); 566 } 567 } 568 } 569 } 570 571 @Override 572 public void onServiceDisconnected(ComponentName name) { 573 mCallback.sessionConnectionGone(this); 574 synchronized (mLock) { 575 mService = null; 576 } 577 } 578 579 public void dump(String prefix, PrintWriter pw) { 580 pw.print(prefix); pw.print("mToken="); pw.println(mToken); 581 pw.print(prefix); pw.print("mShown="); pw.println(mShown); 582 pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs); 583 pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags)); 584 pw.print(prefix); pw.print("mBound="); pw.println(mBound); 585 if (mBound) { 586 pw.print(prefix); pw.print("mService="); pw.println(mService); 587 pw.print(prefix); pw.print("mSession="); pw.println(mSession); 588 pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor); 589 } 590 pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData); 591 if (mHaveAssistData) { 592 pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData); 593 } 594 } 595 596 private Runnable mShowAssistDisclosureRunnable = new Runnable() { 597 @Override 598 public void run() { 599 StatusBarManagerInternal statusBarInternal = LocalServices.getService( 600 StatusBarManagerInternal.class); 601 if (statusBarInternal != null) { 602 statusBarInternal.showAssistDisclosure(); 603 } 604 } 605 }; 606 }; 607