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