1 /* 2 * Copyright (C) 2016 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.systemui.pip.phone; 18 19 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; 20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 21 import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; 22 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; 23 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS; 24 25 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS; 26 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT; 27 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; 28 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION; 29 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE; 30 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS; 31 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS; 32 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU; 33 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; 34 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; 35 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; 36 37 import android.animation.Animator; 38 import android.animation.AnimatorListenerAdapter; 39 import android.animation.AnimatorSet; 40 import android.animation.ObjectAnimator; 41 import android.animation.ValueAnimator; 42 import android.annotation.Nullable; 43 import android.app.Activity; 44 import android.app.ActivityManager; 45 import android.app.PendingIntent.CanceledException; 46 import android.app.RemoteAction; 47 import android.content.ComponentName; 48 import android.content.Intent; 49 import android.content.pm.ParceledListSlice; 50 import android.graphics.Color; 51 import android.graphics.Rect; 52 import android.graphics.drawable.ColorDrawable; 53 import android.graphics.drawable.Drawable; 54 import android.net.Uri; 55 import android.os.Bundle; 56 import android.os.Handler; 57 import android.os.Message; 58 import android.os.Messenger; 59 import android.os.RemoteException; 60 import android.os.UserHandle; 61 import android.util.Log; 62 import android.util.Pair; 63 import android.view.LayoutInflater; 64 import android.view.MotionEvent; 65 import android.view.View; 66 import android.view.ViewGroup; 67 import android.view.WindowManager.LayoutParams; 68 import android.view.accessibility.AccessibilityManager; 69 import android.widget.FrameLayout; 70 import android.widget.ImageView; 71 import android.widget.LinearLayout; 72 73 import com.android.systemui.Interpolators; 74 import com.android.systemui.R; 75 76 import java.util.ArrayList; 77 import java.util.Collections; 78 import java.util.List; 79 80 /** 81 * Translucent activity that gets started on top of a task in PIP to allow the user to control it. 82 */ 83 public class PipMenuActivity extends Activity { 84 85 private static final String TAG = "PipMenuActivity"; 86 87 public static final int MESSAGE_SHOW_MENU = 1; 88 public static final int MESSAGE_POKE_MENU = 2; 89 public static final int MESSAGE_HIDE_MENU = 3; 90 public static final int MESSAGE_UPDATE_ACTIONS = 4; 91 public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5; 92 public static final int MESSAGE_ANIMATION_ENDED = 6; 93 public static final int MESSAGE_TOUCH_EVENT = 7; 94 95 private static final int INITIAL_DISMISS_DELAY = 3500; 96 private static final int POST_INTERACTION_DISMISS_DELAY = 2000; 97 private static final long MENU_FADE_DURATION = 125; 98 99 private static final float MENU_BACKGROUND_ALPHA = 0.3f; 100 private static final float DISMISS_BACKGROUND_ALPHA = 0.6f; 101 102 private static final float DISABLED_ACTION_ALPHA = 0.54f; 103 104 private int mMenuState; 105 private boolean mAllowMenuTimeout = true; 106 private boolean mAllowTouches = true; 107 108 private final List<RemoteAction> mActions = new ArrayList<>(); 109 110 private AccessibilityManager mAccessibilityManager; 111 private View mViewRoot; 112 private Drawable mBackgroundDrawable; 113 private View mMenuContainer; 114 private LinearLayout mActionsGroup; 115 private View mSettingsButton; 116 private View mDismissButton; 117 private ImageView mExpandButton; 118 private int mBetweenActionPaddingLand; 119 120 private AnimatorSet mMenuContainerAnimator; 121 122 private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = 123 new ValueAnimator.AnimatorUpdateListener() { 124 @Override 125 public void onAnimationUpdate(ValueAnimator animation) { 126 final float alpha = (float) animation.getAnimatedValue(); 127 mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255)); 128 } 129 }; 130 131 private Handler mHandler = new Handler(); 132 private Messenger mToControllerMessenger; 133 private Messenger mMessenger = new Messenger(new Handler() { 134 @Override 135 public void handleMessage(Message msg) { 136 switch (msg.what) { 137 case MESSAGE_SHOW_MENU: { 138 final Bundle data = (Bundle) msg.obj; 139 showMenu(data.getInt(EXTRA_MENU_STATE), 140 data.getParcelable(EXTRA_STACK_BOUNDS), 141 data.getParcelable(EXTRA_MOVEMENT_BOUNDS), 142 data.getBoolean(EXTRA_ALLOW_TIMEOUT), 143 data.getBoolean(EXTRA_WILL_RESIZE_MENU)); 144 break; 145 } 146 case MESSAGE_POKE_MENU: 147 cancelDelayedFinish(); 148 break; 149 case MESSAGE_HIDE_MENU: 150 hideMenu((Runnable) msg.obj); 151 break; 152 case MESSAGE_UPDATE_ACTIONS: { 153 final Bundle data = (Bundle) msg.obj; 154 final ParceledListSlice actions = data.getParcelable(EXTRA_ACTIONS); 155 setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null 156 ? actions.getList() : Collections.EMPTY_LIST); 157 break; 158 } 159 case MESSAGE_UPDATE_DISMISS_FRACTION: { 160 final Bundle data = (Bundle) msg.obj; 161 updateDismissFraction(data.getFloat(EXTRA_DISMISS_FRACTION)); 162 break; 163 } 164 case MESSAGE_ANIMATION_ENDED: { 165 mAllowTouches = true; 166 break; 167 } 168 169 case MESSAGE_TOUCH_EVENT: { 170 final MotionEvent ev = (MotionEvent) msg.obj; 171 dispatchTouchEvent(ev); 172 break; 173 } 174 } 175 } 176 }); 177 178 private final Runnable mFinishRunnable = new Runnable() { 179 @Override 180 public void run() { 181 hideMenu(); 182 } 183 }; 184 185 @Override onCreate(@ullable Bundle savedInstanceState)186 protected void onCreate(@Nullable Bundle savedInstanceState) { 187 // Set the flags to allow us to watch for outside touches and also hide the menu and start 188 // manipulating the PIP in the same touch gesture 189 getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); 190 191 super.onCreate(savedInstanceState); 192 setContentView(R.layout.pip_menu_activity); 193 194 mAccessibilityManager = getSystemService(AccessibilityManager.class); 195 mBackgroundDrawable = new ColorDrawable(Color.BLACK); 196 mBackgroundDrawable.setAlpha(0); 197 mViewRoot = findViewById(R.id.background); 198 mViewRoot.setBackground(mBackgroundDrawable); 199 mMenuContainer = findViewById(R.id.menu_container); 200 mMenuContainer.setAlpha(0); 201 mSettingsButton = findViewById(R.id.settings); 202 mSettingsButton.setAlpha(0); 203 mSettingsButton.setOnClickListener((v) -> { 204 if (v.getAlpha() != 0) { 205 showSettings(); 206 } 207 }); 208 mDismissButton = findViewById(R.id.dismiss); 209 mDismissButton.setAlpha(0); 210 mDismissButton.setOnClickListener(v -> dismissPip()); 211 findViewById(R.id.expand_button).setOnClickListener(v -> { 212 if (mMenuContainer.getAlpha() != 0) { 213 expandPip(); 214 } 215 }); 216 mActionsGroup = findViewById(R.id.actions_group); 217 mBetweenActionPaddingLand = getResources().getDimensionPixelSize( 218 R.dimen.pip_between_action_padding_land); 219 mExpandButton = findViewById(R.id.expand_button); 220 221 updateFromIntent(getIntent()); 222 setTitle(R.string.pip_menu_title); 223 setDisablePreviewScreenshots(true); 224 } 225 226 @Override onNewIntent(Intent intent)227 protected void onNewIntent(Intent intent) { 228 super.onNewIntent(intent); 229 updateFromIntent(intent); 230 } 231 232 @Override onUserInteraction()233 public void onUserInteraction() { 234 if (mAllowMenuTimeout) { 235 repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); 236 } 237 } 238 239 @Override onUserLeaveHint()240 protected void onUserLeaveHint() { 241 super.onUserLeaveHint(); 242 243 // If another task is starting on top of the menu, then hide and finish it so that it can be 244 // recreated on the top next time it starts 245 hideMenu(); 246 } 247 248 @Override onStop()249 protected void onStop() { 250 super.onStop(); 251 252 cancelDelayedFinish(); 253 } 254 255 @Override onDestroy()256 protected void onDestroy() { 257 super.onDestroy(); 258 259 // Fallback, if we are destroyed for any other reason (like when the task is being reset), 260 // also reset the callback. 261 notifyActivityCallback(null); 262 } 263 264 @Override onPictureInPictureModeChanged(boolean isInPictureInPictureMode)265 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 266 if (!isInPictureInPictureMode) { 267 finish(); 268 } 269 } 270 271 @Override dispatchTouchEvent(MotionEvent ev)272 public boolean dispatchTouchEvent(MotionEvent ev) { 273 if (!mAllowTouches) { 274 return false; 275 } 276 277 // On the first action outside the window, hide the menu 278 switch (ev.getAction()) { 279 case MotionEvent.ACTION_OUTSIDE: 280 hideMenu(); 281 return true; 282 } 283 return super.dispatchTouchEvent(ev); 284 } 285 286 @Override finish()287 public void finish() { 288 notifyActivityCallback(null); 289 super.finish(); 290 // Hide without an animation (the menu should already be invisible at this point) 291 overridePendingTransition(0, 0); 292 } 293 294 @Override setTaskDescription(ActivityManager.TaskDescription taskDescription)295 public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { 296 // Do nothing 297 } 298 showMenu(int menuState, Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout, boolean resizeMenuOnShow)299 private void showMenu(int menuState, Rect stackBounds, Rect movementBounds, 300 boolean allowMenuTimeout, boolean resizeMenuOnShow) { 301 mAllowMenuTimeout = allowMenuTimeout; 302 if (mMenuState != menuState) { 303 // Disallow touches if the menu needs to resize while showing, and we are transitioning 304 // to/from a full menu state. 305 boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow && 306 (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL); 307 mAllowTouches = !disallowTouchesUntilAnimationEnd; 308 cancelDelayedFinish(); 309 updateActionViews(stackBounds); 310 if (mMenuContainerAnimator != null) { 311 mMenuContainerAnimator.cancel(); 312 } 313 notifyMenuStateChange(menuState); 314 mMenuContainerAnimator = new AnimatorSet(); 315 ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, 316 mMenuContainer.getAlpha(), 1f); 317 menuAnim.addUpdateListener(mMenuBgUpdateListener); 318 ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 319 mSettingsButton.getAlpha(), 1f); 320 ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, 321 mDismissButton.getAlpha(), 1f); 322 if (menuState == MENU_STATE_FULL) { 323 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); 324 } else { 325 mMenuContainerAnimator.playTogether(dismissAnim); 326 } 327 mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); 328 mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); 329 if (allowMenuTimeout) { 330 mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { 331 @Override 332 public void onAnimationEnd(Animator animation) { 333 repostDelayedFinish(INITIAL_DISMISS_DELAY); 334 } 335 }); 336 } 337 mMenuContainerAnimator.start(); 338 } else { 339 // If we are already visible, then just start the delayed dismiss and unregister any 340 // existing input consumers from the previous drag 341 if (allowMenuTimeout) { 342 repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); 343 } 344 } 345 } 346 hideMenu()347 private void hideMenu() { 348 hideMenu(null); 349 } 350 hideMenu(Runnable animationEndCallback)351 private void hideMenu(Runnable animationEndCallback) { 352 hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false /* isDismissing */); 353 } 354 hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, boolean isDismissing)355 private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, 356 boolean isDismissing) { 357 if (mMenuState != MENU_STATE_NONE) { 358 cancelDelayedFinish(); 359 if (notifyMenuVisibility) { 360 notifyMenuStateChange(MENU_STATE_NONE); 361 } 362 mMenuContainerAnimator = new AnimatorSet(); 363 ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, 364 mMenuContainer.getAlpha(), 0f); 365 menuAnim.addUpdateListener(mMenuBgUpdateListener); 366 ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 367 mSettingsButton.getAlpha(), 0f); 368 ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, 369 mDismissButton.getAlpha(), 0f); 370 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); 371 mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); 372 mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); 373 mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { 374 @Override 375 public void onAnimationEnd(Animator animation) { 376 if (animationFinishedRunnable != null) { 377 animationFinishedRunnable.run(); 378 } 379 380 if (!isDismissing) { 381 // If we are dismissing the PiP, then don't try to pre-emptively finish the 382 // menu activity 383 finish(); 384 } 385 } 386 }); 387 mMenuContainerAnimator.start(); 388 } else { 389 // If the menu is not visible, just finish now 390 finish(); 391 } 392 } 393 updateFromIntent(Intent intent)394 private void updateFromIntent(Intent intent) { 395 mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER); 396 if (mToControllerMessenger == null) { 397 Log.w(TAG, "Controller messenger is null. Stopping."); 398 finish(); 399 return; 400 } 401 notifyActivityCallback(mMessenger); 402 403 ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS); 404 if (actions != null) { 405 mActions.clear(); 406 mActions.addAll(actions.getList()); 407 } 408 409 final int menuState = intent.getIntExtra(EXTRA_MENU_STATE, MENU_STATE_NONE); 410 if (menuState != MENU_STATE_NONE) { 411 Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS); 412 Rect movementBounds = intent.getParcelableExtra(EXTRA_MOVEMENT_BOUNDS); 413 boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true); 414 boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false); 415 showMenu(menuState, stackBounds, movementBounds, allowMenuTimeout, willResizeMenu); 416 } 417 } 418 setActions(Rect stackBounds, List<RemoteAction> actions)419 private void setActions(Rect stackBounds, List<RemoteAction> actions) { 420 mActions.clear(); 421 mActions.addAll(actions); 422 updateActionViews(stackBounds); 423 } 424 updateActionViews(Rect stackBounds)425 private void updateActionViews(Rect stackBounds) { 426 ViewGroup expandContainer = findViewById(R.id.expand_container); 427 ViewGroup actionsContainer = findViewById(R.id.actions_container); 428 actionsContainer.setOnTouchListener((v, ev) -> { 429 // Do nothing, prevent click through to parent 430 return true; 431 }); 432 433 if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) { 434 actionsContainer.setVisibility(View.INVISIBLE); 435 } else { 436 actionsContainer.setVisibility(View.VISIBLE); 437 if (mActionsGroup != null) { 438 // Ensure we have as many buttons as actions 439 final LayoutInflater inflater = LayoutInflater.from(this); 440 while (mActionsGroup.getChildCount() < mActions.size()) { 441 final ImageView actionView = (ImageView) inflater.inflate( 442 R.layout.pip_menu_action, mActionsGroup, false); 443 mActionsGroup.addView(actionView); 444 } 445 446 // Update the visibility of all views 447 for (int i = 0; i < mActionsGroup.getChildCount(); i++) { 448 mActionsGroup.getChildAt(i).setVisibility(i < mActions.size() 449 ? View.VISIBLE 450 : View.GONE); 451 } 452 453 // Recreate the layout 454 final boolean isLandscapePip = stackBounds != null && 455 (stackBounds.width() > stackBounds.height()); 456 for (int i = 0; i < mActions.size(); i++) { 457 final RemoteAction action = mActions.get(i); 458 final ImageView actionView = (ImageView) mActionsGroup.getChildAt(i); 459 460 // TODO: Check if the action drawable has changed before we reload it 461 action.getIcon().loadDrawableAsync(this, d -> { 462 d.setTint(Color.WHITE); 463 actionView.setImageDrawable(d); 464 }, mHandler); 465 actionView.setContentDescription(action.getContentDescription()); 466 if (action.isEnabled()) { 467 actionView.setOnClickListener(v -> { 468 mHandler.post(() -> { 469 try { 470 action.getActionIntent().send(); 471 } catch (CanceledException e) { 472 Log.w(TAG, "Failed to send action", e); 473 } 474 }); 475 }); 476 } 477 actionView.setEnabled(action.isEnabled()); 478 actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); 479 480 // Update the margin between actions 481 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) 482 actionView.getLayoutParams(); 483 lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0; 484 } 485 } 486 487 // Update the expand container margin to adjust the center of the expand button to 488 // account for the existence of the action container 489 FrameLayout.LayoutParams expandedLp = 490 (FrameLayout.LayoutParams) expandContainer.getLayoutParams(); 491 expandedLp.topMargin = getResources().getDimensionPixelSize( 492 R.dimen.pip_action_padding); 493 expandedLp.bottomMargin = getResources().getDimensionPixelSize( 494 R.dimen.pip_expand_container_edge_margin); 495 expandContainer.requestLayout(); 496 } 497 } 498 updateDismissFraction(float fraction)499 private void updateDismissFraction(float fraction) { 500 int alpha; 501 final float menuAlpha = 1 - fraction; 502 if (mMenuState == MENU_STATE_FULL) { 503 mMenuContainer.setAlpha(menuAlpha); 504 mSettingsButton.setAlpha(menuAlpha); 505 mDismissButton.setAlpha(menuAlpha); 506 final float interpolatedAlpha = 507 MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction; 508 alpha = (int) (interpolatedAlpha * 255); 509 } else { 510 if (mMenuState == MENU_STATE_CLOSE) { 511 mDismissButton.setAlpha(menuAlpha); 512 } 513 alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255); 514 } 515 mBackgroundDrawable.setAlpha(alpha); 516 } 517 notifyMenuStateChange(int menuState)518 private void notifyMenuStateChange(int menuState) { 519 mMenuState = menuState; 520 Message m = Message.obtain(); 521 m.what = PipMenuActivityController.MESSAGE_MENU_STATE_CHANGED; 522 m.arg1 = menuState; 523 sendMessage(m, "Could not notify controller of PIP menu visibility"); 524 } 525 expandPip()526 private void expandPip() { 527 // Do not notify menu visibility when hiding the menu, the controller will do this when it 528 // handles the message 529 hideMenu(() -> { 530 sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP, 531 "Could not notify controller to expand PIP"); 532 }, false /* notifyMenuVisibility */, false /* isDismissing */); 533 } 534 dismissPip()535 private void dismissPip() { 536 // Do not notify menu visibility when hiding the menu, the controller will do this when it 537 // handles the message 538 hideMenu(() -> { 539 sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP, 540 "Could not notify controller to dismiss PIP"); 541 }, false /* notifyMenuVisibility */, true /* isDismissing */); 542 } 543 showSettings()544 private void showSettings() { 545 final Pair<ComponentName, Integer> topPipActivityInfo = 546 PipUtils.getTopPinnedActivity(this, ActivityManager.getService()); 547 if (topPipActivityInfo.first != null) { 548 final UserHandle user = UserHandle.of(topPipActivityInfo.second); 549 final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, 550 Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); 551 settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user); 552 settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); 553 startActivity(settingsIntent); 554 } 555 } 556 notifyActivityCallback(Messenger callback)557 private void notifyActivityCallback(Messenger callback) { 558 Message m = Message.obtain(); 559 m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK; 560 m.replyTo = callback; 561 sendMessage(m, "Could not notify controller of activity finished"); 562 } 563 sendEmptyMessage(int what, String errorMsg)564 private void sendEmptyMessage(int what, String errorMsg) { 565 Message m = Message.obtain(); 566 m.what = what; 567 sendMessage(m, errorMsg); 568 } 569 sendMessage(Message m, String errorMsg)570 private void sendMessage(Message m, String errorMsg) { 571 if (mToControllerMessenger == null) { 572 return; 573 } 574 try { 575 mToControllerMessenger.send(m); 576 } catch (RemoteException e) { 577 Log.e(TAG, errorMsg, e); 578 } 579 } 580 cancelDelayedFinish()581 private void cancelDelayedFinish() { 582 mHandler.removeCallbacks(mFinishRunnable); 583 } 584 repostDelayedFinish(int delay)585 private void repostDelayedFinish(int delay) { 586 int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay, 587 FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS); 588 mHandler.removeCallbacks(mFinishRunnable); 589 mHandler.postDelayed(mFinishRunnable, recommendedTimeout); 590 } 591 } 592