diff options
8 files changed, 298 insertions, 249 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 79d2a8102358..76226888bf1a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4801,7 +4801,6 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.time, View.GONE); contentView.setImageViewIcon(R.id.profile_badge, null); contentView.setViewVisibility(R.id.profile_badge, View.GONE); - contentView.setViewVisibility(R.id.alerted_icon, View.GONE); mN.mUsesStandardHeader = false; } diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml index 3615b9e2f9cb..df271f0f2942 100644 --- a/core/res/res/layout/notification_material_action_list.xml +++ b/core/res/res/layout/notification_material_action_list.xml @@ -25,6 +25,7 @@ android:id="@+id/actions_container_layout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:gravity="end" android:orientation="horizontal" android:paddingEnd="@dimen/bubble_gone_padding_end" > diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml index 1782d2536035..5aab0a172b99 100644 --- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml +++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml @@ -29,13 +29,12 @@ android:orientation="horizontal" app:layout_constraintGuide_begin="@dimen/headsup_scrim_height"/> - <!-- Include a FocusParkingView at the beginning or end. The rotary controller "parks" the - focus here when the user navigates to another window. This is also used to prevent - wrap-around which is why it must be first or last in Tab order. --> + <!-- Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here + when the user navigates to another window. This is also used to prevent wrap-around. --> <com.android.car.ui.FocusParkingView android:layout_width="wrap_content" android:layout_height="wrap_content" - app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> <View diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java index d60bc418ece2..adf8d4d5acf8 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java @@ -148,10 +148,9 @@ public class NavigationBarViewFactory { CarNavigationBarView view = (CarNavigationBarView) View.inflate(mContext, barLayout, /* root= */ null); - // Include a FocusParkingView at the end. The rotary controller "parks" the focus here when - // the user navigates to another window. This is also used to prevent wrap-around which is - // why it must be first or last in Tab order. - view.addView(new FocusParkingView(mContext)); + // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here + // when the user navigates to another window. This is also used to prevent wrap-around. + view.addView(new FocusParkingView(mContext), 0); mCachedViewMap.put(type, view); return mCachedViewMap.get(type); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index d6e1a16bc69e..85444303490d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -214,9 +214,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private Animator mScreenshotAnimation; private Runnable mOnCompleteRunnable; private Animator mDismissAnimation; - private boolean mInDarkMode = false; - private boolean mDirectionLTR = true; - private boolean mOrientationPortrait = true; + private boolean mInDarkMode; + private boolean mDirectionLTR; + private boolean mOrientationPortrait; private float mCornerSizeX; private float mDismissDeltaY; @@ -245,9 +245,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } }; - /** - * @param context everything needs a context :( - */ @Inject public GlobalScreenshot( Context context, @Main Resources resources, @@ -320,6 +317,104 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset inoutInfo.touchableRegion.set(touchRegion); } + void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) { + mOnCompleteRunnable = onComplete; + + mDisplay.getRealMetrics(mDisplayMetrics); + takeScreenshotInternal( + finisher, + new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + } + + void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, + Insets visibleInsets, int taskId, int userId, ComponentName topComponent, + Consumer<Uri> finisher, Runnable onComplete) { + // TODO: use task Id, userId, topComponent for smart handler + + mOnCompleteRunnable = onComplete; + if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { + saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); + } else { + saveScreenshot(screenshot, finisher, + new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, + true); + } + } + + /** + * Displays a screenshot selector + */ + @SuppressLint("ClickableViewAccessibility") + void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { + dismissScreenshot("new screenshot requested", true); + mOnCompleteRunnable = onComplete; + + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mScreenshotSelectorView.setOnTouchListener((v, event) -> { + ScreenshotSelectorView view = (ScreenshotSelectorView) v; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + view.startSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_MOVE: + view.updateSelection((int) event.getX(), (int) event.getY()); + return true; + case MotionEvent.ACTION_UP: + view.setVisibility(View.GONE); + mWindowManager.removeView(mScreenshotLayout); + final Rect rect = view.getSelectionRect(); + if (rect != null) { + if (rect.width() != 0 && rect.height() != 0) { + // Need mScreenshotLayout to handle it after the view disappears + mScreenshotLayout.post(() -> takeScreenshotInternal(finisher, rect)); + } + } + + view.stopSelection(); + return true; + } + + return false; + }); + mScreenshotLayout.post(() -> { + mScreenshotSelectorView.setVisibility(View.VISIBLE); + mScreenshotSelectorView.requestFocus(); + }); + } + + /** + * Cancels screenshot request + */ + void stopScreenshot() { + // If the selector layer still presents on screen, we remove it and resets its state. + if (mScreenshotSelectorView.getSelectionRect() != null) { + mWindowManager.removeView(mScreenshotLayout); + mScreenshotSelectorView.stopSelection(); + } + } + + /** + * Clears current screenshot + */ + void dismissScreenshot(String reason, boolean immediate) { + Log.v(TAG, "clearing screenshot: " + reason); + mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); + mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + if (!immediate) { + mDismissAnimation = createScreenshotDismissAnimation(); + mDismissAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + clearScreenshot(); + } + }); + mDismissAnimation.start(); + } else { + clearScreenshot(); + } + } + private void onConfigChanged(Configuration newConfig) { boolean needsUpdate = false; // dark mode @@ -408,15 +503,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } return mScreenshotLayout.onApplyWindowInsets(insets); }); - mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - dismissScreenshot("back pressed", true); - return true; - } - return false; + mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> { + if (keyCode == KeyEvent.KEYCODE_BACK) { + dismissScreenshot("back pressed", true); + return true; } + return false; }); // Get focus so that the key events go to the layout. mScreenshotLayout.setFocusableInTouchMode(true); @@ -471,59 +563,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } /** - * Updates the window focusability. If the window is already showing, then it updates the - * window immediately, otherwise the layout params will be applied when the window is next - * shown. - */ - private void setWindowFocusable(boolean focusable) { - if (focusable) { - mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; - } else { - mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; - } - if (mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); - } - } - - /** - * Creates a new worker thread and saves the screenshot to the media store. - */ - private void saveScreenshotInWorkerThread( - Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { - SaveImageInBackgroundData data = new SaveImageInBackgroundData(); - data.image = mScreenBitmap; - data.finisher = finisher; - data.mActionsReadyListener = actionsReadyListener; - - if (mSaveInBgTask != null) { - // just log success/failure for the pre-existing screenshot - mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { - @Override - void onActionsReady(SavedImageData imageData) { - logSuccessOnActionsReady(imageData); - } - }); - } - - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); - mSaveInBgTask.execute(); - } - - /** * Takes a screenshot of the current display and shows an animation. */ - private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { + private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) { // copy the input Rect, since SurfaceControl.screenshot can mutate it Rect screenRect = new Rect(crop); int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); - takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, + saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, Insets.NONE, true); } - private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, + private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); @@ -561,85 +613,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset startAnimation(finisher, screenRect, screenInsets, showFlash); } - void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) { - mOnCompleteRunnable = onComplete; - - mDisplay.getRealMetrics(mDisplayMetrics); - takeScreenshot( - finisher, - new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); - } - - void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, int userId, ComponentName topComponent, - Consumer<Uri> finisher, Runnable onComplete) { - // TODO: use task Id, userId, topComponent for smart handler - - mOnCompleteRunnable = onComplete; - if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) { - takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false); - } else { - takeScreenshot(screenshot, finisher, - new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE, - true); - } - } - - /** - * Displays a screenshot selector - */ - @SuppressLint("ClickableViewAccessibility") - void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { - dismissScreenshot("new screenshot requested", true); - mOnCompleteRunnable = onComplete; - - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - ScreenshotSelectorView view = (ScreenshotSelectorView) v; - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - view.startSelection((int) event.getX(), (int) event.getY()); - return true; - case MotionEvent.ACTION_MOVE: - view.updateSelection((int) event.getX(), (int) event.getY()); - return true; - case MotionEvent.ACTION_UP: - view.setVisibility(View.GONE); - mWindowManager.removeView(mScreenshotLayout); - final Rect rect = view.getSelectionRect(); - if (rect != null) { - if (rect.width() != 0 && rect.height() != 0) { - // Need mScreenshotLayout to handle it after the view disappears - mScreenshotLayout.post(() -> takeScreenshot(finisher, rect)); - } - } - - view.stopSelection(); - return true; - } - - return false; - } - }); - mScreenshotLayout.post(() -> { - mScreenshotSelectorView.setVisibility(View.VISIBLE); - mScreenshotSelectorView.requestFocus(); - }); - } - - /** - * Cancels screenshot request - */ - void stopScreenshot() { - // If the selector layer still presents on screen, we remove it and resets its state. - if (mScreenshotSelectorView.getSelectionRect() != null) { - mWindowManager.removeView(mScreenshotLayout); - mScreenshotSelectorView.stopSelection(); - } - } - /** * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on * failure). @@ -670,55 +643,78 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - private boolean isUserSetupComplete() { - return Settings.Secure.getInt(mContext.getContentResolver(), - SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + /** + * Starts the animation after taking the screenshot + */ + private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, + boolean showFlash) { + + // If power save is on, show a toast so there is some visual indication that a + // screenshot has been taken. + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + if (powerManager.isPowerSaveMode()) { + Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); + } + + mScreenshotHandler.post(() -> { + if (!mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + } + mScreenshotAnimatedView.setImageDrawable( + createScreenDrawable(mScreenBitmap, screenInsets)); + setAnimatedViewSize(screenRect.width(), screenRect.height()); + // Show when the animation starts + mScreenshotAnimatedView.setVisibility(View.GONE); + + mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); + // make static preview invisible (from gone) so we can query its location on screen + mScreenshotPreview.setVisibility(View.INVISIBLE); + + mScreenshotHandler.post(() -> { + mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + + mScreenshotAnimation = + createScreenshotDropInAnimation(screenRect, showFlash); + + saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { + @Override + void onActionsReady(SavedImageData imageData) { + showUiOnActionsReady(imageData); + } + }); + + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + + mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mScreenshotPreview.buildLayer(); + mScreenshotAnimation.start(); + }); + }); } /** - * Clears current screenshot + * Creates a new worker thread and saves the screenshot to the media store. */ - void dismissScreenshot(String reason, boolean immediate) { - Log.v(TAG, "clearing screenshot: " + reason); - mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); - mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - if (!immediate) { - mDismissAnimation = createScreenshotDismissAnimation(); - mDismissAnimation.addListener(new AnimatorListenerAdapter() { + private void saveScreenshotInWorkerThread( + Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { + SaveImageInBackgroundData data = new SaveImageInBackgroundData(); + data.image = mScreenBitmap; + data.finisher = finisher; + data.mActionsReadyListener = actionsReadyListener; + + if (mSaveInBgTask != null) { + // just log success/failure for the pre-existing screenshot + mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - clearScreenshot(); + void onActionsReady(SavedImageData imageData) { + logSuccessOnActionsReady(imageData); } }); - mDismissAnimation.start(); - } else { - clearScreenshot(); } - } - private void clearScreenshot() { - if (mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.removeView(mScreenshotLayout); - } - - // Clear any references to the bitmap - mScreenshotPreview.setImageDrawable(null); - mScreenshotAnimatedView.setImageDrawable(null); - mScreenshotAnimatedView.setVisibility(View.GONE); - mActionsContainerBackground.setVisibility(View.GONE); - mActionsContainer.setVisibility(View.GONE); - mBackgroundProtection.setAlpha(0f); - mDismissButton.setVisibility(View.GONE); - mScreenshotPreview.setVisibility(View.GONE); - mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); - mScreenshotPreview.setContentDescription( - mContext.getResources().getString(R.string.screenshot_preview_description)); - mScreenshotLayout.setAlpha(1); - mDismissButton.setTranslationY(0); - mActionsContainer.setTranslationY(0); - mActionsContainerBackground.setTranslationY(0); - mScreenshotPreview.setTranslationY(0); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); + mSaveInBgTask.execute(); } /** @@ -768,56 +764,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } - /** - * Starts the animation after taking the screenshot - */ - private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, - boolean showFlash) { - - // If power save is on, show a toast so there is some visual indication that a - // screenshot has been taken. - PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - if (powerManager.isPowerSaveMode()) { - Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); - } - - mScreenshotHandler.post(() -> { - if (!mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - } - mScreenshotAnimatedView.setImageDrawable( - createScreenDrawable(mScreenBitmap, screenInsets)); - setAnimatedViewSize(screenRect.width(), screenRect.height()); - // Show when the animation starts - mScreenshotAnimatedView.setVisibility(View.GONE); - - mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); - // make static preview invisible (from gone) so we can query its location on screen - mScreenshotPreview.setVisibility(View.INVISIBLE); - - mScreenshotHandler.post(() -> { - mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - - mScreenshotAnimation = - createScreenshotDropInAnimation(screenRect, showFlash); - - saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { - @Override - void onActionsReady(SavedImageData imageData) { - showUiOnActionsReady(imageData); - } - }); - - // Play the shutter sound to notify that we've taken a screenshot - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - - mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mScreenshotPreview.buildLayer(); - mScreenshotAnimation.start(); - }); - }); - } - private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); @@ -1070,6 +1016,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return animSet; } + private void clearScreenshot() { + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.removeView(mScreenshotLayout); + } + + // Clear any references to the bitmap + mScreenshotPreview.setImageDrawable(null); + mScreenshotAnimatedView.setImageDrawable(null); + mScreenshotAnimatedView.setVisibility(View.GONE); + mActionsContainerBackground.setVisibility(View.GONE); + mActionsContainer.setVisibility(View.GONE); + mBackgroundProtection.setAlpha(0f); + mDismissButton.setVisibility(View.GONE); + mScreenshotPreview.setVisibility(View.GONE); + mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); + mScreenshotPreview.setContentDescription( + mContext.getResources().getString(R.string.screenshot_preview_description)); + mScreenshotLayout.setAlpha(1); + mDismissButton.setTranslationY(0); + mActionsContainer.setTranslationY(0); + mActionsContainerBackground.setTranslationY(0); + mScreenshotPreview.setTranslationY(0); + } + private void setAnimatedViewSize(int width, int height) { ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams(); layoutParams.width = width; @@ -1077,6 +1047,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotAnimatedView.setLayoutParams(layoutParams); } + /** + * Updates the window focusability. If the window is already showing, then it updates the + * window immediately, otherwise the layout params will be applied when the window is next + * shown. + */ + private void setWindowFocusable(boolean focusable) { + if (focusable) { + mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; + } else { + mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; + } + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); + } + } + + private boolean isUserSetupComplete() { + return Settings.Secure.getInt(mContext.getContentResolver(), + SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) { int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; @@ -1128,7 +1119,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) { // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need // to fill in the background of the drawable. - return new LayerDrawable(new Drawable[] { + return new LayerDrawable(new Drawable[]{ new ColorDrawable(Color.BLACK), insetDrawable}); } else { return insetDrawable; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 9f8a9bb4a432..6f143da5405a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -102,7 +102,7 @@ public class TakeScreenshotService extends Service { switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: - mScreenshot.takeScreenshot(uriConsumer, onComplete); + mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index 5e10916c4491..a4e58a1faeac 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -649,16 +649,33 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mReporter.onStartPackageBackup(PM_PACKAGE); mCurrentPackage = new PackageInfo(); mCurrentPackage.packageName = PM_PACKAGE; - try { - extractPmAgentData(mCurrentPackage); + // If we can't even extractPmAgentData(), then we treat the local state as + // compromised, just in case. This means that we will clear data and will + // start from a clean slate in the next attempt. It's not clear whether that's + // the right thing to do, but matches what we have historically done. + try { + extractPmAgentData(mCurrentPackage); + } catch (TaskException e) { + throw TaskException.stateCompromised(e); // force stateCompromised + } + // During sendDataToTransport, we generally trust any thrown TaskException + // about whether stateCompromised because those are likely transient; + // clearing state for those would have the potential to lead to cascading + // failures, as discussed in http://b/144030477. + // For specific status codes (e.g. TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED), + // cleanUpAgentForTransportStatus() or theoretically handleTransportStatus() + // still have the opportunity to perform additional clean-up tasks. int status = sendDataToTransport(mCurrentPackage); cleanUpAgentForTransportStatus(status); } catch (AgentException | TaskException e) { mReporter.onExtractPmAgentDataError(e); cleanUpAgentForError(e); - // PM agent failure is task failure. - throw TaskException.stateCompromised(e); + if (e instanceof TaskException) { + throw (TaskException) e; + } else { + throw TaskException.stateCompromised(e); // PM agent failure is task failure. + } } } diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index ec56e1ebc8e0..b5c9375fcc0d 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -122,6 +122,7 @@ import com.android.server.testing.shadows.ShadowBackupDataOutput; import com.android.server.testing.shadows.ShadowEventLog; import com.android.server.testing.shadows.ShadowSystemServiceRegistry; +import com.google.common.base.Charsets; import com.google.common.truth.IterableSubject; import org.junit.After; @@ -1910,7 +1911,8 @@ public class KeyValueBackupTaskTest { } @Test - public void testRunTask_whenTransportReturnsError_updatesFilesAndCleansUp() throws Exception { + public void testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp() + throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); when(transportMock.transport.performBackup( argThat(packageInfo(PACKAGE_1)), any(), anyInt())) @@ -1926,6 +1928,39 @@ public class KeyValueBackupTaskTest { assertCleansUpFilesAndAgent(mTransport, PACKAGE_1); } + /** + * Checks that TRANSPORT_ERROR during @pm@ backup keeps the state file untouched. + * http://b/144030477 + */ + @Test + public void testRunTask_whenTransportReturnsErrorForPm_updatesFilesAndCleansUp() + throws Exception { + // TODO(tobiast): Refactor this method to share code with + // testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp + // See patchset 7 of http://ag/11762961 + final PackageData packageData = PM_PACKAGE; + TransportMock transportMock = setUpInitializedTransport(mTransport); + when(transportMock.transport.performBackup( + argThat(packageInfo(packageData)), any(), anyInt())) + .thenReturn(BackupTransport.TRANSPORT_ERROR); + + byte[] pmStateBytes = "fake @pm@ state for testing".getBytes(Charsets.UTF_8); + + Path pmStatePath = createPmStateFile(pmStateBytes.clone()); + PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData); + runTask(task); + verify(pmAgent, never()).onBackup(any(), any(), any()); + + assertThat(Files.readAllBytes(pmStatePath)).isEqualTo(pmStateBytes.clone()); + + boolean existed = deletePmStateFile(); + assertThat(existed).isTrue(); + // unbindAgent() is skipped for @pm@. Comment in KeyValueBackupTask.java: + // "For PM metadata (for which applicationInfo is null) there is no agent-bound state." + assertCleansUpFiles(mTransport, packageData); + } + @Test public void testRunTask_whenTransportGetBackupQuotaThrowsForPm() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); @@ -2707,21 +2742,29 @@ public class KeyValueBackupTaskTest { * </ul> * </ul> */ - private void createPmStateFile() throws IOException { - createPmStateFile(mTransport); + private Path createPmStateFile() throws IOException { + return createPmStateFile("pmState".getBytes()); + } + + private Path createPmStateFile(byte[] bytes) throws IOException { + return createPmStateFile(bytes, mTransport); + } + + private Path createPmStateFile(TransportData transport) throws IOException { + return createPmStateFile("pmState".getBytes(), mTransport); } - /** @see #createPmStateFile() */ - private void createPmStateFile(TransportData transport) throws IOException { - Files.write(getStateFile(transport, PM_PACKAGE), "pmState".getBytes()); + /** @see #createPmStateFile(byte[]) */ + private Path createPmStateFile(byte[] bytes, TransportData transport) throws IOException { + return Files.write(getStateFile(transport, PM_PACKAGE), bytes); } /** * Forces transport initialization and call to {@link * UserBackupManagerService#resetBackupState(File)} */ - private void deletePmStateFile() throws IOException { - Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE)); + private boolean deletePmStateFile() throws IOException { + return Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE)); } /** |