summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/safetycenter
diff options
context:
space:
mode:
authorPresubmit Automerger Backend <android-build-presubmit-automerger-backend@system.gserviceaccount.com>2023-03-06 12:12:32 +0000
committerYuri Ufimtsev <yufimtsev@google.com>2023-03-06 17:03:35 +0000
commit145b01a0d967938b11a90025fe1aa3a434956eba (patch)
treeedbcbc1c90735a845f86e2f3670032a973f00776 /PermissionController/src/com/android/permissioncontroller/safetycenter
parent6c6bbf772ac0e14155dafe93690000484f28c653 (diff)
parentb8bf87e868322bbcb121bf7fe0d4f30bbda4ffac (diff)
downloadPermission-145b01a0d967938b11a90025fe1aa3a434956eba.tar.gz
Fix the Safety Center Status showing logic to cover edge cases 2p: b8bf87e868
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Permission/+/20980501 Bug: 261160581 Change-Id: Ia792283b6e2fdbd438caf38a43bc6a7d5064980f
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/safetycenter')
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusAnimationSequencer.kt168
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java161
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt24
3 files changed, 275 insertions, 78 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusAnimationSequencer.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusAnimationSequencer.kt
new file mode 100644
index 000000000..c48ad734b
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusAnimationSequencer.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.ui
+
+import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
+
+/**
+ * Controls the animation flow and hold all the data necessary to determine the appearance of Status
+ * icon of [SafetyStatusPreference]. For each lifecycle event (such as [onUpdateReceived],
+ * [onStartScanningAnimationStart], [onStartScanningAnimationEnd], etc.) it changes its internal
+ * state and may provide a presentation instruction in the form of [Action].
+ */
+internal class SafetyStatusAnimationSequencer {
+
+ private var isIconChangeAnimationRunning: Boolean = false
+ private var isScanAnimationRunning: Boolean = false
+ private var shouldStartScanAnimation: Boolean = false
+ private var queuedIconChangeAnimationSeverityLevel: Int? = null
+ /**
+ * Stores the last known Severity Level that user could observe as a static status image, as
+ * scan animation, or as the beginning state of a changing status animation.
+ */
+ private var currentlyVisibleSeverityLevel: Int = OVERALL_SEVERITY_LEVEL_UNKNOWN
+
+ fun getCurrentlyVisibleSeverityLevel(): Int {
+ return currentlyVisibleSeverityLevel
+ }
+
+ fun onUpdateReceived(isRefreshInProgress: Boolean, severityLevel: Int): Action? {
+ if (isRefreshInProgress) {
+ if (isIconChangeAnimationRunning) {
+ shouldStartScanAnimation = true
+ return null
+ } else if (!isScanAnimationRunning) {
+ currentlyVisibleSeverityLevel = severityLevel
+ return Action.START_SCANNING_ANIMATION
+ }
+ // isRefreshInProgress && isScanAnimationRunning && !isIconChangeAnimationRunning
+ // Next action needs to wait for onStartScanningAnimationEnd or
+ // onContinueScanningAnimationEnd not to break currently running animation.
+ return null
+ } else {
+ val isDifferentSeverityQueued =
+ queuedIconChangeAnimationSeverityLevel != null &&
+ queuedIconChangeAnimationSeverityLevel != severityLevel
+ val shouldChangeIcon =
+ currentlyVisibleSeverityLevel != severityLevel || isDifferentSeverityQueued
+
+ if (isIconChangeAnimationRunning || shouldChangeIcon && isScanAnimationRunning) {
+ queuedIconChangeAnimationSeverityLevel = severityLevel
+ }
+ if (isScanAnimationRunning) {
+ return Action.FINISH_SCANNING_ANIMATION
+ } else if (shouldChangeIcon && !isIconChangeAnimationRunning) {
+ return Action.START_ICON_CHANGE_ANIMATION
+ } else if (!isIconChangeAnimationRunning) {
+ // Possible if status was finalized by Safety Center at the beginning,
+ // when no scanning animation is launched and refresh is not in progress.
+ // In this case we need to show the final icon straigt away without any animations.
+ return Action.CHANGE_ICON_WITHOUT_ANIMATION
+ }
+ // !isRefreshInProgress && !isScanAnimationRunning && isIconChangeAnimationRunning
+ // Next action needs to wait for onIconChangeAnimationEnd not to break currently
+ // running animation.
+ return null
+ }
+ }
+
+ fun onStartScanningAnimationStart() {
+ isScanAnimationRunning = true
+ }
+
+ fun onStartScanningAnimationEnd(): Action {
+ return Action.CONTINUE_SCANNING_ANIMATION
+ }
+
+ fun onContinueScanningAnimationEnd(isRefreshInProgress: Boolean, severityLevel: Int): Action? {
+ if (isRefreshInProgress) {
+ if (currentlyVisibleSeverityLevel != severityLevel) {
+ // onUpdateReceived does not handle this case since we should not break
+ // the animation while it is running. Once current scan cycle is finished, this
+ // call will return the request to restart animation with updated severity level.
+ currentlyVisibleSeverityLevel = severityLevel
+ return Action.RESET_SCANNING_ANIMATION
+ } else {
+ return Action.CONTINUE_SCANNING_ANIMATION
+ }
+ } else {
+ // Possible if scanning animation has been ended right after status is updated with
+ // final data, but before we got the onUpdateReceived call (that is posted to the
+ // message queue and will happen soon), so no need to do anything right now.
+ return null
+ }
+ }
+
+ fun onFinishScanAnimationEnd(isRefreshing: Boolean, severityLevel: Int): Action {
+ isScanAnimationRunning = false
+ currentlyVisibleSeverityLevel = severityLevel
+ return handleQueuedAction(isRefreshing, severityLevel)
+ }
+
+ fun onCouldNotStartIconChangeAnimation(isRefreshing: Boolean, severityLevel: Int): Action {
+ return handleQueuedAction(isRefreshing, severityLevel)
+ }
+
+ fun onIconChangeAnimationStart() {
+ isIconChangeAnimationRunning = true
+ }
+
+ fun onIconChangeAnimationEnd(isRefreshing: Boolean, severityLevel: Int): Action {
+ isIconChangeAnimationRunning = false
+ currentlyVisibleSeverityLevel = severityLevel
+ return handleQueuedAction(isRefreshing, severityLevel)
+ }
+
+ private fun handleQueuedAction(isRefreshing: Boolean, severityLevel: Int): Action {
+ if (shouldStartScanAnimation) {
+ shouldStartScanAnimation = false
+ if (isRefreshing) {
+ return Action.START_SCANNING_ANIMATION
+ } else {
+ return handleQueuedAction(isRefreshing, severityLevel)
+ }
+ } else if (queuedIconChangeAnimationSeverityLevel != null) {
+ val queuedSeverityLevel = queuedIconChangeAnimationSeverityLevel
+ queuedIconChangeAnimationSeverityLevel = null
+ if (currentlyVisibleSeverityLevel != queuedSeverityLevel) {
+ return Action.START_ICON_CHANGE_ANIMATION
+ } else {
+ return handleQueuedAction(isRefreshing, severityLevel)
+ }
+ }
+ currentlyVisibleSeverityLevel = severityLevel
+ return Action.CHANGE_ICON_WITHOUT_ANIMATION
+ }
+
+ /** Set of instructions of what should Status icon currently show. */
+ enum class Action {
+ START_SCANNING_ANIMATION,
+ /**
+ * Requests to continue the scanning animation with the same Severity Level as stored in
+ * [currentlyVisibleSeverityLevel].
+ */
+ CONTINUE_SCANNING_ANIMATION,
+ /**
+ * Requests to start scanning animation from the beginning when
+ * [currentlyVisibleSeverityLevel] has been changed.
+ */
+ RESET_SCANNING_ANIMATION,
+ FINISH_SCANNING_ANIMATION,
+ START_ICON_CHANGE_ANIMATION,
+ CHANGE_ICON_WITHOUT_ANIMATION
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
index 1f6c15183..6c1c7aa90 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
@@ -17,7 +17,6 @@
package com.android.permissioncontroller.safetycenter.ui;
import static android.os.Build.VERSION_CODES.TIRAMISU;
-import static android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
import android.content.Context;
import android.graphics.drawable.Animatable2;
@@ -70,12 +69,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
setLayoutResource(R.layout.preference_safety_status);
}
- private boolean mIsScanAnimationRunning;
- private boolean mIsIconChangeAnimationRunning;
private boolean mIsTextChangeAnimationRunning;
- private int mQueuedScanAnimationSeverityLevel;
- private int mQueuedIconAnimationSeverityLevel;
- private int mSettledSeverityLevel = OVERALL_SEVERITY_LEVEL_UNKNOWN;
+ private final SafetyStatusAnimationSequencer mSequencer = new SafetyStatusAnimationSequencer();
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
@@ -162,29 +157,13 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
private void updateStatusIcon(ImageView statusImage, View rescanButton) {
int severityLevel = mStatus.getSeverityLevel();
-
boolean isRefreshing = mStatus.isRefreshInProgress();
- boolean shouldStartScanAnimation = isRefreshing && !mIsScanAnimationRunning;
- boolean shouldEndScanAnimation = !isRefreshing && mIsScanAnimationRunning;
- boolean shouldChangeIcon = mSettledSeverityLevel != severityLevel;
-
- if (shouldStartScanAnimation && !mIsIconChangeAnimationRunning) {
- mSettledSeverityLevel = severityLevel;
- startScanningAnimation(statusImage);
- } else if (shouldStartScanAnimation) {
- mQueuedScanAnimationSeverityLevel = severityLevel;
- } else if (mIsScanAnimationRunning && shouldChangeIcon) {
- mSettledSeverityLevel = severityLevel;
- continueScanningAnimation(statusImage);
- } else if (shouldEndScanAnimation) {
- endScanningAnimation(statusImage, rescanButton);
- } else if (shouldChangeIcon && !mIsScanAnimationRunning) {
- startIconChangeAnimation(statusImage);
- } else if (shouldChangeIcon) {
- mQueuedIconAnimationSeverityLevel = severityLevel;
- } else if (!mIsScanAnimationRunning && !mIsIconChangeAnimationRunning) {
- setSettledStatus(statusImage);
- }
+
+ handleAnimationSequencerAction(
+ mSequencer.onUpdateReceived(isRefreshing, severityLevel),
+ statusImage,
+ rescanButton,
+ /* scanningAnimation= */ null);
}
private void runTextAnimationIfNeeded(TextView titleView, TextView summaryView) {
@@ -212,22 +191,27 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
}
}
- private void startScanningAnimation(ImageView statusImage) {
- mIsScanAnimationRunning = true;
+ private void startScanningAnimation(ImageView statusImage, View rescanButton) {
+ mSequencer.onStartScanningAnimationStart();
statusImage.setImageResource(
- StatusAnimationResolver.getScanningStartAnimation(mSettledSeverityLevel));
+ StatusAnimationResolver.getScanningStartAnimation(
+ mSequencer.getCurrentlyVisibleSeverityLevel()));
AnimatedVectorDrawable animation = (AnimatedVectorDrawable) statusImage.getDrawable();
animation.registerAnimationCallback(
new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
- continueScanningAnimation(statusImage);
+ handleAnimationSequencerAction(
+ mSequencer.onStartScanningAnimationEnd(),
+ statusImage,
+ rescanButton,
+ /* scanningAnimation= */ null);
}
});
animation.start();
}
- private void continueScanningAnimation(ImageView statusImage) {
+ private void continueScanningAnimation(ImageView statusImage, View rescanButton) {
// clear previous scan animation in case we need to continue with different severity level
Drawable statusDrawable = statusImage.getDrawable();
if (statusDrawable instanceof AnimatedVectorDrawable) {
@@ -235,17 +219,19 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
}
statusImage.setImageResource(
- StatusAnimationResolver.getScanningAnimation(mSettledSeverityLevel));
+ StatusAnimationResolver.getScanningAnimation(
+ mSequencer.getCurrentlyVisibleSeverityLevel()));
AnimatedVectorDrawable scanningAnim = (AnimatedVectorDrawable) statusImage.getDrawable();
scanningAnim.registerAnimationCallback(
new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
- if (mIsScanAnimationRunning && mStatus.isRefreshInProgress()) {
- scanningAnim.start();
- } else {
- scanningAnim.clearAnimationCallbacks();
- }
+ handleAnimationSequencerAction(
+ mSequencer.onContinueScanningAnimationEnd(
+ mStatus.isRefreshInProgress(), mStatus.getSeverityLevel()),
+ statusImage,
+ rescanButton,
+ scanningAnim);
}
});
scanningAnim.start();
@@ -253,18 +239,19 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
private void endScanningAnimation(ImageView statusImage, View rescanButton) {
Drawable statusDrawable = statusImage.getDrawable();
+ int finishingSeverityLevel = mStatus.getSeverityLevel();
if (!(statusDrawable instanceof AnimatedVectorDrawable)) {
- finishScanAnimation(statusImage, rescanButton);
+ finishScanAnimation(statusImage, rescanButton, finishingSeverityLevel);
return;
}
AnimatedVectorDrawable animatedStatusDrawable = (AnimatedVectorDrawable) statusDrawable;
if (!animatedStatusDrawable.isRunning()) {
- finishScanAnimation(statusImage, rescanButton);
+ finishScanAnimation(statusImage, rescanButton, finishingSeverityLevel);
return;
}
- int scanningSeverityLevel = mSettledSeverityLevel;
+ int scanningSeverityLevel = mSequencer.getCurrentlyVisibleSeverityLevel();
animatedStatusDrawable.clearAnimationCallbacks();
animatedStatusDrawable.registerAnimationCallback(
new Animatable2.AnimationCallback() {
@@ -272,7 +259,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
public void onAnimationEnd(Drawable drawable) {
statusImage.setImageResource(
StatusAnimationResolver.getScanningEndAnimation(
- scanningSeverityLevel, mStatus.getSeverityLevel()));
+ scanningSeverityLevel, finishingSeverityLevel));
AnimatedVectorDrawable animatedDrawable =
(AnimatedVectorDrawable) statusImage.getDrawable();
animatedDrawable.registerAnimationCallback(
@@ -280,7 +267,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
@Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
- finishScanAnimation(statusImage, rescanButton);
+ finishScanAnimation(
+ statusImage, rescanButton, finishingSeverityLevel);
}
});
animatedDrawable.start();
@@ -288,22 +276,32 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
});
}
- private void finishScanAnimation(ImageView statusImage, View rescanButton) {
- mIsScanAnimationRunning = false;
+ private void finishScanAnimation(
+ ImageView statusImage, View rescanButton, int finishedSeverityLevel) {
setRescanButtonState(rescanButton);
- setSettledStatus(statusImage);
- handleQueuedAction(statusImage);
+ handleAnimationSequencerAction(
+ mSequencer.onFinishScanAnimationEnd(
+ mStatus.isRefreshInProgress(), finishedSeverityLevel),
+ statusImage,
+ rescanButton,
+ /* scanningAnimation= */ null);
}
- private void startIconChangeAnimation(ImageView statusImage) {
+ private void startIconChangeAnimation(ImageView statusImage, View rescanButton) {
+ int finalSeverityLevel = mStatus.getSeverityLevel();
int changeAnimationResId =
StatusAnimationResolver.getStatusChangeAnimation(
- mSettledSeverityLevel, mStatus.getSeverityLevel());
+ mSequencer.getCurrentlyVisibleSeverityLevel(), finalSeverityLevel);
if (changeAnimationResId == 0) {
- setSettledStatus(statusImage);
+ handleAnimationSequencerAction(
+ mSequencer.onCouldNotStartIconChangeAnimation(
+ mStatus.isRefreshInProgress(), finalSeverityLevel),
+ statusImage,
+ rescanButton,
+ /* scanningAnimation= */ null);
return;
}
- mIsIconChangeAnimationRunning = true;
+ mSequencer.onIconChangeAnimationStart();
statusImage.setImageResource(changeAnimationResId);
AnimatedVectorDrawable animation = (AnimatedVectorDrawable) statusImage.getDrawable();
animation.clearAnimationCallbacks();
@@ -311,32 +309,59 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
- mIsIconChangeAnimationRunning = false;
- setSettledStatus(statusImage);
- handleQueuedAction(statusImage);
+ handleAnimationSequencerAction(
+ mSequencer.onIconChangeAnimationEnd(
+ mStatus.isRefreshInProgress(), finalSeverityLevel),
+ statusImage,
+ rescanButton,
+ /* scanningAnimation= */ null);
}
});
animation.start();
}
+ private void handleAnimationSequencerAction(
+ @Nullable SafetyStatusAnimationSequencer.Action action,
+ ImageView statusImage,
+ View rescanButton,
+ @Nullable AnimatedVectorDrawable scanningAnimation) {
+ if (action == null) {
+ return;
+ }
+ switch (action) {
+ case START_SCANNING_ANIMATION:
+ startScanningAnimation(statusImage, rescanButton);
+ break;
+ case CONTINUE_SCANNING_ANIMATION:
+ if (scanningAnimation != null) {
+ scanningAnimation.start();
+ } else {
+ continueScanningAnimation(statusImage, rescanButton);
+ }
+ break;
+ case RESET_SCANNING_ANIMATION:
+ continueScanningAnimation(statusImage, rescanButton);
+ break;
+ case FINISH_SCANNING_ANIMATION:
+ endScanningAnimation(statusImage, rescanButton);
+ break;
+ case START_ICON_CHANGE_ANIMATION:
+ startIconChangeAnimation(statusImage, rescanButton);
+ break;
+ case CHANGE_ICON_WITHOUT_ANIMATION:
+ setSettledStatus(statusImage);
+ break;
+ }
+ }
+
private void setSettledStatus(ImageView statusImage) {
Drawable statusDrawable = statusImage.getDrawable();
if (statusDrawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) statusDrawable).clearAnimationCallbacks();
}
-
- mSettledSeverityLevel = mStatus.getSeverityLevel();
- statusImage.setImageResource(mStatus.getStatusImageResId());
- }
-
- private void handleQueuedAction(ImageView statusImage) {
- if (mQueuedScanAnimationSeverityLevel != 0) {
- mQueuedScanAnimationSeverityLevel = 0;
- startScanningAnimation(statusImage);
- } else if (mQueuedIconAnimationSeverityLevel != 0) {
- mQueuedIconAnimationSeverityLevel = 0;
- startIconChangeAnimation(statusImage);
- }
+ statusImage.setImageResource(
+ StatusUiData.Companion.getStatusImageResId(
+ mSequencer.getCurrentlyVisibleSeverityLevel()));
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt
index beeda213c..d83abbe12 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt
@@ -1,12 +1,15 @@
package com.android.permissioncontroller.safetycenter.ui.model
import android.content.Context
+import android.os.Build.VERSION_CODES.TIRAMISU
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterStatus
import android.util.Log
+import androidx.annotation.RequiresApi
import com.android.permissioncontroller.R
/** UI model representation of a Status Card. */
+@RequiresApi(TIRAMISU)
data class StatusUiData(
private val status: SafetyCenterStatus,
@get:JvmName("hasIssues") val hasIssues: Boolean = false,
@@ -21,16 +24,9 @@ data class StatusUiData(
fun copyForPendingActions(hasPendingActions: Boolean) =
copy(hasPendingActions = hasPendingActions)
- private companion object {
- val TAG: String = StatusUiData::class.java.simpleName
- }
-
- val title: CharSequence by status::title
- val originalSummary: CharSequence by status::summary
- val severityLevel: Int by status::severityLevel
-
- val statusImageResId: Int
- get() =
+ companion object {
+ private val TAG: String = StatusUiData::class.java.simpleName
+ fun getStatusImageResId(severityLevel: Int) =
when (severityLevel) {
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN,
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK -> R.drawable.safety_status_info
@@ -43,6 +39,14 @@ data class StatusUiData(
R.drawable.safety_status_info
}
}
+ }
+
+ val title: CharSequence by status::title
+ val originalSummary: CharSequence by status::summary
+ val severityLevel: Int by status::severityLevel
+
+ val statusImageResId: Int
+ get() = getStatusImageResId(severityLevel)
fun getSummary(context: Context): CharSequence {
return if (hasPendingActions) {