summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/companion/AssociationInfo.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt8
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt21
-rw-r--r--packages/SystemUI/res-keyguard/font/clock.xml28
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml32
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml13
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_large.xml1
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_small.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt18
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt22
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt40
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java27
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt153
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java182
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java209
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt259
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java64
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java218
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java40
-rw-r--r--services/companion/java/com/android/server/companion/AssociationStoreImpl.java16
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java366
-rw-r--r--services/companion/java/com/android/server/companion/PersistentDataStore.java24
-rw-r--r--services/companion/java/com/android/server/companion/RolesUtils.java2
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java54
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java782
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java1574
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java227
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java414
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java61
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java135
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java84
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java819
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java2
-rw-r--r--telecomm/java/android/telecom/InCallService.java25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt18
72 files changed, 1605 insertions, 5115 deletions
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index f7f0235cd508..93748f81ffa1 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -55,6 +55,14 @@ public final class AssociationInfo implements Parcelable {
private final boolean mSelfManaged;
private final boolean mNotifyOnDeviceNearby;
+
+ /**
+ * Indicates that the association has been revoked (removed), but we keep the association
+ * record for final clean up (e.g. removing the app from the list of the role holders).
+ *
+ * @see CompanionDeviceManager#disassociate(int)
+ */
+ private final boolean mRevoked;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
@@ -71,7 +79,7 @@ public final class AssociationInfo implements Parcelable {
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
- long timeApprovedMs, long lastTimeConnectedMs) {
+ boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -91,6 +99,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ mRevoked = revoked;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
}
@@ -176,6 +185,14 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * @return if the association has been revoked (removed).
+ * @hide
+ */
+ public boolean isRevoked() {
+ return mRevoked;
+ }
+
+ /**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
@@ -244,6 +261,7 @@ public final class AssociationInfo implements Parcelable {
+ ", mDeviceProfile='" + mDeviceProfile + '\''
+ ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ + ", mRevoked=" + mRevoked
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
@@ -260,6 +278,7 @@ public final class AssociationInfo implements Parcelable {
&& mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
+ && mRevoked == that.mRevoked
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
@@ -271,7 +290,7 @@ public final class AssociationInfo implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
- mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs,
+ mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
mLastTimeConnectedMs);
}
@@ -293,6 +312,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
+ dest.writeBoolean(mRevoked);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
}
@@ -309,6 +329,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
+ mRevoked = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
}
@@ -352,11 +373,13 @@ public final class AssociationInfo implements Parcelable {
@NonNull
private final AssociationInfo mOriginalInfo;
private boolean mNotifyOnDeviceNearby;
+ private boolean mRevoked;
private long mLastTimeConnectedMs;
private Builder(@NonNull AssociationInfo info) {
mOriginalInfo = info;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+ mRevoked = info.mRevoked;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
}
@@ -388,6 +411,17 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @Override
+ @NonNull
+ public Builder setRevoked(boolean revoked) {
+ mRevoked = revoked;
+ return this;
+ }
+
+ /**
* @hide
*/
@NonNull
@@ -401,6 +435,7 @@ public final class AssociationInfo implements Parcelable {
mOriginalInfo.mDeviceProfile,
mOriginalInfo.mSelfManaged,
mNotifyOnDeviceNearby,
+ mRevoked,
mOriginalInfo.mTimeApprovedMs,
mLastTimeConnectedMs
);
@@ -433,5 +468,12 @@ public final class AssociationInfo implements Parcelable {
*/
@NonNull
Builder setLastTimeConnected(long lastTimeConnectedMs);
+
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @NonNull
+ Builder setRevoked(boolean revoked);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 28427a808d90..c80c14f353d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -42,6 +42,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SP
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import android.animation.Animator;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
@@ -248,6 +249,13 @@ public class PipTransition extends PipTransitionController {
return false;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ end();
+ }
+
/** Helper to identify whether this handler is currently the one playing an animation */
private boolean isAnimatingLocally() {
return mFinishTransaction != null;
@@ -283,6 +291,13 @@ public class PipTransition extends PipTransitionController {
}
@Override
+ public void end() {
+ Animator animator = mPipAnimationController.getCurrentAnimator();
+ if (animator == null) return;
+ animator.end();
+ }
+
+ @Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index d3f69f6762f9..a43b6043908b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -237,6 +237,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
@NonNull final Transitions.TransitionFinishCallback finishCallback) {
}
+ /** End the currently-playing PiP animation. */
+ public void end() {
+ }
+
/**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index f7057d454df9..e55729a883e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -225,9 +225,25 @@ class SplitScreenTransitions {
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
- if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) {
+ if (mergeTarget != mAnimatingTransition) return;
+ if (mActiveRemoteHandler != null) {
mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ } else {
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animator anim = mAnimations.get(i);
+ mTransitions.getAnimExecutor().execute(anim::end);
+ }
+ }
+ }
+
+ boolean end() {
+ // If its remote, there's nothing we can do right now.
+ if (mActiveRemoteHandler != null) return false;
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animator anim = mAnimations.get(i);
+ mTransitions.getAnimExecutor().execute(anim::end);
}
+ return true;
}
void onTransitionMerged(@NonNull IBinder transition) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 6cfb700fc16a..4e7b20e3ae84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1521,6 +1521,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
+ /** Jump the current transition animation to the end. */
+ public boolean end() {
+ return mSplitTransitions.end();
+ }
+
@Override
public void onTransitionMerged(@NonNull IBinder transition) {
mSplitTransitions.onTransitionMerged(transition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 1ffe26df729f..7234d559e153 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -53,10 +53,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
+ /** The default animation for this mixed transition. */
+ static final int ANIM_TYPE_DEFAULT = 0;
+
+ /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
+ static final int ANIM_TYPE_GOING_HOME = 1;
+
final int mType;
+ int mAnimType = 0;
final IBinder mTransition;
Transitions.TransitionFinishCallback mFinishCallback = null;
+ Transitions.TransitionHandler mLeftoversHandler = null;
/**
* Mixed transitions are made up of multiple "parts". This keeps track of how many
@@ -128,7 +136,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
MixedTransition mixed = null;
for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
if (mActiveTransitions.get(i).mTransition != transition) continue;
- mixed = mActiveTransitions.remove(i);
+ mixed = mActiveTransitions.get(i);
break;
}
if (mixed == null) return false;
@@ -137,6 +145,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
} else {
+ mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
+ mixed.mType);
}
@@ -178,6 +187,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
--mixed.mInFlightSubAnimations;
if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
if (isGoingHome) {
mSplitHandler.onTransitionAnimationComplete();
}
@@ -216,8 +226,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
finishCB);
// Dispatch the rest of the transition normally. This will most-likely be taken by
// recents or default handler.
- mPlayer.dispatchTransition(mixed.mTransition, everythingElse, otherStartT,
- finishTransaction, finishCB, this);
+ mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse,
+ otherStartT, finishTransaction, finishCB, this);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ "forward animation to Pip-Handler.");
@@ -235,6 +245,32 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ if (mActiveTransitions.get(i) != mergeTarget) continue;
+ MixedTransition mixed = mActiveTransitions.get(i);
+ if (mixed.mInFlightSubAnimations <= 0) {
+ // Already done, so no need to end it.
+ return;
+ }
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
+ boolean ended = mSplitHandler.end();
+ // If split couldn't end (because it is remote), then don't end everything else
+ // since we have to play out the animation anyways.
+ if (!ended) return;
+ mPipHandler.end();
+ if (mixed.mLeftoversHandler != null) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ }
+ } else {
+ mPipHandler.end();
+ }
+ } else {
+ throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ + mixed.mType);
+ }
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index c3eaa8ee1da0..05e5b8e66a00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -523,6 +523,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ArrayList<Animator> anims = mAnimations.get(mergeTarget);
+ if (anims == null) return;
+ for (int i = anims.size() - 1; i >= 0; --i) {
+ final Animator anim = anims.get(i);
+ mAnimExecutor.execute(anim::end);
+ }
+ }
+
private void edgeExtendWindow(TransitionInfo.Change change,
Animation a, SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 20fac93df0af..db14fdf5930b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -474,7 +474,13 @@ class TextInterpolator(layout: Layout) {
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
- val count = layout.getLineEnd(lineNo) - lineStart
+ var count = layout.getLineEnd(lineNo) - lineStart
+ // Do not render the last character in the line if it's a newline and unprintable
+ val last = lineStart + count - 1
+ if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
+ count--
+ }
+
val runs = mutableListOf<PositionedGlyphs>()
TextShaper.shapeText(
layout.text,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 0c1916074e0c..cafdc8676173 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -13,9 +13,9 @@
*/
package com.android.systemui.plugins
+import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.View
-import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.plugins.annotations.ProvidesInterface
import java.io.PrintWriter
import java.util.Locale
@@ -57,7 +57,15 @@ interface Clock {
val events: ClockEvents
/** Triggers for various animations */
- val animation: ClockAnimation
+ val animations: ClockAnimations
+
+ /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
+ fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ events.onColorPaletteChanged(resources)
+ animations.doze(dozeFraction)
+ animations.fold(foldFraction)
+ events.onTimeTick()
+ }
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) { }
@@ -80,15 +88,12 @@ interface ClockEvents {
/** Call whenever font settings change */
fun onFontSettingChanged() { }
- /** Call whenever the color pallete should update */
- fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
+ /** Call whenever the color palette should update */
+ fun onColorPaletteChanged(resources: Resources) { }
}
/** Methods which trigger various clock animations */
-interface ClockAnimation {
- /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
- fun initialize(dozeFraction: Float, foldFraction: Float) { }
-
+interface ClockAnimations {
/** Runs an enter animation (if any) */
fun enter() { }
diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
deleted file mode 100644
index 0137dc39921f..000000000000
--- a/packages/SystemUI/res-keyguard/font/clock.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2020, 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.
-*/
--->
-
-<!--
-** AOD/LockScreen Clock font.
-** Should include all numeric glyphs in all supported locales.
-** Recommended: font with variable width to support AOD => LS animations
--->
-<!-- TODO: Remove when clock migration complete -->
-<font-family xmlns:android="http://schemas.android.com/apk/res/android">
- <font android:typeface="monospace"/>
-</font-family> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 6a38507b2ad7..8b8ebf00e190 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,42 +31,14 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:paddingStart="@dimen/clock_padding_start">
- <com.android.systemui.shared.clocks.AnimatableClockView
- android:id="@+id/animatable_clock_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:gravity="start"
- android:textSize="@dimen/clock_text_size"
- android:fontFamily="@font/clock"
- android:elegantTextHeight="false"
- android:singleLine="true"
- android:fontFeatureSettings="pnum"
- chargeAnimationDelay="350"
- dozeWeight="200"
- lockScreenWeight="400"
- />
</FrameLayout>
<FrameLayout
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_below="@id/keyguard_slice_view"
+ android:paddingTop="@dimen/keyguard_large_clock_top_padding"
android:visibility="gone">
- <com.android.systemui.shared.clocks.AnimatableClockView
- android:id="@+id/animatable_clock_view_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center_horizontal"
- android:textSize="@dimen/large_clock_text_size"
- android:fontFamily="@font/clock"
- android:typeface="monospace"
- android:elegantTextHeight="false"
- chargeAnimationDelay="200"
- dozeWeight="200"
- lockScreenWeight="400"
- />
</FrameLayout>
<!-- Not quite optimal but needed to translate these items as a group. The
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index e066d38e446f..27ee428d3528 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -19,7 +19,7 @@
android:id="@+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="@font/clock"
+ android:fontFamily="@*android:string/config_clockFontFamily"
android:includeFontPadding="false"
android:textColor="@android:color/white"
android:format12Hour="h:mm"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3b8505015f3e..1210b79d3ff5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -661,13 +661,7 @@
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
<!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
-
- <!-- TODO: Remove during migration -->
- <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
- <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
- <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
- <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+ <dimen name="keyguard_large_clock_top_padding">100dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -890,11 +884,6 @@
burn-in on AOD. -->
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
- <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <!-- TODO: Remove when clock migration complete -->
- <dimen name="large_clock_text_size">150dp</dimen>
- <dimen name="clock_text_size">86dp</dimen>
-
<!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
index 8510a0a8b550..0139d50dcfba 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_large.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -18,7 +18,6 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/animatable_clock_view_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
index ec0e427e6a4d..390ff5e3ff78 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_small.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -18,7 +18,6 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
@@ -26,6 +25,7 @@
android:textSize="@dimen/small_clock_text_size"
android:fontFamily="@*android:string/config_clockFontFamily"
android:elegantTextHeight="false"
+ android:ellipsize="none"
android:singleLine="true"
android:fontFeatureSettings="pnum"
chargeAnimationDelay="350"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 2739d59dbf00..8f1959e884cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,12 +20,15 @@ import android.annotation.ColorInt
import android.annotation.FloatRange
import android.annotation.IntRange
import android.annotation.SuppressLint
+import android.app.compat.ChangeIdStateCache.invalidate
import android.content.Context
import android.graphics.Canvas
import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.widget.TextView
+import com.android.internal.R.attr.contentDescription
+import com.android.internal.R.attr.format
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
@@ -75,6 +78,12 @@ class AnimatableClockView @JvmOverloads constructor(
val lockScreenWeight: Int
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+ /**
+ * The number of pixels below the baseline. For fonts that support languages such as
+ * Burmese, this space can be significant and should be accounted for when computing layout.
+ */
+ val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+
init {
val animatableClockViewAttributes = context.obtainStyledAttributes(
attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
@@ -133,6 +142,15 @@ class AnimatableClockView @JvmOverloads constructor(
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
+
+ // Because the TextLayout may mutate under the hood as a result of the new text, we
+ // notify the TextAnimator that it may have changed and request a measure/layout. A
+ // crash will occur on the next invocation of setTextStyle if the layout is mutated
+ // without being notified TextInterpolator being notified.
+ if (layout != null) {
+ textAnimator?.updateLayout(layout)
+ }
+ requestLayout()
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a4c03b0b57c8..4b8b46d54848 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -35,8 +35,6 @@ import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
private val DEBUG = true
-typealias ClockChangeListener = () -> Unit
-
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -51,12 +49,17 @@ open class ClockRegistry(
defaultClockProvider: DefaultClockProvider
) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
+ // Usually this would be a typealias, but a SAM provides better java interop
+ fun interface ClockChangeListener {
+ fun onClockChanged()
+ }
+
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
@@ -73,7 +76,7 @@ open class ClockRegistry(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
- return gson.fromJson(json, ClockSetting::class.java).clockId
+ return gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
}
set(value) {
val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
@@ -100,8 +103,11 @@ open class ClockRegistry(
val id = clock.clockId
val current = availableClocks[id]
if (current != null) {
- Log.e(TAG, "Clock Id conflict: $id is registered by both " +
- "${provider::class.simpleName} and ${current.provider::class.simpleName}")
+ Log.e(
+ TAG,
+ "Clock Id conflict: $id is registered by both " +
+ "${provider::class.simpleName} and ${current.provider::class.simpleName}"
+ )
return
}
@@ -110,7 +116,7 @@ open class ClockRegistry(
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
}
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
}
}
@@ -122,7 +128,7 @@ open class ClockRegistry(
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 5d8da5985768..1d8abe3fdf42 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,10 +19,9 @@ import android.graphics.drawable.Drawable
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
-import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimation
+import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
@@ -102,10 +101,13 @@ class DefaultClock(
TypedValue.COMPLEX_UNIT_PX,
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
)
+ recomputePadding()
}
- override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
- clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
+ override fun onColorPaletteChanged(resources: Resources) {
+ val color = resources.getColor(android.R.color.system_accent1_100)
+ clocks.forEach { it.setColors(DOZE_COLOR, color) }
+ }
override fun onLocaleChanged(locale: Locale) {
val nf = NumberFormat.getInstance(locale)
@@ -119,8 +121,17 @@ class DefaultClock(
}
}
- override val animation = object : ClockAnimation {
- override fun initialize(dozeFraction: Float, foldFraction: Float) {
+ override var animations = DefaultClockAnimations(0f, 0f)
+ private set
+
+ inner class DefaultClockAnimations(
+ dozeFraction: Float,
+ foldFraction: Float
+ ) : ClockAnimations {
+ private var foldState = AnimationState(0f)
+ private var dozeState = AnimationState(0f)
+
+ init {
dozeState = AnimationState(dozeFraction)
foldState = AnimationState(foldFraction)
@@ -132,14 +143,13 @@ class DefaultClock(
}
override fun enter() {
- if (dozeState.isActive) {
+ if (!dozeState.isActive) {
clocks.forEach { it.animateAppearOnLockscreen() }
}
}
override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
- private var foldState = AnimationState(0f)
override fun fold(fraction: Float) {
val (hasChanged, hasJumped) = foldState.update(fraction)
if (hasChanged) {
@@ -147,7 +157,6 @@ class DefaultClock(
}
}
- private var dozeState = AnimationState(0f)
override fun doze(fraction: Float) {
val (hasChanged, hasJumped) = dozeState.update(fraction)
if (hasChanged) {
@@ -172,6 +181,19 @@ class DefaultClock(
init {
events.onLocaleChanged(Locale.getDefault())
+ clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
+ }
+
+ override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ recomputePadding()
+ animations = DefaultClockAnimations(dozeFraction, foldFraction)
+ events.onColorPaletteChanged(resources)
+ events.onTimeTick()
+ }
+
+ private fun recomputePadding() {
+ val topPadding = -1 * (largeClock.bottom.toInt() - 180)
+ largeClock.setPadding(0, topPadding, 0, 0)
}
override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 76a09b38036c..302ba8444f05 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -114,6 +114,8 @@ public class RemoteAnimationAdapterCompat {
private static IRemoteTransition.Stub wrapRemoteTransition(
final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
@Override
public void startAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t,
@@ -229,19 +231,32 @@ public class RemoteAnimationAdapterCompat {
}
}
};
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.put(token, animationFinishedCallback);
+ }
// TODO(bc-unlcok): Pass correct transit type.
- remoteAnimationAdapter.onAnimationStart(
- TRANSIT_OLD_NONE,
- appsCompat, wallpapersCompat, nonAppsCompat,
- animationFinishedCallback);
+ remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
+ appsCompat, wallpapersCompat, nonAppsCompat, () -> {
+ synchronized (mFinishRunnables) {
+ if (mFinishRunnables.remove(token) == null) return;
+ }
+ animationFinishedCallback.run();
+ });
}
@Override
public void mergeAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
- // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, ignore
- // any incoming merges.
+ // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+ // to legacy cancel.
+ final Runnable finishRunnable;
+ synchronized (mFinishRunnables) {
+ finishRunnable = mFinishRunnables.remove(mergeTarget);
+ }
+ if (finishRunnable == null) return;
+ remoteAnimationAdapter.onAnimationCancelled();
+ finishRunnable.run();
}
};
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index c69ff7ee1cd8..e0b11d83bf75 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -182,18 +182,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
- /**
- * @return the number of pixels below the baseline. For fonts that support languages such as
- * Burmese, this space can be significant.
- */
- public float getBottom() {
- if (mView.getPaint() != null && mView.getPaint().getFontMetrics() != null) {
- return mView.getPaint().getFontMetrics().bottom;
- }
-
- return 0f;
- }
-
/** Animate the clock appearance */
public void animateAppear() {
if (!mIsDozing) mView.animateAppearOnLockscreen();
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
new file mode 100644
index 000000000000..efd7bcf10cd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.text.format.DateFormat
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
+import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import javax.inject.Inject
+
+/**
+ * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
+ * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
+ */
+class ClockEventController @Inject constructor(
+ private val statusBarStateController: StatusBarStateController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val batteryController: BatteryController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val configurationController: ConfigurationController,
+ @Main private val resources: Resources,
+ private val context: Context
+) {
+ var clock: Clock? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ value.initialize(resources, dozeAmount, 0f)
+ }
+ }
+
+ private var isDozing = false
+ private set
+
+ private var isCharging = false
+ private var dozeAmount = 0f
+ private var isKeyguardShowing = false
+
+ private val configListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ clock?.events?.onColorPaletteChanged(resources)
+ }
+ }
+
+ private val batteryCallback = object : BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ if (isKeyguardShowing && !isCharging && charging) {
+ clock?.animations?.charge()
+ }
+ isCharging = charging
+ }
+ }
+
+ private val localeBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ clock?.events?.onLocaleChanged(Locale.getDefault())
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ clock?.animations?.doze(linear)
+
+ isDozing = linear > dozeAmount
+ dozeAmount = linear
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(showing: Boolean) {
+ isKeyguardShowing = showing
+ if (!isKeyguardShowing) {
+ clock?.animations?.doze(if (isDozing) 1f else 0f)
+ }
+ }
+
+ override fun onTimeFormatChanged(timeFormat: String) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ clock?.events?.onTimeZoneChanged(timeZone)
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+ }
+
+ init {
+ isDozing = statusBarStateController.isDozing
+ }
+
+ fun registerListeners() {
+ dozeAmount = statusBarStateController.dozeAmount
+ isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+
+ broadcastDispatcher.registerReceiver(
+ localeBroadcastReceiver,
+ IntentFilter(Intent.ACTION_LOCALE_CHANGED)
+ )
+ configurationController.addCallback(configListener)
+ batteryController.addCallback(batteryCallback)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ statusBarStateController.addCallback(statusBarStateListener)
+ }
+
+ fun unregisterListeners() {
+ broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
+ configurationController.removeCallback(configListener)
+ batteryController.removeCallback(batteryCallback)
+ keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+ statusBarStateController.removeCallback(statusBarStateListener)
+ }
+
+ /**
+ * Dump information for debugging
+ */
+ fun dump(pw: PrintWriter) {
+ pw.println(this)
+ clock?.dump(pw)
+ }
+
+ companion object {
+ private val TAG = ClockEventController::class.simpleName
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 206b8bee323c..e1fabdef3651 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,10 +5,8 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.util.AttributeSet;
-import android.util.TypedValue;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -17,19 +15,14 @@ import android.widget.RelativeLayout;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.TimeZone;
-
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -50,17 +43,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
public static final int SMALL = 1;
/**
- * Optional/alternative clock injected via plugin.
- */
- private ClockPlugin mClockPlugin;
-
- /**
* Frame for small/large clocks
*/
- private FrameLayout mClockFrame;
+ private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
- private AnimatableClockView mClockView;
- private AnimatableClockView mLargeClockView;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -80,12 +66,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
@VisibleForTesting AnimatorSet mClockOutAnim = null;
private ObjectAnimator mStatusAreaAnim = null;
- /**
- * If the Keyguard Slice has a header (big center-aligned text.)
- */
- private boolean mSupportsDarkText;
- private int[] mColorPalette;
-
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -97,97 +77,38 @@ public class KeyguardClockSwitch extends RelativeLayout {
* Apply dp changes on font/scale change
*/
public void onDensityOrFontScaleChanged() {
- mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
- .getDimensionPixelSize(R.dimen.large_clock_text_size));
- mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
- .getDimensionPixelSize(R.dimen.clock_text_size));
-
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
-
mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_smartspace_top_offset);
}
- /**
- * Returns if this view is presenting a custom clock, or the default implementation.
- */
- public boolean hasCustomClock() {
- return mClockPlugin != null;
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mClockFrame = findViewById(R.id.lockscreen_clock_view);
- mClockView = findViewById(R.id.animatable_clock_view);
+ mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
- mLargeClockView = findViewById(R.id.animatable_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
- void setClockPlugin(ClockPlugin plugin, int statusBarState) {
+ void setClock(Clock clock, int statusBarState) {
// Disconnect from existing plugin.
- if (mClockPlugin != null) {
- View smallClockView = mClockPlugin.getView();
- if (smallClockView != null && smallClockView.getParent() == mClockFrame) {
- mClockFrame.removeView(smallClockView);
- }
- View bigClockView = mClockPlugin.getBigClockView();
- if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) {
- mLargeClockFrame.removeView(bigClockView);
- }
- mClockPlugin.onDestroyView();
- mClockPlugin = null;
- }
- if (plugin == null) {
- mClockView.setVisibility(View.VISIBLE);
- mLargeClockView.setVisibility(View.VISIBLE);
- return;
- }
- // Attach small and big clock views to hierarchy.
- View smallClockView = plugin.getView();
- if (smallClockView != null) {
- mClockFrame.addView(smallClockView, -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- mClockView.setVisibility(View.GONE);
- }
- View bigClockView = plugin.getBigClockView();
- if (bigClockView != null) {
- mLargeClockFrame.addView(bigClockView);
- mLargeClockView.setVisibility(View.GONE);
- }
-
- // Initialize plugin parameters.
- mClockPlugin = plugin;
- mClockPlugin.setStyle(getPaint().getStyle());
- mClockPlugin.setTextColor(getCurrentTextColor());
- mClockPlugin.setDarkAmount(mDarkAmount);
- if (mColorPalette != null) {
- mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
- }
- }
+ mSmallClockFrame.removeAllViews();
+ mLargeClockFrame.removeAllViews();
- /**
- * It will also update plugin setStyle if plugin is connected.
- */
- public void setStyle(Style style) {
- if (mClockPlugin != null) {
- mClockPlugin.setStyle(style);
+ if (clock == null) {
+ Log.e(TAG, "No clock being shown");
+ return;
}
- }
- /**
- * It will also update plugin setTextColor if plugin is connected.
- */
- public void setTextColor(int color) {
- if (mClockPlugin != null) {
- mClockPlugin.setTextColor(color);
- }
+ // Attach small and big clock views to hierarchy.
+ mSmallClockFrame.addView(clock.getSmallClock(), -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mLargeClockFrame.addView(clock.getLargeClock());
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -203,14 +124,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
int direction = 1;
float statusAreaYTranslation;
if (useLargeClock) {
- out = mClockFrame;
+ out = mSmallClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
} else {
- in = mClockFrame;
+ in = mSmallClockFrame;
out = mLargeClockFrame;
statusAreaYTranslation = 0f;
@@ -269,18 +190,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
/**
- * Set the amount (ratio) that the device has transitioned to doze.
- *
- * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
- */
- public void setDarkAmount(float darkAmount) {
- mDarkAmount = darkAmount;
- if (mClockPlugin != null) {
- mClockPlugin.setDarkAmount(darkAmount);
- }
- }
-
- /**
* Display the desired clock and hide the other one
*
* @return true if desired clock appeared and false if it was already visible
@@ -311,64 +220,11 @@ public class KeyguardClockSwitch extends RelativeLayout {
mChildrenAreLaidOut = true;
}
- public Paint getPaint() {
- return mClockView.getPaint();
- }
-
- public int getCurrentTextColor() {
- return mClockView.getCurrentTextColor();
- }
-
- public float getTextSize() {
- return mClockView.getTextSize();
- }
-
- /**
- * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
- */
- public void refresh() {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeTick();
- }
- }
-
- /**
- * Notifies that the time zone has changed.
- */
- public void onTimeZoneChanged(TimeZone timeZone) {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeZoneChanged(timeZone);
- }
- }
-
- /**
- * Notifies that the time format has changed.
- *
- * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
- */
- public void onTimeFormatChanged(String timeFormat) {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeFormatChanged(timeFormat);
- }
- }
-
- void updateColors(ColorExtractor.GradientColors colors) {
- mSupportsDarkText = colors.supportsDarkText();
- mColorPalette = colors.getColorPalette();
- if (mClockPlugin != null) {
- mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
- }
- }
-
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
- pw.println(" mClockPlugin: " + mClockPlugin);
- pw.println(" mClockFrame: " + mClockFrame);
+ pw.println(" mClockFrame: " + mSmallClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
pw.println(" mStatusArea: " + mStatusArea);
- pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" mSupportsDarkText: " + mSupportsDarkText);
- pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6c32a4910c56..d566f49c04ff 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,8 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import android.app.WallpaperManager;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -32,34 +30,28 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -69,48 +61,24 @@ import javax.inject.Inject;
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
- private static final boolean CUSTOM_CLOCKS_ENABLED = true;
-
private final StatusBarStateController mStatusBarStateController;
- private final SysuiColorExtractor mColorExtractor;
- private final ClockManager mClockManager;
+ private final ClockRegistry mClockRegistry;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
- private final Resources mResources;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
+ private final ClockEventController mClockEventController;
- /**
- * Clock for both small and large sizes
- */
- private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame; // top aligned clock
- private AnimatableClockController mLargeClockViewController;
+ /** Clock frames for both small and large sizes */
+ private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
private int mKeyguardClockTopMargin = 0;
-
- /**
- * Listener for changes to the color palette.
- *
- * The color palette changes when the wallpaper is changed.
- */
- private final ColorExtractor.OnColorsChangedListener mColorsListener =
- (extractor, which) -> {
- if ((which & WallpaperManager.FLAG_LOCK) != 0) {
- mView.updateColors(getGradientColors());
- }
- };
-
- private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
+ private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea;
// If set will replace keyguard_slice_view
@@ -119,9 +87,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private boolean mOnlyClock = false;
- private Executor mUiExecutor;
+ private final Executor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
- private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+ private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
updateDoubleLineClock();
@@ -142,34 +110,30 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch,
StatusBarStateController statusBarStateController,
- SysuiColorExtractor colorExtractor,
- ClockManager clockManager,
+ ClockRegistry clockRegistry,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
- BroadcastDispatcher broadcastDispatcher,
- BatteryController batteryController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main Executor uiExecutor,
- @Main Resources resources,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ ClockEventController clockEventController) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
- mColorExtractor = colorExtractor;
- mClockManager = clockManager;
+ mClockRegistry = clockRegistry;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
- mBroadcastDispatcher = broadcastDispatcher;
- mBatteryController = batteryController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mSmartspaceController = smartspaceController;
- mResources = resources;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
+ mClockEventController = clockEventController;
+
+ mClockChangedListener = () -> {
+ setClock(mClockRegistry.createCurrentClock());
+ };
}
/**
@@ -186,40 +150,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public void onInit() {
mKeyguardSliceViewController.init();
- mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
- mClockViewController =
- new AnimatableClockController(
- mView.findViewById(R.id.animatable_clock_view),
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources);
- mClockViewController.init();
-
- mLargeClockViewController =
- new AnimatableClockController(
- mView.findViewById(R.id.animatable_clock_view_large),
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources);
- mLargeClockViewController.init();
-
mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
mDumpManager.registerDumpable(getClass().toString(), this);
}
@Override
protected void onViewAttached() {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.addOnClockChangedListener(mClockChangedListener);
- }
- mColorExtractor.addOnColorsChangedListener(mColorsListener);
- mView.updateColors(getGradientColors());
+ mClockRegistry.registerClockChangeListener(mClockChangedListener);
+ setClock(mClockRegistry.createCurrentClock());
+ mClockEventController.registerListeners();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
@@ -242,7 +184,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
ksv.setVisibility(View.GONE);
addSmartspaceView(ksvIndex);
- updateClockLayout();
}
mSecureSettings.registerContentObserverForUser(
@@ -264,11 +205,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
@Override
protected void onViewDetached() {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.removeOnClockChangedListener(mClockChangedListener);
- }
- mColorExtractor.removeOnColorsChangedListener(mColorsListener);
- mView.setClockPlugin(null, mStatusBarStateController.getState());
+ mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
+ mClockEventController.unregisterListeners();
+ setClock(null);
mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
@@ -307,18 +246,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mView.onDensityOrFontScaleChanged();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
-
- updateClockLayout();
- }
-
- private void updateClockLayout() {
- int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
- R.dimen.keyguard_large_clock_top_margin)
- - (int) mLargeClockViewController.getBottom();
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT);
- lp.topMargin = largeClockTopMargin;
- mLargeClockFrame.setLayoutParams(lp);
}
/**
@@ -334,44 +261,31 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
boolean appeared = mView.switchToClock(clockSize, animate);
if (animate && appeared && clockSize == LARGE) {
- mLargeClockViewController.animateAppear();
+ getClock().getAnimations().enter();
}
}
- public void animateFoldToAod() {
- if (mClockViewController != null) {
- mClockViewController.animateFoldAppear();
- mLargeClockViewController.animateFoldAppear();
- }
- }
-
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mView.hasCustomClock();
- }
-
/**
- * Get the clock text size.
+ * Animates the clock view between folded and unfolded states
*/
- public float getClockTextSize() {
- return mView.getTextSize();
+ public void animateFoldToAod(float foldFraction) {
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.getAnimations().fold(foldFraction);
+ }
}
/**
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
- if (mClockViewController != null) {
- mClockViewController.refreshTime();
- mLargeClockViewController.refreshTime();
- }
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
-
- mView.refresh();
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.getEvents().onTimeTick();
+ }
}
/**
@@ -383,7 +297,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
- PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
+ PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
x, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
scale, props, animate);
@@ -396,25 +310,39 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
}
- void updateTimeZone(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
/**
* Get y-bottom position of the currently visible clock on the keyguard.
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
+ Clock clock = getClock();
+ if (clock == null) {
+ return 0;
+ }
+
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
- View clock = mLargeClockFrame.findViewById(
- com.android.systemui.R.id.animatable_clock_view_large);
int frameHeight = mLargeClockFrame.getHeight();
- int clockHeight = clock.getHeight();
+ int clockHeight = clock.getLargeClock().getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
- return mClockFrame.findViewById(
- com.android.systemui.R.id.animatable_clock_view).getHeight()
- + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ int clockHeight = clock.getSmallClock().getHeight();
+ return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ /**
+ * Get the height of the currently visible clock on the keyguard.
+ */
+ int getClockHeight() {
+ Clock clock = getClock();
+ if (clock == null) {
+ return 0;
+ }
+
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ return clock.getLargeClock().getHeight();
+ } else {
+ return clock.getSmallClock().getHeight();
}
}
@@ -429,12 +357,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mNotificationIconAreaController.setupAodIcons(nic);
}
- private void setClockPlugin(ClockPlugin plugin) {
- mView.setClockPlugin(plugin, mStatusBarStateController.getState());
+ private void setClock(Clock clock) {
+ mClockEventController.setClock(clock);
+ mView.setClock(clock, mStatusBarStateController.getState());
}
- private ColorExtractor.GradientColors getGradientColors() {
- return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
+ private Clock getClock() {
+ return mClockEventController.getClock();
}
private int getCurrentLayoutDirection() {
@@ -467,8 +396,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
- mClockViewController.dump(pw);
- mLargeClockViewController.dump(pw);
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.dump(pw);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index cb3172dabdb1..83e23bd52f19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,14 +17,11 @@
package com.android.keyguard;
import android.content.Context;
-import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
-import androidx.core.graphics.ColorUtils;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -46,7 +43,6 @@ public class KeyguardStatusView extends GridLayout {
private View mMediaHostContainer;
private float mDarkAmount = 0;
- private int mTextColor;
public KeyguardStatusView(Context context) {
this(context, null, 0);
@@ -71,7 +67,6 @@ public class KeyguardStatusView extends GridLayout {
}
mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
- mTextColor = mClockView.getCurrentTextColor();
mMediaHostContainer = findViewById(R.id.status_view_media_container);
@@ -83,15 +78,12 @@ public class KeyguardStatusView extends GridLayout {
return;
}
mDarkAmount = darkAmount;
- mClockView.setDarkAmount(darkAmount);
CrossFadeHelper.fadeOut(mMediaHostContainer, darkAmount);
updateDark();
}
void updateDark() {
- final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mKeyguardSlice.setDarkAmount(mDarkAmount);
- mClockView.setTextColor(blendedTextColor);
}
/** Sets a translationY value on every child view except for the media view. */
@@ -113,7 +105,6 @@ public class KeyguardStatusView extends GridLayout {
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
if (mClockView != null) {
mClockView.dump(pw, args);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 014d08288158..c715a4eaef2b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -30,8 +30,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
-import java.util.TimeZone;
-
import javax.inject.Inject;
/**
@@ -96,13 +94,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * The amount we're in doze.
- */
- public void setDarkAmount(float darkAmount) {
- mView.setDarkAmount(darkAmount);
- }
-
- /**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
@@ -114,16 +105,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
* Performs fold to aod animation of the clocks (changes font weight from bold to thin).
* This animation is played when AOD is enabled and foldable device is fully folded, it is
* displayed on the outer screen
+ * @param foldFraction current fraction of fold animation complete
*/
- public void animateFoldToAod() {
- mKeyguardClockSwitchController.animateFoldToAod();
- }
-
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mKeyguardClockSwitchController.hasCustomClock();
+ public void animateFoldToAod(float foldFraction) {
+ mKeyguardClockSwitchController.animateFoldToAod(foldFraction);
}
/**
@@ -143,24 +128,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * Set pivot x.
- */
- public void setPivotX(float pivot) {
- mView.setPivotX(pivot);
- }
-
- /**
- * Set pivot y.
- */
- public void setPivotY(float pivot) {
- mView.setPivotY(pivot);
- }
-
- /**
- * Get the clock text size.
+ * Update the pivot position based on the parent view
*/
- public float getClockTextSize() {
- return mKeyguardClockSwitchController.getClockTextSize();
+ public void updatePivot(float parentWidth, float parentHeight) {
+ mView.setPivotX(parentWidth / 2f);
+ mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f);
}
/**
@@ -240,11 +212,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
@Override
- public void onTimeZoneChanged(TimeZone timeZone) {
- mKeyguardClockSwitchController.updateTimeZone(timeZone);
- }
-
- @Override
public void onKeyguardVisibilityChanged(boolean showing) {
if (showing) {
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index fc71e2fb2329..69e41ba9b284 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -101,6 +101,9 @@ public class DreamOverlayStateController implements
public void addComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
+ if (DEBUG) {
+ Log.d(TAG, "addComplication: added " + complication);
+ }
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -112,6 +115,9 @@ public class DreamOverlayStateController implements
public void removeComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
+ if (DEBUG) {
+ Log.d(TAG, "removeComplication: removed " + complication);
+ }
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
index 486fc893732f..be94e5031917 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -82,6 +82,7 @@ public class SmartSpaceComplication implements Complication {
mSmartSpaceController.addListener(mSmartspaceListener);
} else {
mSmartSpaceController.removeListener(mSmartspaceListener);
+ mDreamOverlayStateController.removeComplication(mComplication);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index c1173aea8b15..fd6cfc0700ad 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Debug;
import android.util.Log;
import android.view.View;
@@ -44,7 +45,8 @@ import javax.inject.Named;
* a {@link ComplicationLayoutEngine}.
*/
public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
- public static final String TAG = "ComplicationHostVwCtrl";
+ private static final String TAG = "ComplicationHostVwCtrl";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
private final LifecycleOwner mLifecycleOwner;
@@ -90,6 +92,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
}
private void updateComplications(Collection<ComplicationViewModel> complications) {
+ if (DEBUG) {
+ Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25));
+ Log.d(TAG, " mComplications = " + mComplications.toString());
+ Log.d(TAG, " complications = " + complications.toString());
+ }
final Collection<ComplicationId> ids = complications.stream()
.map(complicationViewModel -> complicationViewModel.getId())
.collect(Collectors.toSet());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index ded61a8b80d9..9cd149b9bfee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -54,7 +54,7 @@ import javax.inject.Named;
*/
@DreamOverlayComponent.DreamOverlayScope
public class ComplicationLayoutEngine implements Complication.VisibilityController {
- public static final String TAG = "ComplicationLayoutEngine";
+ public static final String TAG = "ComplicationLayoutEng";
/**
* {@link ViewEntry} is an internal container, capturing information necessary for working with
@@ -529,7 +529,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
*/
public void addComplication(ComplicationId id, View view,
ComplicationLayoutParams lp, @Complication.Category int category) {
- Log.d(TAG, "engine: " + this + " addComplication");
+ Log.d(TAG, "@" + Integer.toHexString(this.hashCode()) + " addComplication: " + id);
// If the complication is present, remove.
if (mEntries.containsKey(id)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
index f0239371ee63..00cf58c0c665 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
@@ -64,4 +64,9 @@ public class ComplicationViewModel extends ViewModel {
public void exitDream() {
mHost.requestExitDream();
}
+
+ @Override
+ public String toString() {
+ return mId + "=" + mComplication.toString();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 9789cef949d3..63f63a5093d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -145,9 +145,6 @@ class DreamSmartspaceController @Inject constructor(
if (view !is View) {
return null
}
-
- view.setIsDreaming(true)
-
return view
} else {
null
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 66b6a5078462..ab6fe89a0588 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -157,7 +157,7 @@ public class Flags {
/***************************************/
// 900 - media
- public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
+ public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b96eee717260..d71956d054be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -55,6 +55,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
@@ -157,7 +158,7 @@ public class KeyguardService extends Service {
Rect localBounds = new Rect(change.getEndAbsBounds());
localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
- out.add(new RemoteAnimationTarget(
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId,
newModeToLegacyMode(change.getMode()),
change.getLeash(),
@@ -168,7 +169,15 @@ public class KeyguardService extends Service {
info.getChanges().size() - i,
new Point(), localBounds, new Rect(change.getEndAbsBounds()),
windowConfiguration, isNotInRecents, null /* startLeash */,
- change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */));
+ change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */);
+ // Use hasAnimatingParent to mark the anything below root task
+ if (taskId != -1 && change.getParent() != null) {
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ if (parentChange != null && parentChange.getTaskInfo() != null) {
+ target.hasAnimatingParent = true;
+ }
+ }
+ out.add(target);
}
return out.toArray(new RemoteAnimationTarget[out.size()]);
}
@@ -189,8 +198,12 @@ public class KeyguardService extends Service {
}
}
+ // Wrap Keyguard going away animation
private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks =
+ new ArrayMap<>();
+
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
@@ -200,16 +213,37 @@ public class KeyguardService extends Service {
final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */);
final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
- // TODO: Remove this, and update alpha value in the IAnimationRunner.
- for (TransitionInfo.Change change : info.getChanges()) {
- t.setAlpha(change.getLeash(), 1.0f);
+ // Sets the alpha to 0 for the opening root task for fade in animation. And since
+ // the fade in animation can only apply on the first opening app, so set alpha to 1
+ // for anything else.
+ boolean foundOpening = false;
+ for (RemoteAnimationTarget target : apps) {
+ if (target.taskId != -1
+ && target.mode == RemoteAnimationTarget.MODE_OPENING
+ && !target.hasAnimatingParent) {
+ if (foundOpening) {
+ Log.w(TAG, "More than one opening target");
+ t.setAlpha(target.leash, 1.0f);
+ continue;
+ }
+ t.setAlpha(target.leash, 0.0f);
+ foundOpening = true;
+ } else {
+ t.setAlpha(target.leash, 1.0f);
+ }
}
t.apply();
+ synchronized (mFinishCallbacks) {
+ mFinishCallbacks.put(transition, finishCallback);
+ }
runner.onAnimationStart(getTransitionOldType(info.getType(), info.getFlags(), apps),
apps, wallpapers, nonApps,
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
+ synchronized (mFinishCallbacks) {
+ if (mFinishCallbacks.remove(transition) == null) return;
+ }
Slog.d(TAG, "Finish IRemoteAnimationRunner.");
finishCallback.onTransitionFinished(null /* wct */, null /* t */);
}
@@ -220,7 +254,20 @@ public class KeyguardService extends Service {
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
-
+ try {
+ final IRemoteTransitionFinishedCallback origFinishCB;
+ synchronized (mFinishCallbacks) {
+ origFinishCB = mFinishCallbacks.remove(transition);
+ }
+ if (origFinishCB == null) {
+ // already finished (or not started yet), so do nothing.
+ return;
+ }
+ runner.onAnimationCancelled();
+ origFinishCB.onTransitionFinished(null /* wct */, null /* t */);
+ } catch (RemoteException e) {
+ // nothing, we'll just let it finish on its own I guess.
+ }
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 99b57200a397..382323f3aed9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -26,6 +26,7 @@ import android.os.Handler
import android.os.RemoteException
import android.util.Log
import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -293,6 +294,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
private val handler = Handler()
+ private val tmpFloat = FloatArray(9)
+
init {
with(surfaceBehindAlphaAnimator) {
duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
@@ -723,13 +726,27 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount
else surfaceBehindAlpha
- applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget!!.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build())
+ // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable
+ // to draw
+ val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash
+ if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+ setCornerRadius(sc, roundedCornerRadius)
+ setAlpha(sc, animationAlpha)
+ apply()
+ }
+ } else {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget!!.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
+ )
+ }
}
/**
@@ -744,8 +761,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
handler.removeCallbacksAndMessages(null)
// Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. If the launcher is doing its own animation, let it continue without
- // forcing it to 1f.
+ // fully visible. The exit animation is finished, and we should not hold the leash anymore,
+ // so forcing it to 1f.
+ surfaceBehindAlphaAnimator.cancel()
+ surfaceBehindEntryAnimator.cancel()
+ surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
@@ -910,4 +930,4 @@ class KeyguardUnlockAnimationController @Inject constructor(
return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 72dd36fec349..de8b0cfa495a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2497,10 +2497,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("DismissPanel"));
+ // Apply the opening animation on root task if exists
+ RemoteAnimationTarget aniTarget = apps[0];
+ for (RemoteAnimationTarget tmpTarget : apps) {
+ if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) {
+ aniTarget = tmpTarget;
+ break;
+ }
+ }
// Pass the surface and metadata to the unlock animation controller.
mKeyguardUnlockAnimationControllerLazy.get()
.notifyStartSurfaceBehindRemoteAnimation(
- apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
+ aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("RemoteAnimationDisabled"));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 892c28306f46..0288c9fce64a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -342,7 +342,8 @@ class FgsManagerController @Inject constructor(
}
val addedPackages = runningServiceTokens.keys.filter {
- it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
+ currentProfileIds.contains(it.userId) &&
+ it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
}
val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index d3ae198e8e35..236ba1f92d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -56,6 +56,8 @@ interface SmartspaceViewComponent {
):
BcSmartspaceDataPlugin.SmartspaceView {
val ssView = plugin.getView(parent)
+ // Currently, this is only used to provide SmartspaceView on Dream surface.
+ ssView.setIsDreaming(true)
ssView.registerDataProvider(plugin)
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
@@ -81,4 +83,4 @@ interface SmartspaceViewComponent {
return ssView
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 86f9fa155179..9e029095ea6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -407,7 +408,7 @@ public class KeyguardIndicationController {
organizationName);
} else {
return mDevicePolicyManager.getResources().getString(
- KEYGUARD_MANAGEMENT_DISCLOSURE,
+ KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE,
() -> packageResources.getString(
R.string.do_disclosure_with_name, organizationName),
organizationName);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index 9e8b35af1bce..1494574b26f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -68,4 +68,4 @@ class RowAppearanceCoordinator @Inject internal constructor(
// Show the "alerted" bell icon
controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
index 2148d3bb336a..82c7aae08f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.collection.provider
import android.content.Context
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index 50e7d1ce4ba0..7b9483022fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -43,4 +43,4 @@ class SectionStyleProvider @Inject constructor() {
fun isMinimizedSection(section: NotifSection): Boolean {
return lowPrioritySections.contains(section.sectioner)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index 34d25cf9c3be..d234e54e6725 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -107,4 +107,4 @@ class NodeSpecBuilder(
.apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
else -> throw RuntimeException("Unexpected entry: $entry")
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 954f74ccc0d2..4e7624cf4994 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -276,11 +276,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final NotificationIconAreaController mNotificationIconAreaController;
- // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
- // changed.
- private static final int CAP_HEIGHT = 1456;
- private static final int FONT_HEIGHT = 2163;
-
/**
* Maximum time before which we will expand the panel even for slow motions when getting a
* touch passed over from launcher.
@@ -1123,13 +1118,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- /**
- * Returns if there's a custom clock being presented.
- */
- public boolean hasCustomClock() {
- return mKeyguardStatusViewController.hasCustomClock();
- }
-
private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
// TODO: this can be injected.
mCentralSurfaces = centralSurfaces;
@@ -4008,9 +3996,10 @@ public class NotificationPanelViewController extends PanelViewController {
endAction.run();
}
})
+ .setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ })
.start();
-
- mKeyguardStatusViewController.animateFoldToAod();
}
/**
@@ -4872,7 +4861,6 @@ public class NotificationPanelViewController extends PanelViewController {
public void onDozeAmountChanged(float linearAmount, float amount) {
mInterpolatedDarkAmount = amount;
mLinearDarkAmount = linearAmount;
- mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
positionClockAndNotifications();
}
@@ -4981,11 +4969,8 @@ public class NotificationPanelViewController extends PanelViewController {
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
- // Update Clock Pivot
- mKeyguardStatusViewController.setPivotX(mView.getWidth() / 2);
- mKeyguardStatusViewController.setPivotY(
- (FONT_HEIGHT - CAP_HEIGHT) / 2048f
- * mKeyguardStatusViewController.getClockTextSize());
+ // Update Clock Pivot (used by anti-burnin transformations)
+ mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 670ba96ca1ff..25892d9db080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
import android.animation.Animator;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Fragment;
@@ -395,14 +394,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
state |= DISABLE_CLOCK;
}
-
- // The shelf will be hidden when dozing with a custom clock, we must show notification
- // icons in this occasion.
- if (mStatusBarStateController.isDozing()
- && mNotificationPanelViewController.hasCustomClock()) {
- state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
- }
-
if (mOngoingCallController.hasOngoingCall()) {
state &= ~DISABLE_ONGOING_CALL_CHIP;
} else {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
new file mode 100644
index 000000000000..4f3995252b54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 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.keyguard
+
+import android.content.BroadcastReceiver
+import android.testing.AndroidTestingRunner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import java.util.TimeZone
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockEventControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var animations: ClockAnimations
+ @Mock private lateinit var events: ClockEvents
+ @Mock private lateinit var clock: Clock
+
+ private lateinit var clockEventController: ClockEventController
+
+ @Before
+ fun setUp() {
+ whenever(clock.smallClock).thenReturn(TextView(context))
+ whenever(clock.largeClock).thenReturn(TextView(context))
+ whenever(clock.events).thenReturn(events)
+ whenever(clock.animations).thenReturn(animations)
+
+ clockEventController = ClockEventController(
+ statusBarStateController,
+ broadcastDispatcher,
+ batteryController,
+ keyguardUpdateMonitor,
+ configurationController,
+ context.resources,
+ context
+ )
+ }
+
+ @Test
+ fun clockSet_validateInitialization() {
+ clockEventController.clock = clock
+
+ verify(clock).initialize(any(), anyFloat(), anyFloat())
+ }
+
+ @Test
+ fun clockUnset_validateState() {
+ clockEventController.clock = clock
+ clockEventController.clock = null
+
+ assertEquals(clockEventController.clock, null)
+ }
+
+ @Test
+ fun themeChanged_verifyClockPaletteUpdated() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
+ captor.value.onThemeChanged()
+
+ verify(events).onColorPaletteChanged(any())
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, times(1)).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(false)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, never()).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, false)
+
+ verify(animations, never()).charge()
+ }
+
+ @Test
+ fun localeCallback_verifyClockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(broadcastDispatcher).registerReceiver(
+ capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
+ )
+ captor.value.onReceive(context, mock())
+
+ verify(events).onLocaleChanged(any())
+ }
+
+ @Test
+ fun keyguardCallback_visibilityChanged_clockDozeCalled() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+
+ captor.value.onKeyguardVisibilityChanged(true)
+ verify(animations, never()).doze(0f)
+
+ captor.value.onKeyguardVisibilityChanged(false)
+ verify(animations, times(1)).doze(0f)
+ }
+
+ @Test
+ fun keyguardCallback_timeFormat_clockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onTimeFormatChanged("12h")
+
+ verify(events).onTimeFormatChanged(false)
+ }
+
+ @Test
+ fun keyguardCallback_timezoneChanged_clockNotified() {
+ val mockTimeZone = mock<TimeZone>()
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onTimeZoneChanged(mockTimeZone)
+
+ verify(events).onTimeZoneChanged(mockTimeZone)
+ }
+
+ @Test
+ fun keyguardCallback_userSwitched_clockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onUserSwitchComplete(10)
+
+ verify(events).onTimeFormatChanged(false)
+ }
+
+ @Test
+ fun keyguardCallback_verifyKeyguardChanged() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(capture(captor))
+ captor.value.onDozeAmountChanged(0.4f, 0.6f)
+
+ verify(animations).doze(0.4f)
+ }
+
+ @Test
+ fun unregisterListeners_validate() {
+ clockEventController.unregisterListeners()
+ verify(broadcastDispatcher).unregisterReceiver(any())
+ verify(configurationController).removeCallback(any())
+ verify(batteryController).removeCallback(any())
+ verify(keyguardUpdateMonitor).removeCallback(any())
+ verify(statusBarStateController).removeCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 8b9a1e022d26..cd2a43fc6a44 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -41,23 +40,18 @@ import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -79,22 +73,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
- private SysuiColorExtractor mColorExtractor;
- @Mock
- private ClockManager mClockManager;
+ private ClockRegistry mClockRegistry;
@Mock
KeyguardSliceViewController mKeyguardSliceViewController;
@Mock
NotificationIconAreaController mNotificationIconAreaController;
@Mock
- BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- BatteryController mBatteryController;
- @Mock
- KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- KeyguardBypassController mBypassController;
- @Mock
LockscreenSmartspaceController mSmartspaceController;
@Mock
@@ -102,11 +86,11 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- private ClockPlugin mClockPlugin;
- @Mock
- ColorExtractor.GradientColors mGradientColors;
+ private Clock mClock;
@Mock
DumpManager mDumpManager;
+ @Mock
+ ClockEventController mClockEventController;
@Mock
private NotificationIconContainer mNotificationIcons;
@@ -136,8 +120,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mView.getContext()).thenReturn(getContext());
when(mView.getResources()).thenReturn(mResources);
- when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
- when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
when(mClockView.getContext()).thenReturn(getContext());
when(mLargeClockView.getContext()).thenReturn(getContext());
@@ -148,23 +130,19 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mController = new KeyguardClockSwitchController(
mView,
mStatusBarStateController,
- mColorExtractor,
- mClockManager,
+ mClockRegistry,
mKeyguardSliceViewController,
mNotificationIconAreaController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
mSmartspaceController,
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
- mResources,
- mDumpManager
+ mDumpManager,
+ mClockEventController
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
+ when(mClockRegistry.createCurrentClock()).thenReturn(mClock);
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -211,20 +189,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mColorExtractor).removeOnColorsChangedListener(
- any(ColorExtractor.OnColorsChangedListener.class));
+ verify(mClockEventController).unregisterListeners();
}
@Test
public void testPluginPassesStatusBarState() {
- ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
+ ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
mController.init();
- verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
+ verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
- listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
- verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
+ listenerArgumentCaptor.getValue().onClockChanged();
+ verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE);
+ verify(mClockEventController, times(2)).setClock(mClock);
}
@Test
@@ -281,10 +259,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
}
private void verifyAttachment(VerificationMode times) {
- verify(mClockManager, times).addOnClockChangedListener(
- any(ClockManager.ClockChangedListener.class));
- verify(mColorExtractor, times).addOnColorsChangedListener(
- any(ColorExtractor.OnColorsChangedListener.class));
- verify(mView, times).updateColors(mGradientColors);
+ verify(mClockRegistry, times).registerClockChangeListener(
+ any(ClockRegistry.ClockChangeListener.class));
+ verify(mClockEventController, times).registerListeners();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 6c6f0acd7085..a0295d09826f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -24,56 +23,61 @@ import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.TestCase.assertEquals;
+
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.TextClock;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
// the main thread before acquiring a wake lock. This class is constructed when
-// the keyguard_clcok_switch layout is inflated.
+// the keyguard_clock_switch layout is inflated.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardClockSwitchTest extends SysuiTestCase {
- private FrameLayout mClockFrame;
+ @Mock
+ ViewGroup mMockKeyguardSliceView;
+
+ @Mock
+ Clock mClock;
+
+ private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
- private TextClock mBigClock;
- private AnimatableClockView mClockView;
- private AnimatableClockView mLargeClockView;
- View mMockKeyguardSliceView;
KeyguardClockSwitch mKeyguardClockSwitch;
@Before
public void setUp() {
- mMockKeyguardSliceView = mock(KeyguardSliceView.class);
+ MockitoAnnotations.initMocks(this);
when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
+ when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
+
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -93,164 +97,68 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
});
mKeyguardClockSwitch =
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
- mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
- mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view);
+ mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
- mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large);
- mBigClock = new TextClock(getContext());
mKeyguardClockSwitch.mChildrenAreLaidOut = true;
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void onPluginConnected_showPluginClock() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
-
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- assertThat(mClockView.getVisibility()).isEqualTo(GONE);
- assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame);
}
@Test
- public void onPluginConnected_showPluginBigClock() {
- // GIVEN the plugin returns a view for the big clock
- ClockPlugin plugin = mock(ClockPlugin.class);
- when(plugin.getBigClockView()).thenReturn(mBigClock);
- // WHEN the plugin is connected
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- // THEN the big clock container is visible and it is the parent of the
- // big clock view.
- assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame);
+ public void noPluginConnected_showNothing() {
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 0);
+ assertEquals(mSmallClockFrame.getChildCount(), 0);
}
@Test
- public void onPluginConnected_nullView() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
- }
-
- @Test
- public void onPluginConnected_showSecondPluginClock() {
- // GIVEN a plugin has already connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
- // WHEN a second plugin is connected
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
- // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame);
- assertThat(plugin1.getView().getParent()).isNull();
- }
+ public void pluginConnectedThenDisconnected_showNothing() {
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 1);
+ assertEquals(mSmallClockFrame.getChildCount(), 1);
- @Test
- public void onPluginConnected_darkAmountInitialized() {
- // GIVEN that the dark amount has already been set
- mKeyguardClockSwitch.setDarkAmount(0.5f);
- // WHEN a plugin is connected
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- // THEN dark amount should be initalized on the plugin.
- verify(plugin).setDarkAmount(0.5f);
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 0);
+ assertEquals(mSmallClockFrame.getChildCount(), 0);
}
@Test
- public void onPluginDisconnected_showDefaultClock() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
-
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(GONE);
+ public void onPluginConnected_showClock() {
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
-
- assertThat(plugin.getView().getParent()).isNull();
+ assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
+ assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
}
@Test
- public void onPluginDisconnected_hidePluginBigClock() {
- // GIVEN the plugin returns a view for the big clock
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getBigClockView()).thenReturn(pluginView);
- // WHEN the plugin is connected and then disconnected
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN the big lock container is GONE and the big clock view doesn't have
- // a parent.
- assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE);
- assertThat(pluginView.getParent()).isNull();
- }
+ public void onPluginConnected_showSecondPluginClock() {
+ // GIVEN a plugin has already connected
+ Clock otherClock = mock(Clock.class);
+ when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
- @Test
- public void onPluginDisconnected_nullView() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+ // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
+ assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
+ assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
+ assertThat(mClock.getSmallClock().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getParent()).isNull();
}
@Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
+ Clock otherClock = mock(Clock.class);
+ when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
// WHEN the second plugin is disconnected
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN the default clock should be shown.
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
- assertThat(plugin1.getView().getParent()).isNull();
- assertThat(plugin2.getView().getParent()).isNull();
- }
-
- @Test
- public void onPluginDisconnected_onDestroyView() {
- // GIVEN a plugin is connected
- ClockPlugin clockPlugin = mock(ClockPlugin.class);
- when(clockPlugin.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD);
- // WHEN the plugin is disconnected
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN onDestroyView is called on the plugin
- verify(clockPlugin).onDestroyView();
- }
-
- @Test
- public void setTextColor_pluginClockSetTextColor() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- mKeyguardClockSwitch.setTextColor(Color.WHITE);
-
- verify(plugin).setTextColor(Color.WHITE);
- }
-
-
- @Test
- public void setStyle_pluginClockSetStyle() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
- Style style = mock(Style.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- mKeyguardClockSwitch.setStyle(style);
-
- verify(plugin).setStyle(style);
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ // THEN nothing should be shown
+ assertThat(otherClock.getSmallClock().getParent()).isNull();
+ assertThat(otherClock.getLargeClock().getParent()).isNull();
+ assertThat(mClock.getSmallClock().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getParent()).isNull();
}
@Test
@@ -262,7 +170,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -271,7 +179,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -281,8 +189,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
- assertThat(mClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
@@ -292,8 +200,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
mKeyguardClockSwitch.switchToClock(SMALL, false);
- assertThat(mClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
index dc1ae0e93757..964e6d79f0bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
@@ -72,19 +72,67 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
}
/**
- * Ensures {@link SmartSpaceComplication} is only registered when it is available.
+ * Ensures {@link SmartSpaceComplication} isn't registered right away on start.
*/
@Test
- public void testAvailability() {
+ public void testRegistrantStart_doesNotAddComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
+ }
- final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
+ private SmartSpaceComplication.Registrant getRegistrant() {
+ return new SmartSpaceComplication.Registrant(
mContext,
mDreamOverlayStateController,
mComplication,
mSmartspaceController);
+ }
+
+ @Test
+ public void testOverlayActive_addsTargetListener() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
- verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ // Test
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+ }
+
+ @Test
+ public void testOverlayActive_targetsNonEmpty_addsComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+
+ // Test
+ final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testOverlayActive_targetsEmpty_removesComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
@@ -100,10 +148,41 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ // Test
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList());
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testOverlayInActive_removesTargetListener_removesComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+
+ final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ // Test
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(false);
+ dreamCallbackCaptor.getValue().onStateChanged();
+ verify(mSmartspaceController).removeListener(listenerCaptor.getValue());
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
}
@Test
- public void testGetViewReusesSameView() {
+ public void testGetView_reusesSameView() {
final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
mSmartspaceController);
final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index d61989fc3128..136a395196e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -129,13 +129,16 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(list, listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2"),
- ClockMetadata("clock_3", "clock 3"),
- ClockMetadata("clock_4", "clock 4")
- ))
+ assertEquals(
+ list,
+ listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ )
+ )
}
@Test
@@ -157,11 +160,14 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(list, listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2")
- ))
+ assertEquals(
+ list,
+ listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ )
+ )
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
@@ -221,7 +227,7 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin2, mockContext)
var changeCallCount = 0
- registry.registerClockChangeListener({ changeCallCount++ })
+ registry.registerClockChangeListener { changeCallCount++ }
pluginListener.onPluginDisconnected(plugin1)
assertEquals(0, changeCallCount)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 2f0f0a0a1b8f..37f96c8d7023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -36,17 +36,17 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Spy
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.concurrent.Executor
+import org.mockito.Spy
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -87,6 +87,34 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
private lateinit var controller: DreamSmartspaceController
+ /**
+ * A class which implements SmartspaceView and extends View. This is mocked to provide the right
+ * object inheritance and interface implementation used in DreamSmartspaceController
+ */
+ private class TestView(context: Context?) : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
+
+ override fun setPrimaryTextColor(color: Int) {}
+
+ override fun setIsDreaming(isDreaming: Boolean) {}
+
+ override fun setDozeAmount(amount: Float) {}
+
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
+
+ override fun setFalsingManager(falsingManager: FalsingManager?) {}
+
+ override fun setDnd(image: Drawable?, description: String?) {}
+
+ override fun setNextAlarm(image: Drawable?, description: String?) {}
+
+ override fun setMediaTarget(target: SmartspaceTarget?) {}
+
+ override fun getSelectedPage(): Int { return 0; }
+
+ override fun getCurrentCardTopPadding(): Int { return 0; }
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -130,34 +158,6 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
}
/**
- * A class which implements SmartspaceView and extends View. This is mocked to provide the right
- * object inheritance and interface implementation used in DreamSmartspaceController
- */
- private class TestView(context: Context?) : View(context), SmartspaceView {
- override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
-
- override fun setPrimaryTextColor(color: Int) {}
-
- override fun setIsDreaming(isDreaming: Boolean) {}
-
- override fun setDozeAmount(amount: Float) {}
-
- override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
-
- override fun setFalsingManager(falsingManager: FalsingManager?) {}
-
- override fun setDnd(image: Drawable?, description: String?) {}
-
- override fun setNextAlarm(image: Drawable?, description: String?) {}
-
- override fun setMediaTarget(target: SmartspaceTarget?) {}
-
- override fun getSelectedPage(): Int { return 0; }
-
- override fun getCurrentCardTopPadding(): Int { return 0; }
- }
-
- /**
* Ensures session begins when a view is attached.
*/
@Test
@@ -180,16 +180,4 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(session).close()
}
-
- /**
- * Ensures setIsDreaming(true) is called when the view is built.
- */
- @Test
- fun testSetIsDreamingTrueOnViewCreate() {
- `when`(precondition.conditionsMet()).thenReturn(true)
-
- controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
-
- verify(smartspaceView).setIsDreaming(true)
- }
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
deleted file mode 100644
index 6171e2f760d3..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 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.systemui.statusbar.notification.collection.coordinator;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Handler;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * TODO(b/224771204) Create test cases
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@Ignore
-public class KeyguardCoordinatorTest extends SysuiTestCase {
- private static final int NOTIF_USER_ID = 0;
- private static final int CURR_USER_ID = 1;
-
- @Mock private Handler mMainHandler;
- @Mock private KeyguardStateController mKeyguardStateController;
- @Mock private BroadcastDispatcher mBroadcastDispatcher;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- @Mock private NotifPipeline mNotifPipeline;
- @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
-
- private NotificationEntry mEntry;
- private NotifFilter mKeyguardFilter;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator(
- mKeyguardNotificationVisibilityProvider,
- mSectionHeaderVisibilityProvider,
- mock(SharedCoordinatorLogger.class),
- mStatusBarStateController);
-
- mEntry = new NotificationEntryBuilder()
- .setUser(new UserHandle(NOTIF_USER_ID))
- .build();
-
- ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- keyguardCoordinator.attach(mNotifPipeline);
- verify(mNotifPipeline, times(1)).addFinalizeFilter(filterCaptor.capture());
- mKeyguardFilter = filterCaptor.getValue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
new file mode 100644
index 000000000000..8c506a6d16ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class KeyguardCoordinatorTest : SysuiTestCase() {
+ private val notifPipeline: NotifPipeline = mock()
+ private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
+ private val sharedCoordinatorLogger: SharedCoordinatorLogger = mock()
+ private val statusBarStateController: StatusBarStateController = mock()
+
+ private lateinit var onStateChangeListener: Consumer<String>
+ private lateinit var keyguardFilter: NotifFilter
+
+ @Before
+ fun setup() {
+ val keyguardCoordinator = KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ sectionHeaderVisibilityProvider,
+ sharedCoordinatorLogger,
+ statusBarStateController
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ onStateChangeListener = withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+ keyguardFilter = withArgCaptor {
+ verify(notifPipeline).addFinalizeFilter(capture())
+ }
+ }
+
+ @Test
+ fun testSetSectionHeadersVisibleInShade() {
+ clearInvocations(sectionHeaderVisibilityProvider)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ onStateChangeListener.accept("state change")
+ verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(true)
+ }
+
+ @Test
+ fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ clearInvocations(sectionHeaderVisibilityProvider)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ onStateChangeListener.accept("state change")
+ verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
index 40859d0e6304..3f3de009fb04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
@@ -37,8 +37,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidTestingRunner::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index ff601938d544..ac254abe60b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -413,4 +413,4 @@ private fun buildSection(
return nodeController
}
}, index)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index b94aac21acad..3d03c4750869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -223,10 +223,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() {
+ public void disable_isDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -235,10 +234,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_customClockButNotDozing_clockAndSystemInfoVisible() {
+ public void disable_NotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -247,40 +245,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() {
- CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
-
- // Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.VISIBLE, getClockView().getVisibility());
-
- fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
-
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.GONE, getClockView().getVisibility());
- }
-
- @Test
- public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() {
- CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
-
- // Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.VISIBLE, getClockView().getVisibility());
-
- fragment.onDozingChanged(true);
-
- // When this callback is triggered, we want to make sure the clock and system info
- // visibilities are recalculated. Since dozing=true, they shouldn't be visible.
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.GONE, getClockView().getVisibility());
- }
-
- @Test
public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index 229799a8457d..d5991d3930a8 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -73,6 +73,9 @@ class AssociationStoreImpl implements AssociationStore {
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
void addAssociation(@NonNull AssociationInfo association) {
+ // Validity check first.
+ checkNotRevoked(association);
+
final int id = association.getId();
if (DEBUG) {
@@ -99,6 +102,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void updateAssociation(@NonNull AssociationInfo updated) {
+ // Validity check first.
+ checkNotRevoked(updated);
+
final int id = updated.getId();
if (DEBUG) {
@@ -292,6 +298,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void setAssociations(Collection<AssociationInfo> allAssociations) {
+ // Validity check first.
+ allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+
if (DEBUG) {
Log.i(TAG, "setAssociations() n=" + allAssociations.size());
final StringJoiner stringJoiner = new StringJoiner(", ");
@@ -324,4 +333,11 @@ class AssociationStoreImpl implements AssociationStore {
mAddressMap.clear();
mCachedPerUser.clear();
}
+
+ private static void checkNotRevoked(@NonNull AssociationInfo association) {
+ if (association.isRevoked()) {
+ throw new IllegalArgumentException(
+ "Revoked (removed) associations MUST NOT appear in the AssociationStore");
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 04468ed47640..ab9966f218c1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.companion;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -49,6 +50,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@@ -93,6 +96,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.infra.PerUser;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
@@ -110,6 +114,7 @@ import com.android.server.pm.UserManagerInternal;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -133,6 +138,9 @@ public class CompanionDeviceManagerService extends SystemService {
private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
+ private final ActivityManager mActivityManager;
+ private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
+
private PersistentDataStore mPersistentStore;
private final PersistUserStateHandler mUserPersistenceHandler;
@@ -160,12 +168,40 @@ public class CompanionDeviceManagerService extends SystemService {
@GuardedBy("mPreviouslyUsedIds")
private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
+ /**
+ * A structure that consists of a set of revoked associations that pending for role holder
+ * removal per each user.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
+ new PerUserAssociationSet();
+ /**
+ * Contains uid-s of packages pending to be removed from the role holder list (after
+ * revocation of an association), which will happen one the package is no longer visible to the
+ * user.
+ * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
+ * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
+ * from uid-s using {@link UserHandle#getUserId(int)}).
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
+
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>();
public CompanionDeviceManagerService(Context context) {
super(context);
+ mActivityManager = context.getSystemService(ActivityManager.class);
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -176,6 +212,9 @@ public class CompanionDeviceManagerService extends SystemService {
mUserPersistenceHandler = new PersistUserStateHandler();
mAssociationStore = new AssociationStoreImpl();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
+
+ mOnPackageVisibilityChangeListener =
+ new OnPackageVisibilityChangeListener(mActivityManager);
}
@Override
@@ -217,7 +256,33 @@ public class CompanionDeviceManagerService extends SystemService {
mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
}
- mAssociationStore.setAssociations(allAssociations);
+ final Set<AssociationInfo> activeAssociations =
+ new ArraySet<>(/* capacity */ allAssociations.size());
+ // A set contains the userIds that need to persist state after remove the app
+ // from the list of role holders.
+ final Set<Integer> usersToPersistStateFor = new ArraySet<>();
+
+ for (AssociationInfo association : allAssociations) {
+ if (!association.isRevoked()) {
+ activeAssociations.add(association);
+ } else if (maybeRemoveRoleHolderForAssociation(association)) {
+ // Nothing more to do here, but we'll need to persist all the associations to the
+ // disk afterwards.
+ usersToPersistStateFor.add(association.getUserId());
+ } else {
+ addToPendingRoleHolderRemoval(association);
+ }
+ }
+
+ mAssociationStore.setAssociations(activeAssociations);
+
+ // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
+ // persistStateForUser() queries AssociationStore.
+ // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it
+ // would effectively just clear-out all the persisted associations).
+ for (int userId : usersToPersistStateFor) {
+ persistStateForUser(userId);
+ }
}
@Override
@@ -367,10 +432,18 @@ public class CompanionDeviceManagerService extends SystemService {
}
private void persistStateForUser(@UserIdInt int userId) {
- final List<AssociationInfo> updatedAssociations =
- mAssociationStore.getAssociationsForUser(userId);
+ // We want to store both active associations and the revoked (removed) association that we
+ // are keeping around for the final clean-up (delayed role holder removal).
+ final List<AssociationInfo> allAssociations;
+ // Start with the active associations - these we can get from the AssociationStore.
+ allAssociations = new ArrayList<>(
+ mAssociationStore.getAssociationsForUser(userId));
+ // ... and add the revoked (removed) association, that are yet to be permanently removed.
+ allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
- mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser);
+
+ mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -438,13 +511,17 @@ public class CompanionDeviceManagerService extends SystemService {
removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
}
- for (AssociationInfo ai : mAssociationStore.getAssociations()) {
- if (!ai.isSelfManaged()) continue;
- final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow;
- if (isInactive) {
- Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId());
- disassociateInternal(ai.getId());
- }
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association id=" + id);
+ disassociateInternal(id);
}
}
@@ -712,7 +789,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerIsSystemOr(userId, packageName);
AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
- userId, packageName, deviceAddress);
+ userId, packageName, deviceAddress);
if (association == null) {
throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
@@ -772,7 +849,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
checkState(!ArrayUtils.isEmpty(
- mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
+ mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
"App must have an association before calling this API");
}
@@ -832,8 +909,8 @@ public class CompanionDeviceManagerService extends SystemService {
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- macAddress, displayName, deviceProfile, selfManaged, false, timestamp,
- Long.MAX_VALUE);
+ macAddress, displayName, deviceProfile, selfManaged,
+ /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
Slog.i(TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
@@ -845,6 +922,11 @@ public class CompanionDeviceManagerService extends SystemService {
updateSpecialAccessPermissionForAssociatedPackage(association);
logCreateAssociation(deviceProfile);
+
+ // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
+ // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
+ // that there are other devices with the same profile, so the role holder won't be removed.
+
return association;
}
@@ -925,39 +1007,187 @@ public class CompanionDeviceManagerService extends SystemService {
final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile();
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Need to remove the app from list of the role holders, but will have to do it later
+ // (the app is in foreground at the moment).
+ addToPendingRoleHolderRemoval(association);
+ }
+
+ // Need to check if device still present now because CompanionDevicePresenceMonitor will
+ // remove current connected device after mAssociationStore.removeAssociation
final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
// Removing the association.
mAssociationStore.removeAssociation(associationId);
+ // Do not need to persistUserState since CompanionDeviceManagerService will get callback
+ // from #onAssociationChanged, and it will handle the persistUserState which including
+ // active and revoked association.
logRemoveAssociation(deviceProfile);
// Remove all the system data transfer requests for the association.
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
- final List<AssociationInfo> otherAssociations =
- mAssociationStore.getAssociationsForPackage(userId, packageName);
-
- // Check if the package is associated with other devices with the same profile.
- // If not: take away the role.
- if (deviceProfile != null) {
- final boolean shouldKeepTheRole = any(otherAssociations,
- it -> deviceProfile.equals(it.getDeviceProfile()));
- if (!shouldKeepTheRole) {
- Binder.withCleanCallingIdentity(() ->
- removeRoleHolderForAssociation(getContext(), association));
- }
- }
-
if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
// The device was connected and the app was notified: check if we need to unbind the app
// now.
- final boolean shouldStayBound = any(otherAssociations,
+ final boolean shouldStayBound = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (shouldStayBound) return;
mCompanionAppController.unbindCompanionApplication(userId, packageName);
}
+ /**
+ * First, checks if the companion application should be removed from the list role holders when
+ * upon association's removal, i.e.: association's profile (matches the role) is not null,
+ * the application does not have other associations with the same profile, etc.
+ *
+ * <p>
+ * Then, if establishes that the application indeed has to be removed from the list of the role
+ * holders, checks if it could be done right now -
+ * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
+ * will kill the application's process, which leads poor user experience if the application was
+ * in foreground when this happened, to avoid this CDMS delays invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
+ *
+ * @return {@code true} if the application does NOT need be removed from the list of the role
+ * holders OR if the application was successfully removed from the list of role holders.
+ * I.e.: from the role-management perspective the association is done with.
+ * {@code false} if the application needs to be removed from the list of role the role
+ * holders, BUT it CDMS would prefer to do it later.
+ * I.e.: application is in the foreground at the moment, but invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
+ * which would lead to the poor UX, hence need to try later.
+ */
+
+ private boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (deviceProfile == null) {
+ // No role was granted to for this association, there is nothing else we need to here.
+ return true;
+ }
+
+ // Check if the applications is associated with another devices with the profile. If so,
+ // it should remain the role holder.
+ final int id = association.getId();
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final boolean roleStillInUse = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
+ if (roleStillInUse) {
+ // Application should remain a role holder, there is nothing else we need to here.
+ return true;
+ }
+
+ final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
+ if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
+ // Need to remove the app from the list of role holders, but the process is visible to
+ // the user at the moment, so we'll need to it later: log and return false.
+ Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
+ + " now - process is visible.");
+ return false;
+ }
+
+ removeRoleHolderForAssociation(getContext(), association);
+ return true;
+ }
+
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
+ * Set revoked flag for active association and add the revoked association and the uid into
+ * the caches.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ // First: set revoked flag.
+ association = AssociationInfo.builder(association)
+ .setRevoked(true)
+ .build();
+
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+
+ // Second: add to the set.
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
+ .add(association);
+ if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
+ mUidsPendingRoleHolderRemoval.put(uid, packageName);
+
+ if (mUidsPendingRoleHolderRemoval.size() == 1) {
+ // Just added first uid: start the listener
+ mOnPackageVisibilityChangeListener.startListening();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the revoked association form the cache and also remove the uid form the map if
+ * there are other associations with the same package still pending for role holder removal.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
+ .remove(association);
+
+ final boolean shouldKeepUidForRemoval = any(
+ getPendingRoleHolderRemovalAssociationsForUser(userId),
+ ai -> packageName.equals(ai.getPackageName()));
+ // Do not remove the uid form the map since other associations with
+ // the same packageName still pending for role holder removal.
+ if (!shouldKeepUidForRemoval) {
+ mUidsPendingRoleHolderRemoval.remove(uid);
+ }
+
+ if (mUidsPendingRoleHolderRemoval.isEmpty()) {
+ // The set is empty now - can "turn off" the listener.
+ mOnPackageVisibilityChangeListener.stopListening();
+ }
+ }
+ }
+
+ /**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ private String getPackageNameByUid(int uid) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ return mUidsPendingRoleHolderRemoval.get(uid);
+ }
+ }
+
private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
@@ -1175,4 +1405,80 @@ public class CompanionDeviceManagerService extends SystemService {
persistStateForUser(userId);
}
}
+
+ /**
+ * An OnUidImportanceListener class which watches the importance of the packages.
+ * In this class, we ONLY interested in the importance of the running process is greater than
+ * {@link RunningAppProcessInfo.IMPORTANCE_VISIBLE} for the uids have been added into the
+ * {@link mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the revoked
+ * associations for the same packages.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ private class OnPackageVisibilityChangeListener implements
+ ActivityManager.OnUidImportanceListener {
+ final @NonNull ActivityManager mAm;
+
+ OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
+ this.mAm = am;
+ }
+
+ void startListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.addOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this,
+ RunningAppProcessInfo.IMPORTANCE_VISIBLE));
+ }
+
+ void stopListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.removeOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this));
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (importance <= RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
+ // The lower the importance value the more "important" the process is.
+ // We are only interested when the process ceases to be visible.
+ return;
+ }
+
+ final String packageName = getPackageNameByUid(uid);
+ if (packageName == null) {
+ // Not interested in this uid.
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+
+ boolean needToPersistStateForUser = false;
+
+ for (AssociationInfo association :
+ getPendingRoleHolderRemovalAssociationsForUser(userId)) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Did not remove the role holder, will have to try again later.
+ continue;
+ }
+
+ removeFromPendingRoleHolderRemoval(association);
+ needToPersistStateForUser = true;
+ }
+
+ if (needToPersistStateForUser) {
+ mUserPersistenceHandler.postPersistUserState(userId);
+ }
+ }
+ }
+
+ private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ @Override
+ protected @NonNull Set<AssociationInfo> create(int userId) {
+ return new ArraySet<>();
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 4d42838fff50..4b56c1b28036 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -103,7 +103,7 @@ import java.util.concurrent.ConcurrentMap;
* Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
- * See {@link #getStorageFileForUser(int)}
+ * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
*
* <p>
* Since Android T the data is stored using the v1 schema.
@@ -120,7 +120,7 @@ import java.util.concurrent.ConcurrentMap;
* <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()}
* </ul>
*
- * The following snippet is a sample of a file that is using v0 schema.
+ * The following snippet is a sample of a file that is using v1 schema.
* <pre>{@code
* <state persistence-version="1">
* <associations>
@@ -130,6 +130,8 @@ import java.util.concurrent.ConcurrentMap;
* mac_address="AA:BB:CC:DD:EE:00"
* self_managed="false"
* notify_device_nearby="false"
+ * revoked="false"
+ * last_time_connected="1634641160229"
* time_approved="1634389553216"/>
*
* <association
@@ -139,6 +141,8 @@ import java.util.concurrent.ConcurrentMap;
* display_name="Jhon's Chromebook"
* self_managed="true"
* notify_device_nearby="false"
+ * revoked="false"
+ * last_time_connected="1634641160229"
* time_approved="1634641160229"/>
* </associations>
*
@@ -178,6 +182,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_PROFILE = "profile";
private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
+ private static final String XML_ATTR_REVOKED = "revoked";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
@@ -415,7 +420,8 @@ final class PersistentDataStore {
out.add(new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile,
- /* managedByCompanionApp */false, notify, timeApproved, Long.MAX_VALUE));
+ /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
+ Long.MAX_VALUE));
}
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -444,13 +450,14 @@ final class PersistentDataStore {
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
+ final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
- appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved,
- lastTimeConnected);
+ appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
+ timeApproved, lastTimeConnected);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -503,6 +510,8 @@ final class PersistentDataStore {
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
+ writeBooleanAttribute(
+ serializer, XML_ATTR_REVOKED, a.isRevoked());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -544,11 +553,12 @@ final class PersistentDataStore {
private static AssociationInfo createAssociationInfoNoThrow(int associationId,
@UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
- boolean notify, long timeApproved, long lastTimeConnected) {
+ boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) {
AssociationInfo associationInfo = null;
try {
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
- displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
+ displayName, profile, selfManaged, notify, revoked, timeApproved,
+ lastTimeConnected);
} catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 35488a80b78b..0fff3f488562 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -85,6 +85,8 @@ final class RolesUtils {
final int userId = associationInfo.getUserId();
final UserHandle userHandle = UserHandle.of(userId);
+ Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
+ + ", package=u" + userId + "\\" + packageName);
roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 395cf1805b63..70745ba0b368 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -24,8 +24,6 @@ import static android.content.ComponentName.createRelative;
import static com.android.server.companion.Utils.prepareForIpc;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
@@ -41,23 +39,17 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.UserHandle;
+import android.permission.PermissionControllerManager;
import android.util.Slog;
-import android.util.Xml;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
-import com.android.server.companion.datatransfer.permbackup.BackupHelper;
import com.android.server.companion.proto.CompanionMessage;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* This processor builds user consent intent for a given SystemDataTransferRequest and processes the
@@ -83,6 +75,8 @@ public class SystemDataTransferProcessor {
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private final CompanionMessageProcessor mCompanionMessageProcessor;
+ private final PermissionControllerManager mPermissionControllerManager;
+ private final ExecutorService mExecutor;
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
@@ -93,6 +87,8 @@ public class SystemDataTransferProcessor {
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mCompanionMessageProcessor = companionMessageProcessor;
mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
+ mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
+ mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -180,23 +176,13 @@ public class SystemDataTransferProcessor {
// TODO: Establish a secure channel
- final long callingIdentityToken = Binder.clearCallingIdentity();
-
// Start permission sync
+ final long callingIdentityToken = Binder.clearCallingIdentity();
try {
- BackupHelper backupHelper = new BackupHelper(mContext, UserHandle.of(userId));
- XmlSerializer serializer = Xml.newSerializer();
- ByteArrayOutputStream backup = new ByteArrayOutputStream();
- serializer.setOutput(backup, UTF_8.name());
-
- backupHelper.writeState(serializer);
-
- serializer.flush();
-
- mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup.toByteArray(),
- CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId);
- } catch (IOException ioe) {
- Slog.e(LOG_TAG, "Error while writing permission state.");
+ mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
+ mExecutor,
+ backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup,
+ CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId));
} finally {
Binder.restoreCallingIdentity(callingIdentityToken);
}
@@ -219,21 +205,11 @@ public class SystemDataTransferProcessor {
private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
+ UserHandle user = mContext.getUser();
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
- BackupHelper backupHelper = new BackupHelper(mContext, mContext.getUser());
- XmlPullParser parser = Xml.newPullParser();
- ByteArrayInputStream stream = new ByteArrayInputStream(
- messageInfo.getData());
- parser.setInput(stream, UTF_8.name());
-
- backupHelper.restoreState(parser);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "IOException reading message: "
- + new String(messageInfo.getData()));
- } catch (XmlPullParserException e) {
- Slog.e(LOG_TAG, "Error parsing message: "
- + new String(messageInfo.getData()));
+ mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
+ messageInfo.getData(), user);
} finally {
Slog.i(LOG_TAG, "Permissions applied.");
Binder.restoreCallingIdentity(callingIdentityToken);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java
deleted file mode 100644
index 5e3c4c7834fe..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java
+++ /dev/null
@@ -1,782 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup;
-
-import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
-
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.Signature;
-import android.content.pm.SigningInfo;
-import android.os.Build;
-import android.os.UserHandle;
-import android.permission.PermissionManager;
-import android.permission.PermissionManager.SplitPermissionInfo;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup;
-import com.android.server.companion.datatransfer.permbackup.model.AppPermissions;
-import com.android.server.companion.datatransfer.permbackup.model.Permission;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper for creating and restoring permission backups.
- */
-public class BackupHelper {
- private static final String LOG_TAG = BackupHelper.class.getSimpleName();
-
- private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
- private static final String ATTR_PLATFORM_VERSION = "version";
-
- private static final String TAG_ALL_GRANTS = "rt-grants";
-
- private static final String TAG_GRANT = "grant";
- private static final String ATTR_PACKAGE_NAME = "pkg";
- private static final String ATTR_HAS_MULTIPLE_SIGNERS = "multi-signers";
-
- private static final String TAG_SIGNATURE = "sig";
- private static final String ATTR_SIGNATURE_VALUE = "v";
-
- private static final String TAG_PERMISSION = "perm";
- private static final String ATTR_PERMISSION_NAME = "name";
- private static final String ATTR_IS_GRANTED = "g";
- private static final String ATTR_USER_SET = "set";
- private static final String ATTR_USER_FIXED = "fixed";
- private static final String ATTR_WAS_REVIEWED = "was-reviewed";
-
- /** Flags of permissions to <u>not</u> back up */
- private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED
- | FLAG_PERMISSION_SYSTEM_FIXED;
-
- /** Make sure only one user can change the delayed permissions at a time */
- private static final Object sLock = new Object();
-
- private final Context mContext;
-
- /**
- * Create a new backup utils for a user.
- *
- * @param context A context to use
- * @param user The user that is backed up / restored
- */
- public BackupHelper(@NonNull Context context, @NonNull UserHandle user) {
- try {
- mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user);
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- throw new IllegalStateException();
- }
- }
-
- /**
- * Forward parser and skip everything up to the end of the current tag.
- *
- * @param parser The parser to forward
- */
- private static void skipToEndOfTag(@NonNull XmlPullParser parser)
- throws IOException, XmlPullParserException {
- int numOpenTags = 1;
- while (numOpenTags > 0) {
- switch (parser.next()) {
- case START_TAG:
- numOpenTags++;
- break;
- case END_TAG:
- numOpenTags--;
- break;
- default:
- // ignore
- }
- }
- }
-
- /**
- * Forward parser to a given direct sub-tag.
- *
- * @param parser The parser to forward
- * @param tag The tag to search for
- */
- private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag)
- throws IOException, XmlPullParserException {
- int type;
- do {
- type = parser.next();
-
- switch (type) {
- case START_TAG:
- if (!parser.getName().equals(tag)) {
- skipToEndOfTag(parser);
- }
-
- return;
- }
- } while (type != END_DOCUMENT);
- }
-
- /**
- * Read a XML file and return the packages stored in it.
- *
- * @param parser The file to read
- *
- * @return The packages in this file
- */
- private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser)
- throws IOException, XmlPullParserException {
- ArrayList<BackupPackageState> pkgStates = new ArrayList<>();
-
- skipToTag(parser, TAG_PERMISSION_BACKUP);
-
- int backupPlatformVersion;
- try {
- backupPlatformVersion = Integer.parseInt(
- parser.getAttributeValue(null, ATTR_PLATFORM_VERSION));
- } catch (NumberFormatException ignored) {
- // Platforms P and before did not store the platform version
- backupPlatformVersion = Build.VERSION_CODES.P;
- }
-
- skipToTag(parser, TAG_ALL_GRANTS);
-
- if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) {
- throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > "
- + TAG_ALL_GRANTS);
- }
-
- // Read packages to restore from xml
- int type;
- do {
- type = parser.next();
-
- switch (type) {
- case START_TAG:
- switch (parser.getName()) {
- case TAG_GRANT:
- try {
- pkgStates.add(BackupPackageState.parseFromXml(parser, mContext,
- backupPlatformVersion));
- } catch (XmlPullParserException e) {
- Log.e(LOG_TAG, "Could not parse permissions ", e);
- skipToEndOfTag(parser);
- }
- break;
- default:
- // ignore tag
- Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
- + " during restore");
- skipToEndOfTag(parser);
- }
- }
- } while (type != END_DOCUMENT);
-
- return pkgStates;
- }
-
- /**
- * Try to restore the permission state from XML.
- *
- * @param parser The xml to read
- */
- public void restoreState(@NonNull XmlPullParser parser) throws IOException,
- XmlPullParserException {
- ArrayList<BackupPackageState> pkgStates = parseFromXml(parser);
-
- ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>();
- int numPkgStates = pkgStates.size();
- if (numPkgStates > 0) {
- // Try to restore packages
- for (int i = 0; i < numPkgStates; i++) {
- BackupPackageState pkgState = pkgStates.get(i);
-
- PackageInfo pkgInfo;
- try {
- pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName,
- GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
- } catch (PackageManager.NameNotFoundException ignored) {
- packagesToRestoreLater.add(pkgState);
- continue;
- }
-
- pkgState.restore(mContext, pkgInfo);
- }
- }
-
-// synchronized (sLock) {
-// writeDelayedStorePkgsLocked(packagesToRestoreLater);
-// }
- }
-
- /**
- * Write a xml file for the given packages.
- *
- * @param serializer The file to write to
- * @param pkgs The packages to write
- */
- private static void writePkgsAsXml(@NonNull XmlSerializer serializer,
- @NonNull ArrayList<BackupPackageState> pkgs) throws IOException {
- serializer.startDocument(null, true);
-
- serializer.startTag(null, TAG_PERMISSION_BACKUP);
-
-// if (SDK_INT >= Build.VERSION_CODES.Q) {
- // STOPSHIP: Remove compatibility code once Q SDK level is declared
- serializer.attribute(null, ATTR_PLATFORM_VERSION,
- Integer.valueOf(Build.VERSION_CODES.Q).toString());
-// } else {
-// serializer.attribute(null, ATTR_PLATFORM_VERSION,
-// Integer.valueOf(SDK_INT).toString());
-// }
-
- serializer.startTag(null, TAG_ALL_GRANTS);
-
- int numPkgs = pkgs.size();
- for (int i = 0; i < numPkgs; i++) {
- BackupPackageState packageState = pkgs.get(i);
-
- if (packageState != null) {
- packageState.writeAsXml(serializer);
- }
- }
-
- serializer.endTag(null, TAG_ALL_GRANTS);
- serializer.endTag(null, TAG_PERMISSION_BACKUP);
-
- serializer.endDocument();
- }
-
- /**
- * Write the state of all packages as XML.
- *
- * @param serializer The xml to write to
- */
- public void writeState(@NonNull XmlSerializer serializer) throws IOException {
- List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages(
- GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
- ArrayList<BackupPackageState> backupPkgs = new ArrayList<>();
-
- int numPkgs = pkgs.size();
- for (int i = 0; i < numPkgs; i++) {
- BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext,
- pkgs.get(i));
-
- if (packageState != null) {
- backupPkgs.add(packageState);
- }
- }
-
- writePkgsAsXml(serializer, backupPkgs);
- }
-
- /**
- * State that needs to be backed up for a permission.
- */
- private static class BackupPermissionState {
- private final @NonNull String mPermissionName;
- private final boolean mIsGranted;
- private final boolean mIsUserSet;
- private final boolean mIsUserFixed;
- private final boolean mWasReviewed;
-
- private BackupPermissionState(@NonNull String permissionName, boolean isGranted,
- boolean isUserSet, boolean isUserFixed, boolean wasReviewed) {
- mPermissionName = permissionName;
- mIsGranted = isGranted;
- mIsUserSet = isUserSet;
- mIsUserFixed = isUserFixed;
- mWasReviewed = wasReviewed;
- }
-
- /**
- * Parse a package state from XML.
- *
- * @param parser The data to read
- * @param context a context to use
- * @param backupPlatformVersion The platform version the backup was created on
- *
- * @return The state
- */
- static @NonNull List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser,
- @NonNull Context context, int backupPlatformVersion)
- throws XmlPullParserException {
- String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
- if (permName == null) {
- throw new XmlPullParserException("Found " + TAG_PERMISSION + " without "
- + ATTR_PERMISSION_NAME);
- }
-
- ArrayList<String> expandedPermissions = new ArrayList<>();
- expandedPermissions.add(permName);
-
- List<SplitPermissionInfo> splitPerms = context.getSystemService(
- PermissionManager.class).getSplitPermissions();
-
- // Expand the properties to permissions that were split between the platform version the
- // backup was taken and the current version.
- int numSplitPerms = splitPerms.size();
- for (int i = 0; i < numSplitPerms; i++) {
- SplitPermissionInfo splitPerm = splitPerms.get(i);
- if (backupPlatformVersion < splitPerm.getTargetSdk()
- && permName.equals(splitPerm.getSplitPermission())) {
- expandedPermissions.addAll(splitPerm.getNewPermissions());
- }
- }
-
- ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>(
- expandedPermissions.size());
- int numExpandedPerms = expandedPermissions.size();
- for (int i = 0; i < numExpandedPerms; i++) {
- parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i),
- "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)),
- "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)),
- "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)),
- "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED))));
- }
-
- return parsedPermissions;
- }
-
- /**
- * Is the permission granted, also considering the app-op.
- *
- * <p>This does not consider the review-required state of the permission.
- *
- * @param perm The permission that might be granted
- *
- * @return {@code true} iff the permission and app-op is granted
- */
- private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) {
- return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed());
- }
-
- /**
- * Get the state of a permission to back up.
- *
- * @param perm The permission to back up
- * @param appSupportsRuntimePermissions If the app supports runtimePermissions
- *
- * @return The state to back up or {@code null} if the permission does not need to be
- * backed up.
- */
- private static @Nullable BackupPermissionState fromPermission(@NonNull Permission perm,
- boolean appSupportsRuntimePermissions) {
- int grantFlags = perm.getFlags();
-
- if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) {
- return null;
- }
-
- if (!perm.isUserSet() && perm.isGrantedByDefault()) {
- return null;
- }
-
- boolean permissionWasReviewed;
- boolean isNotInDefaultGrantState;
- if (appSupportsRuntimePermissions) {
- isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm);
- permissionWasReviewed = false;
- } else {
- isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm);
- permissionWasReviewed = !perm.isReviewRequired();
- }
-
-// if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed()
-// || permissionWasReviewed) {
-// return new BackupPermissionState(perm.getName(),
-// isPermGrantedIncludingAppOp(perm),
-// perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed);
-// } else {
-// return null;
-// }
- if (perm.isUserSet() && isPermGrantedIncludingAppOp(perm)) {
- return new BackupPermissionState(perm.getName(), /* isGranted */ true,
- /* isUserSet */ true, perm.isUserFixed(), permissionWasReviewed);
- } else {
- return null;
- }
- }
-
- /**
- * Get the states of all permissions of a group to back up.
- *
- * @param group The group of the permissions to back up
- *
- * @return The state to back up. Empty list if no permissions in the group need to be backed
- * up
- */
- static @NonNull ArrayList<BackupPermissionState> fromPermissionGroup(
- @NonNull AppPermissionGroup group) {
- ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
- List<Permission> perms = group.getPermissions();
-
- boolean appSupportsRuntimePermissions =
- group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
-
- int numPerms = perms.size();
- for (int i = 0; i < numPerms; i++) {
- BackupPermissionState permState = fromPermission(perms.get(i),
- appSupportsRuntimePermissions);
- if (permState != null) {
- permissionsToRestore.add(permState);
- }
- }
-
- return permissionsToRestore;
- }
-
- /**
- * Write this state as XML.
- *
- * @param serializer The file to write to
- */
- void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
- serializer.startTag(null, TAG_PERMISSION);
-
- serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName);
-
- if (mIsGranted) {
- serializer.attribute(null, ATTR_IS_GRANTED, "true");
- }
-
- if (mIsUserSet) {
- serializer.attribute(null, ATTR_USER_SET, "true");
- }
-
- if (mIsUserFixed) {
- serializer.attribute(null, ATTR_USER_FIXED, "true");
- }
-
- if (mWasReviewed) {
- serializer.attribute(null, ATTR_WAS_REVIEWED, "true");
- }
-
- serializer.endTag(null, TAG_PERMISSION);
- }
-
- /**
- * Restore this permission state.
- *
- * @param appPerms The {@link AppPermissions} to restore the state to
- * @param restoreBackgroundPerms if {@code true} only restore background permissions,
- * if {@code false} do not restore background permissions
- */
- void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) {
- AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName);
- if (group == null) {
- Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in "
- + appPerms.getPackageInfo().packageName);
- return;
- }
-
- if (restoreBackgroundPerms != group.isBackgroundGroup()) {
- return;
- }
-
- Permission perm = group.getPermission(mPermissionName);
- if (mWasReviewed) {
- perm.unsetReviewRequired();
- }
-
- // Don't grant or revoke fixed permission groups
- if (group.isSystemFixed() || group.isPolicyFixed()) {
- return;
- }
-
- if (!perm.isUserSet()) {
- if (mIsGranted) {
- group.grantRuntimePermissions(false, mIsUserFixed,
- new String[]{mPermissionName});
- } else {
- group.revokeRuntimePermissions(mIsUserFixed,
- new String[]{mPermissionName});
- }
-
- perm.setUserSet(mIsUserSet);
- }
- }
- }
-
- /**
- * State that needs to be backed up for a package.
- */
- private static class BackupPackageState {
- final @NonNull String mPackageName;
- final boolean mHasMultipleSigners;
- @NonNull Signature[] mSignatures;
- private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore;
-
- private BackupPackageState(@NonNull String packageName, boolean hasMultipleSigners,
- @NonNull Signature[] signatures,
- @NonNull ArrayList<BackupPermissionState> permissionsToRestore) {
- mPackageName = packageName;
- mHasMultipleSigners = hasMultipleSigners;
- mSignatures = signatures;
- mPermissionsToRestore = permissionsToRestore;
- }
-
- /**
- * Parse a package state from XML.
- *
- * @param parser The data to read
- * @param context a context to use
- * @param backupPlatformVersion The platform version the backup was created on
- *
- * @return The state
- */
- static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser,
- @NonNull Context context, int backupPlatformVersion)
- throws IOException, XmlPullParserException {
- String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
- if (packageName == null) {
- throw new XmlPullParserException("Found " + TAG_GRANT + " without "
- + ATTR_PACKAGE_NAME);
- }
-
- boolean hasMultipleSigners = Boolean.parseBoolean(
- parser.getAttributeValue(null, ATTR_HAS_MULTIPLE_SIGNERS));
- ArrayList<Signature> signatureList = new ArrayList<>();
-
- ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
-
- while (true) {
- switch (parser.next()) {
- case START_TAG:
- switch (parser.getName()) {
- case TAG_PERMISSION:
- try {
- permissionsToRestore.addAll(
- BackupPermissionState.parseFromXml(parser, context,
- backupPlatformVersion));
- } catch (XmlPullParserException e) {
- Log.e(LOG_TAG, "Could not parse permission for "
- + packageName, e);
- }
-
- skipToEndOfTag(parser);
- break;
- case TAG_SIGNATURE:
- signatureList.add(new Signature(
- parser.getAttributeValue(null, ATTR_SIGNATURE_VALUE)));
- skipToEndOfTag(parser);
- break;
- default:
- // ignore tag
- Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
- + " while restoring " + packageName);
- skipToEndOfTag(parser);
- }
-
- break;
- case END_TAG:
- Signature[] signatures = new Signature[signatureList.size()];
- for (int i = 0; i < signatureList.size(); i++) {
- signatures[i] = signatureList.get(i);
- }
- return new BackupPackageState(packageName, hasMultipleSigners, signatures,
- permissionsToRestore);
- case END_DOCUMENT:
- throw new XmlPullParserException("Could not parse state for "
- + packageName);
- }
- }
- }
-
- /**
- * Get the state of a package to back up.
- *
- * @param context A context to use
- * @param pkgInfo The package to back up.
- *
- * @return The state to back up or {@code null} if no permission of the package need to be
- * backed up.
- */
- static @Nullable BackupPackageState fromAppPermissions(@NonNull Context context,
- @NonNull PackageInfo pkgInfo) {
- AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null);
-
- ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
- List<AppPermissionGroup> groups = appPerms.getPermissionGroups();
-
- // Check if the package has signatures
- SigningInfo signingInfo = pkgInfo.signingInfo;
- Signature[] signatures;
- boolean hasMultipleSigners;
- if (signingInfo.hasMultipleSigners()) {
- hasMultipleSigners = true;
- signatures = signingInfo.getApkContentsSigners();
- } else {
- hasMultipleSigners = false;
- signatures = signingInfo.getSigningCertificateHistory();
- }
- if (signatures == null) {
- Slog.d(LOG_TAG, "Skipping " + pkgInfo.packageName + ", it's unsigned.");
- return null;
- }
-
- int numGroups = groups.size();
- for (int groupNum = 0; groupNum < numGroups; groupNum++) {
- AppPermissionGroup group = groups.get(groupNum);
-
- permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group));
-
- // Background permissions are in a subgroup that is not part of
- // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here.
- if (group.getBackgroundPermissions() != null) {
- permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(
- group.getBackgroundPermissions()));
- }
- }
-
- if (permissionsToRestore.size() == 0) {
- return null;
- }
-
- return new BackupPackageState(pkgInfo.packageName, hasMultipleSigners, signatures,
- permissionsToRestore);
- }
-
- /**
- * Write this state as XML.
- *
- * @param serializer The file to write to
- */
- void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
- if (mPermissionsToRestore.size() == 0) {
- return;
- }
-
- serializer.startTag(null, TAG_GRANT);
- serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
-
- // Add signing info
- serializer.attribute(null, ATTR_HAS_MULTIPLE_SIGNERS,
- String.valueOf(mHasMultipleSigners));
- for (Signature signature : mSignatures) {
- serializer.startTag(null, TAG_SIGNATURE);
- serializer.attribute(null, ATTR_SIGNATURE_VALUE, signature.toCharsString());
- serializer.endTag(null, TAG_SIGNATURE);
- }
-
- int numPerms = mPermissionsToRestore.size();
- for (int i = 0; i < numPerms; i++) {
- mPermissionsToRestore.get(i).writeAsXml(serializer);
- }
-
- serializer.endTag(null, TAG_GRANT);
- }
-
- /**
- * Restore this package state.
- *
- * @param context A context to use
- * @param pkgInfo The package to restore.
- */
- void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) {
- Slog.e(LOG_TAG, "Restoring permissions for package [" + mPackageName + "]");
-
- // Verify signature info
- try {
- if (mHasMultipleSigners && pkgInfo.signingInfo.hasMultipleSigners()) {
- // If both packages are signed by multi signers, check if two signature sets are
- // effectively matched.
- if (!Signature.areEffectiveMatch(mSignatures,
- pkgInfo.signingInfo.getApkContentsSigners())) {
- Slog.e(LOG_TAG, "Multi-signers signatures don't match for package ["
- + mPackageName + "], skipped.");
- return;
- }
- } else if (!mHasMultipleSigners && !pkgInfo.signingInfo.hasMultipleSigners()) {
- // If both packages are not signed by multi signers, check if two signature sets
- // have overlaps.
- Signature[] signatures = pkgInfo.signingInfo.getSigningCertificateHistory();
- if (signatures == null) {
- Slog.e(LOG_TAG, "The dest package is unsigned.");
- return;
- }
- boolean isMatched = false;
- for (int i = 0; i < mSignatures.length; i++) {
- for (int j = 0; j < signatures.length; j++) {
- isMatched = Signature.areEffectiveMatch(mSignatures[i], signatures[j]);
- }
- }
- if (!isMatched) {
- Slog.e(LOG_TAG, "Single signer signatures don't match for package ["
- + mPackageName + "], skipped.");
- return;
- }
- } else {
- Slog.e(LOG_TAG, "Number of signers don't match.");
- return;
- }
- } catch (CertificateException ce) {
- Slog.e(LOG_TAG, "Either the source or the dest package's bounced cert length "
- + "looks fishy, skipped package [" + pkgInfo.packageName + "]");
- }
-
- AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null);
-
- ArraySet<String> affectedPermissions = new ArraySet<>();
- // Restore background permissions after foreground permissions as for pre-M apps bg
- // granted and fg revoked cannot be expressed.
- int numPerms = mPermissionsToRestore.size();
- for (int i = 0; i < numPerms; i++) {
- mPermissionsToRestore.get(i).restore(appPerms, false);
- affectedPermissions.add(mPermissionsToRestore.get(i).mPermissionName);
- }
- for (int i = 0; i < numPerms; i++) {
- mPermissionsToRestore.get(i).restore(appPerms, true);
- }
-
- int numGroups = appPerms.getPermissionGroups().size();
- for (int i = 0; i < numGroups; i++) {
- AppPermissionGroup group = appPerms.getPermissionGroups().get(i);
-
- // Only denied groups can be user fixed
- if (group.areRuntimePermissionsGranted()) {
- group.setUserFixed(false);
- }
-
- AppPermissionGroup bgGroup = group.getBackgroundPermissions();
- if (bgGroup != null) {
- // Only denied groups can be user fixed
- if (bgGroup.areRuntimePermissionsGranted()) {
- bgGroup.setUserFixed(false);
- }
- }
- }
-
- appPerms.persistChanges(true, affectedPermissions);
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java
deleted file mode 100644
index cf146ac7a48e..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java
+++ /dev/null
@@ -1,1574 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.model;
-
-import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
-import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.Application;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
-import android.os.Build;
-import android.os.UserHandle;
-import android.permission.PermissionManager;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.server.companion.datatransfer.permbackup.utils.ArrayUtils;
-import com.android.server.companion.datatransfer.permbackup.utils.LocationUtils;
-import com.android.server.companion.datatransfer.permbackup.utils.SoftRestrictedPermissionPolicy;
-import com.android.server.companion.datatransfer.permbackup.utils.Utils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * All permissions of a permission group that are requested by an app.
- *
- * <p>Some permissions only grant access to the protected resource while the app is running in the
- * foreground. These permissions are considered "split" into this foreground and a matching
- * "background" permission.
- *
- * <p>All background permissions of the group are not in the main group and will not be affected
- * by operations on the group. The background permissions can be found in the {@link
- * #getBackgroundPermissions() background permissions group}.
- */
-public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
- private static final String LOG_TAG = AppPermissionGroup.class.getSimpleName();
- private static final String PLATFORM_PACKAGE_NAME = "android";
-
- private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
-
- /**
- * Importance level to define the threshold for whether a package is in a state which resets the
- * timer on its one-time permission session
- */
- private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER =
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
-
- /**
- * Importance level to define the threshold for whether a package is in a state which keeps its
- * one-time permission session alive after the timer ends
- */
- private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
-
- private final Context mContext;
- private final UserHandle mUserHandle;
- private final PackageManager mPackageManager;
- private final AppOpsManager mAppOps;
- private final ActivityManager mActivityManager;
- private final Collator mCollator;
-
- private final PackageInfo mPackageInfo;
- private final String mName;
- private final String mDeclaringPackage;
- private final CharSequence mLabel;
- private final CharSequence mFullLabel;
- private final @StringRes int mRequest;
- private final @StringRes int mRequestDetail;
- private final @StringRes int mBackgroundRequest;
- private final @StringRes int mBackgroundRequestDetail;
- private final @StringRes int mUpgradeRequest;
- private final @StringRes int mUpgradeRequestDetail;
- private final CharSequence mDescription;
- private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
- private final String mIconPkg;
- private final int mIconResId;
-
- /** Delay changes until {@link #persistChanges} is called */
- private final boolean mDelayChanges;
-
- /**
- * Some permissions are split into foreground and background permission. All non-split and
- * foreground permissions are in {@link #mPermissions}, all background permissions are in
- * this field.
- */
- private AppPermissionGroup mBackgroundPermissions;
-
- private final boolean mAppSupportsRuntimePermissions;
- private final boolean mIsEphemeralApp;
- private final boolean mIsNonIsolatedStorage;
- private boolean mContainsEphemeralPermission;
- private boolean mContainsPreRuntimePermission;
-
- /**
- * Does this group contain at least one permission that is split into a foreground and
- * background permission? This does not necessarily mean that the app also requested the
- * background permission.
- */
- private boolean mHasPermissionWithBackgroundMode;
-
- private boolean mTriggerLocationAccessCheckOnPersist;
-
- private boolean mIsSelfRevoked;
-
- /**
- * Create the app permission group.
- *
- * @param context the {@code Context} to retrieve system services.
- * @param packageInfo package information about the app.
- * @param permissionName the name of the permission this object represents.
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
- String permissionName, boolean delayChanges) {
- PermissionInfo permissionInfo;
- try {
- permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
-
- if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
- != PermissionInfo.PROTECTION_DANGEROUS
- || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
- || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
- return null;
- }
-
- String group = Utils.getGroupOfPermission(permissionInfo);
- PackageItemInfo groupInfo = permissionInfo;
- if (group != null) {
- try {
- groupInfo = context.getPackageManager().getPermissionGroupInfo(group, 0);
- } catch (PackageManager.NameNotFoundException e) {
- /* ignore */
- }
- }
-
- List<PermissionInfo> permissionInfos = null;
- if (groupInfo instanceof PermissionGroupInfo) {
- try {
- permissionInfos = Utils.getPermissionInfosForGroup(context.getPackageManager(),
- groupInfo.name);
- } catch (PackageManager.NameNotFoundException e) {
- /* ignore */
- }
- }
-
- return create(context, packageInfo, groupInfo, permissionInfos, delayChanges);
- }
-
- /**
- * Create the app permission group.
- *
- * @param app the current application
- * @param packageName the name of the package
- * @param permissionGroupName the name of the permission group
- * @param user the user of the package
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Application app, String packageName,
- String permissionGroupName, UserHandle user, boolean delayChanges) {
- try {
- PackageInfo packageInfo = Utils.getUserContext(app, user).getPackageManager()
- .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- PackageItemInfo groupInfo = Utils.getGroupInfo(permissionGroupName, app);
- if (groupInfo == null) {
- return null;
- }
-
- List<PermissionInfo> permissionInfos = null;
- if (groupInfo instanceof PermissionGroupInfo) {
- permissionInfos = Utils.getPermissionInfosForGroup(app.getPackageManager(),
- groupInfo.name);
- }
- return create(app, packageInfo, groupInfo, permissionInfos, delayChanges);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
- * Create the app permission group.
- *
- * @param context the {@code Context} to retrieve system services.
- * @param packageInfo package information about the app.
- * @param groupInfo the information about the group created.
- * @param permissionInfos the information about the permissions belonging to the group.
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
- PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, boolean delayChanges) {
- PackageManager packageManager = context.getPackageManager();
- CharSequence groupLabel = groupInfo.loadLabel(packageManager);
- CharSequence fullGroupLabel = groupInfo.loadSafeLabel(packageManager, 0,
- TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE);
- return create(context, packageInfo, groupInfo, permissionInfos, groupLabel,
- fullGroupLabel, delayChanges);
- }
-
- /**
- * Create the app permission group.
- *
- * @param context the {@code Context} to retrieve system services.
- * @param packageInfo package information about the app.
- * @param groupInfo the information about the group created.
- * @param permissionInfos the information about the permissions belonging to the group.
- * @param groupLabel the label of the group.
- * @param fullGroupLabel the untruncated label of the group.
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
- PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,
- CharSequence groupLabel, CharSequence fullGroupLabel, boolean delayChanges) {
- PackageManager packageManager = context.getPackageManager();
- UserHandle userHandle = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid);
-
- if (groupInfo instanceof PermissionInfo) {
- permissionInfos = new ArrayList<>();
- permissionInfos.add((PermissionInfo) groupInfo);
- }
-
- if (permissionInfos == null || permissionInfos.isEmpty()) {
- return null;
- }
-
- AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-
- AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
- groupInfo.packageName, groupLabel, fullGroupLabel,
- /* description */ null, /* request */ 0,
- /* requestDetail */ 0, /* backgroundRequest */ 0,
- /* backgroundRequestDetail */ 0, /* upgradeRequest */0,
- /* upgradeRequestDetail */ 0, groupInfo.packageName, groupInfo.icon,
- userHandle, delayChanges, appOpsManager);
-
- final Set<String> exemptedRestrictedPermissions = context.getPackageManager()
- .getWhitelistedRestrictedPermissions(packageInfo.packageName,
- Utils.FLAGS_PERMISSION_WHITELIST_ALL);
-
- // Parse and create permissions requested by the app
- ArrayMap<String, Permission> allPermissions = new ArrayMap<>();
- final int permissionCount = packageInfo.requestedPermissions == null ? 0
- : packageInfo.requestedPermissions.length;
- String packageName = packageInfo.packageName;
- for (int i = 0; i < permissionCount; i++) {
- String requestedPermission = packageInfo.requestedPermissions[i];
-
- PermissionInfo requestedPermissionInfo = null;
-
- for (PermissionInfo permissionInfo : permissionInfos) {
- if (requestedPermission.equals(permissionInfo.name)) {
- requestedPermissionInfo = permissionInfo;
- break;
- }
- }
-
- if (requestedPermissionInfo == null) {
- continue;
- }
-
- // Collect only runtime permissions.
- if ((requestedPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
- != PermissionInfo.PROTECTION_DANGEROUS) {
- continue;
- }
-
- // Don't allow toggling non-platform permission groups for legacy apps via app ops.
- if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
- && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) {
- continue;
- }
-
- final boolean granted = (packageInfo.requestedPermissionsFlags[i]
- & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
-
- final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
- ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
-
- final boolean appOpAllowed;
- if (appOp == null) {
- appOpAllowed = false;
- } else {
- int appOpsMode = appOpsManager.unsafeCheckOpRaw(appOp,
- packageInfo.applicationInfo.uid, packageName);
- appOpAllowed = appOpsMode == MODE_ALLOWED || appOpsMode == MODE_FOREGROUND;
- }
-
- final int flags = packageManager.getPermissionFlags(
- requestedPermission, packageName, userHandle);
-
- Permission permission = new Permission(requestedPermission, requestedPermissionInfo,
- granted, appOp, appOpAllowed, flags);
-
- if (requestedPermissionInfo.backgroundPermission != null) {
- group.mHasPermissionWithBackgroundMode = true;
- }
-
- allPermissions.put(requestedPermission, permission);
- }
-
- int numPermissions = allPermissions.size();
- if (numPermissions == 0) {
- return null;
- }
-
- // Link up foreground and background permissions
- for (int i = 0; i < allPermissions.size(); i++) {
- Permission permission = allPermissions.valueAt(i);
-
- if (permission.getBackgroundPermissionName() != null) {
- Permission backgroundPermission = allPermissions.get(
- permission.getBackgroundPermissionName());
-
- if (backgroundPermission != null) {
- backgroundPermission.addForegroundPermissions(permission);
- permission.setBackgroundPermission(backgroundPermission);
-
- // The background permissions isAppOpAllowed refers to the background state of
- // the foregound permission's appOp. Hence we can only set it once we know the
- // matching foreground permission.
- // @see #allowAppOp
- if (context.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
- permission.getAppOp(), packageInfo.applicationInfo.uid,
- packageInfo.packageName) == MODE_ALLOWED) {
- backgroundPermission.setAppOpAllowed(true);
- }
- }
- }
- }
-
- // Add permissions found to this group
- for (int i = 0; i < numPermissions; i++) {
- Permission permission = allPermissions.valueAt(i);
-
- if ((!permission.isHardRestricted()
- || exemptedRestrictedPermissions.contains(permission.getName()))
- && (!permission.isSoftRestricted()
- || SoftRestrictedPermissionPolicy.shouldShow(packageInfo, permission))) {
- if (permission.isBackgroundPermission()) {
- if (group.getBackgroundPermissions() == null) {
- group.mBackgroundPermissions = new AppPermissionGroup(group.mContext,
- group.getApp(), group.getName(), group.getDeclaringPackage(),
- group.getLabel(), group.getFullLabel(), group.getDescription(),
- group.getRequest(), group.getRequestDetail(),
- group.getBackgroundRequest(), group.getBackgroundRequestDetail(),
- group.getUpgradeRequest(), group.getUpgradeRequestDetail(),
- group.getIconPkg(), group.getIconResId(), group.getUser(),
- delayChanges, appOpsManager);
- }
-
- group.getBackgroundPermissions().addPermission(permission);
- } else {
- group.addPermission(permission);
- }
- }
- }
-
- if (group.getPermissions().isEmpty()) {
- return null;
- }
-
- return group;
- }
-
- private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
- String declaringPackage, CharSequence label, CharSequence fullLabel,
- CharSequence description, @StringRes int request, @StringRes int requestDetail,
- @StringRes int backgroundRequest, @StringRes int backgroundRequestDetail,
- @StringRes int upgradeRequest, @StringRes int upgradeRequestDetail,
- String iconPkg, int iconResId, UserHandle userHandle, boolean delayChanges,
- @NonNull AppOpsManager appOpsManager) {
- int targetSDK = packageInfo.applicationInfo.targetSdkVersion;
-
- mContext = context;
- mUserHandle = userHandle;
- mPackageManager = mContext.getPackageManager();
- mPackageInfo = packageInfo;
- mAppSupportsRuntimePermissions = targetSDK > Build.VERSION_CODES.LOLLIPOP_MR1;
- mIsEphemeralApp = packageInfo.applicationInfo.isInstantApp();
- mAppOps = appOpsManager;
- mActivityManager = context.getSystemService(ActivityManager.class);
- mDeclaringPackage = declaringPackage;
- mName = name;
- mLabel = label;
- mFullLabel = fullLabel;
- mDescription = description;
- mCollator = Collator.getInstance(
- context.getResources().getConfiguration().getLocales().get(0));
- mRequest = request;
- mRequestDetail = requestDetail;
- mBackgroundRequest = backgroundRequest;
- mBackgroundRequestDetail = backgroundRequestDetail;
- mUpgradeRequest = upgradeRequest;
- mUpgradeRequestDetail = upgradeRequestDetail;
- mDelayChanges = delayChanges;
- if (iconResId != 0) {
- mIconPkg = iconPkg;
- mIconResId = iconResId;
- } else {
- mIconPkg = context.getPackageName();
- mIconResId = 0; // doesn't matter to CDM
- }
-
- mIsNonIsolatedStorage = targetSDK < Build.VERSION_CODES.P
- || (targetSDK < Build.VERSION_CODES.R
- && mAppOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
- packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
- }
-
- boolean doesSupportRuntimePermissions() {
- return mAppSupportsRuntimePermissions;
- }
-
- boolean isGrantingAllowed() {
- return (!mIsEphemeralApp || mContainsEphemeralPermission)
- && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission);
- }
-
- boolean isReviewRequired() {
- if (mAppSupportsRuntimePermissions) {
- return false;
- }
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isReviewRequired()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Are any of the permissions in this group user sensitive.
- *
- * @return {@code true} if any of the permissions in the group is user sensitive.
- */
- public boolean isUserSensitive() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isUserSensitive()) {
- return true;
- }
- }
- return false;
- }
-
- void unsetReviewRequired() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isReviewRequired()) {
- permission.unsetReviewRequired();
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- boolean hasGrantedByDefaultPermission() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isGrantedByDefault()) {
- return true;
- }
- }
- return false;
- }
-
- public PackageInfo getApp() {
- return mPackageInfo;
- }
-
- String getName() {
- return mName;
- }
-
- String getDeclaringPackage() {
- return mDeclaringPackage;
- }
-
- String getIconPkg() {
- return mIconPkg;
- }
-
- int getIconResId() {
- return mIconResId;
- }
-
- CharSequence getLabel() {
- return mLabel;
- }
-
- /**
- * Get the full un-ellipsized label of the permission group.
- *
- * @return the full label of the group.
- */
- public CharSequence getFullLabel() {
- return mFullLabel;
- }
-
- /**
- * @hide
- * @return The resource Id of the request string.
- */
- public @StringRes int getRequest() {
- return mRequest;
- }
-
- /**
- * Get the (subtitle) message explaining to the user that the permission is only granted to
- * the apps running in the foreground.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getRequestDetail() {
- return mRequestDetail;
- }
-
- /**
- * Get the title of the dialog explaining to the user that the permission is granted while
- * the app is in background and in foreground.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getBackgroundRequest() {
- return mBackgroundRequest;
- }
-
- /**
- * Get the (subtitle) message explaining to the user that the she/he is about to allow the
- * app to have background access.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getBackgroundRequestDetail() {
- return mBackgroundRequestDetail;
- }
-
- /**
- * Get the title of the dialog explaining to the user that the permission, which was
- * previously only granted for foreground, is granted while the app is in background and in
- * foreground.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getUpgradeRequest() {
- return mUpgradeRequest;
- }
-
- /**
- * Get the (subtitle) message explaining to the user that the she/he is about to allow the
- * app to have background access while currently having foreground only.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getUpgradeRequestDetail() {
- return mUpgradeRequestDetail;
- }
-
- public CharSequence getDescription() {
- return mDescription;
- }
-
- public UserHandle getUser() {
- return mUserHandle;
- }
-
- /**
- * Check if the group contains the permission.
- */
- public boolean hasPermission(String permission) {
- return mPermissions.get(permission) != null;
- }
-
- /**
- * Return a permission if in this group.
- *
- * @param permissionName The name of the permission
- *
- * @return The permission
- */
- public @Nullable Permission getPermission(@NonNull String permissionName) {
- return mPermissions.get(permissionName);
- }
-
- /**
- * Check if at least one of the permissions in the entire permission group should be considered
- * granted.
- */
- public boolean areRuntimePermissionsGranted() {
- return areRuntimePermissionsGranted(null);
- }
-
- /**
- * Check if at least one of the permissions in the filterPermissions should be considered
- * granted.
- */
- public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
- return areRuntimePermissionsGranted(filterPermissions, false);
- }
-
- /**
- * @param filterPermissions the permissions to check for, null for all in this group
- * @param asOneTime add the requirement that at least one of the granted permissions must have
- * the ONE_TIME flag to return true
- */
- public boolean areRuntimePermissionsGranted(String[] filterPermissions, boolean asOneTime) {
- return areRuntimePermissionsGranted(filterPermissions, asOneTime, true);
- }
-
- /**
- * Returns true if at least one of the permissions in filterPermissions (or the entire
- * permission group if null) should be considered granted and satisfy the requirements
- * described by asOneTime and includingAppOp.
- *
- * @param filterPermissions the permissions to check for, null for all in this group
- * @param asOneTime add the requirement that the granted permission must have the ONE_TIME flag
- * @param includingAppOp add the requirement that if the granted permissions has a
- * corresponding AppOp, it must be allowed.
- */
- public boolean areRuntimePermissionsGranted(String[] filterPermissions, boolean asOneTime,
- boolean includingAppOp) {
- if (LocationUtils.isLocationGroupAndProvider(mContext, mName, mPackageInfo.packageName)) {
- return LocationUtils.isLocationEnabled(mContext) && !asOneTime;
- }
- // The permission of the extra location controller package is determined by the status of
- // the controller package itself.
- if (LocationUtils.isLocationGroupAndControllerExtraPackage(
- mContext, mName, mPackageInfo.packageName)) {
- return LocationUtils.isExtraLocationControllerPackageEnabled(mContext) && !asOneTime;
- }
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (filterPermissions != null
- && !ArrayUtils.contains(filterPermissions, permission.getName())) {
- continue;
- }
- boolean isGranted = includingAppOp ? permission.isGrantedIncludingAppOp()
- : permission.isGranted();
- if (isGranted && (!asOneTime || permission.isOneTime())) {
- return true;
- }
- }
- if (mBackgroundPermissions != null) {
- // If asOneTime is true and none of the foreground permissions are one-time, but some
- // background permissions are, then we still want to return true.
- return mBackgroundPermissions.areRuntimePermissionsGranted(filterPermissions,
- asOneTime, includingAppOp);
- }
- return false;
- }
-
- boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser) {
- return grantRuntimePermissions(setByTheUser, fixedByTheUser, null);
- }
-
- /**
- * Set mode of an app-op if needed.
- *
- * @param op The op to set
- * @param uid The uid the app-op belongs to
- * @param mode The new mode
- *
- * @return {@code true} iff app-op was changed
- */
- private boolean setAppOpMode(@NonNull String op, int uid, int mode) {
- int currentMode = mAppOps.unsafeCheckOpRaw(op, uid, mPackageInfo.packageName);
- if (currentMode == mode) {
- return false;
- }
-
- mAppOps.setUidMode(op, uid, mode);
- return true;
- }
-
- /**
- * Allow the app op for a permission/uid.
- *
- * <p>There are three cases:
- * <dl>
- * <dt>The permission is not split into foreground/background</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
- * <dt>The permission is a foreground permission:</dt>
- * <dd><dl><dt>The background permission permission is granted</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
- * <dt>The background permission permission is <u>not</u> granted</dt>
- * <dd>The app op matching the permission will be set to
- * {@link AppOpsManager#MODE_FOREGROUND}</dd>
- * </dl></dd>
- * <dt>The permission is a background permission:</dt>
- * <dd>All granted foreground permissions for this background permission will be set to
- * {@link AppOpsManager#MODE_ALLOWED}</dd>
- * </dl>
- *
- * @param permission The permission which has an appOps that should be allowed
- * @param uid The uid of the process the app op is for
- *
- * @return {@code true} iff app-op was changed
- */
- private boolean allowAppOp(Permission permission, int uid) {
- boolean wasChanged = false;
-
- if (permission.isBackgroundPermission()) {
- ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions();
-
- int numForegroundPermissions = foregroundPermissions.size();
- for (int i = 0; i < numForegroundPermissions; i++) {
- Permission foregroundPermission = foregroundPermissions.get(i);
- if (foregroundPermission.isAppOpAllowed()) {
- wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid, MODE_ALLOWED);
- }
- }
- } else {
- if (permission.hasBackgroundPermission()) {
- Permission backgroundPermission = permission.getBackgroundPermission();
-
- if (backgroundPermission == null) {
- // The app requested a permission that has a background permission but it did
- // not request the background permission, hence it can never get background
- // access
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND);
- } else {
- if (backgroundPermission.isAppOpAllowed()) {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED);
- } else {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND);
- }
- }
- } else {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED);
- }
- }
-
- return wasChanged;
- }
-
- /**
- * Kills the app the permissions belong to (and all apps sharing the same uid)
- *
- * @param reason The reason why the apps are killed
- */
- private void killApp(String reason) {
- mActivityManager.killUid(mPackageInfo.applicationInfo.uid, reason);
- }
-
- /**
- * Grant permissions of the group.
- *
- * <p>This also automatically grants all app ops for permissions that have app ops.
- * <p>This does <u>only</u> grant permissions in {@link #mPermissions}, i.e. usually not
- * the background permissions.
- *
- * @param setByTheUser If the user has made the decision. This does not unset the flag
- * @param fixedByTheUser If the user requested that she/he does not want to be asked again
- * @param filterPermissions If {@code null} all permissions of the group will be granted.
- * Otherwise only permissions in {@code filterPermissions} will be
- * granted.
- *
- * @return {@code true} iff all permissions of this group could be granted.
- */
- public boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser,
- String[] filterPermissions) {
- boolean killApp = false;
- boolean wasAllGranted = true;
-
- // We toggle permissions only to apps that support runtime
- // permissions, otherwise we toggle the app op corresponding
- // to the permission if the permission is granted to the app.
- for (Permission permission : mPermissions.values()) {
- if (filterPermissions != null
- && !ArrayUtils.contains(filterPermissions, permission.getName())) {
- continue;
- }
-
- if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) {
- // Skip unallowed permissions.
- continue;
- }
-
- boolean wasGranted = permission.isGrantedIncludingAppOp();
-
- if (mAppSupportsRuntimePermissions) {
- // Do not touch permissions fixed by the system.
- if (permission.isSystemFixed()) {
- wasAllGranted = false;
- break;
- }
-
- // Ensure the permission app op is enabled before the permission grant.
- if (permission.affectsAppOp() && !permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(true);
- }
-
- // Grant the permission if needed.
- if (!permission.isGranted()) {
- permission.setGranted(true);
- }
-
- // Update the permission flags.
- if (!fixedByTheUser) {
- if (permission.isUserFixed()) {
- permission.setUserFixed(false);
- }
- if (setByTheUser) {
- if (!permission.isUserSet()) {
- permission.setUserSet(true);
- }
- }
- } else {
- if (!permission.isUserFixed()) {
- permission.setUserFixed(true);
- }
- if (permission.isUserSet()) {
- permission.setUserSet(false);
- }
- }
- if (permission.isReviewRequired()) {
- permission.unsetReviewRequired();
- }
- } else {
- // Legacy apps cannot have a not granted permission but just in case.
- if (!permission.isGranted()) {
- continue;
- }
-
- // If the permissions has no corresponding app op, then it is a
- // third-party one and we do not offer toggling of such permissions.
- if (permission.affectsAppOp()) {
- if (!permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(true);
-
- // Legacy apps do not know that they have to retry access to a
- // resource due to changes in runtime permissions (app ops in this
- // case). Therefore, we restart them on app op change, so they
- // can pick up the change.
- killApp = true;
- }
-
- // Mark that the permission is not kept granted only for compatibility.
- if (permission.isRevokedCompat()) {
- permission.setRevokedCompat(false);
- }
- }
-
- // Granting a permission explicitly means the user already
- // reviewed it so clear the review flag on every grant.
- if (permission.isReviewRequired()) {
- permission.unsetReviewRequired();
- }
- }
-
- // If we newly grant background access to the fine location, double-guess the user some
- // time later if this was really the right choice.
- if (!wasGranted && permission.isGrantedIncludingAppOp()) {
- if (permission.getName().equals(ACCESS_FINE_LOCATION)) {
- Permission bgPerm = permission.getBackgroundPermission();
- if (bgPerm != null) {
- if (bgPerm.isGrantedIncludingAppOp()) {
- mTriggerLocationAccessCheckOnPersist = true;
- }
- }
- } else if (permission.getName().equals(ACCESS_BACKGROUND_LOCATION)) {
- ArrayList<Permission> fgPerms = permission.getForegroundPermissions();
- if (fgPerms != null) {
- int numFgPerms = fgPerms.size();
- for (int fgPermNum = 0; fgPermNum < numFgPerms; fgPermNum++) {
- Permission fgPerm = fgPerms.get(fgPermNum);
-
- if (fgPerm.getName().equals(ACCESS_FINE_LOCATION)) {
- if (fgPerm.isGrantedIncludingAppOp()) {
- mTriggerLocationAccessCheckOnPersist = true;
- }
-
- break;
- }
- }
- }
- }
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
-
- if (killApp) {
- killApp(KILL_REASON_APP_OP_CHANGE);
- }
- }
-
- return wasAllGranted;
- }
-
- boolean revokeRuntimePermissions(boolean fixedByTheUser) {
- return revokeRuntimePermissions(fixedByTheUser, null);
- }
-
- /**
- * Disallow the app op for a permission/uid.
- *
- * <p>There are three cases:
- * <dl>
- * <dt>The permission is not split into foreground/background</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
- * <dt>The permission is a foreground permission:</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
- * <dt>The permission is a background permission:</dt>
- * <dd>All granted foreground permissions for this background permission will be set to
- * {@link AppOpsManager#MODE_FOREGROUND}</dd>
- * </dl>
- *
- * @param permission The permission which has an appOps that should be disallowed
- * @param uid The uid of the process the app op if for
- *
- * @return {@code true} iff app-op was changed
- */
- private boolean disallowAppOp(Permission permission, int uid) {
- boolean wasChanged = false;
-
- if (permission.isBackgroundPermission()) {
- ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions();
-
- int numForegroundPermissions = foregroundPermissions.size();
- for (int i = 0; i < numForegroundPermissions; i++) {
- Permission foregroundPermission = foregroundPermissions.get(i);
- if (foregroundPermission.isAppOpAllowed()) {
- wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid,
- MODE_FOREGROUND);
- }
- }
- } else {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_IGNORED);
- }
-
- return wasChanged;
- }
-
- /**
- * Revoke permissions of the group.
- *
- * <p>This also disallows all app ops for permissions that have app ops.
- * <p>This does <u>only</u> revoke permissions in {@link #mPermissions}, i.e. usually not
- * the background permissions.
- *
- * @param fixedByTheUser If the user requested that she/he does not want to be asked again
- * @param filterPermissions If {@code null} all permissions of the group will be revoked.
- * Otherwise only permissions in {@code filterPermissions} will be
- * revoked.
- *
- * @return {@code true} iff all permissions of this group could be revoked.
- */
- public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
- boolean killApp = false;
- boolean wasAllRevoked = true;
-
- // We toggle permissions only to apps that support runtime
- // permissions, otherwise we toggle the app op corresponding
- // to the permission if the permission is granted to the app.
- for (Permission permission : mPermissions.values()) {
- if (filterPermissions != null
- && !ArrayUtils.contains(filterPermissions, permission.getName())) {
- continue;
- }
-
- // Do not touch permissions fixed by the system.
- if (permission.isSystemFixed()) {
- wasAllRevoked = false;
- break;
- }
-
- if (mAppSupportsRuntimePermissions) {
- // Revoke the permission if needed.
- if (permission.isGranted()) {
- permission.setGranted(false);
- }
-
- // Update the permission flags.
- if (fixedByTheUser) {
- // Take a note that the user fixed the permission.
- if (permission.isUserSet() || !permission.isUserFixed()) {
- permission.setUserSet(false);
- permission.setUserFixed(true);
- }
- } else {
- if (!permission.isUserSet() || permission.isUserFixed()) {
- permission.setUserSet(true);
- permission.setUserFixed(false);
- }
- }
-
- if (permission.affectsAppOp()) {
- permission.setAppOpAllowed(false);
- }
- } else {
- // Legacy apps cannot have a non-granted permission but just in case.
- if (!permission.isGranted()) {
- continue;
- }
-
- // If the permission has no corresponding app op, then it is a
- // third-party one and we do not offer toggling of such permissions.
- if (permission.affectsAppOp()) {
- if (permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(false);
-
- // Disabling an app op may put the app in a situation in which it
- // has a handle to state it shouldn't have, so we have to kill the
- // app. This matches the revoke runtime permission behavior.
- killApp = true;
- }
-
- // Mark that the permission is kept granted only for compatibility.
- if (!permission.isRevokedCompat()) {
- permission.setRevokedCompat(true);
- }
- }
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
-
- if (killApp) {
- killApp(KILL_REASON_APP_OP_CHANGE);
- }
- }
-
- return wasAllRevoked;
- }
-
- /**
- * Mark permissions in this group as policy fixed.
- *
- * @param filterPermissions The permissions to mark
- */
- public void setPolicyFixed(@NonNull String[] filterPermissions) {
- for (String permissionName : filterPermissions) {
- Permission permission = mPermissions.get(permissionName);
-
- if (permission != null) {
- permission.setPolicyFixed(true);
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Set the user-fixed flag for all permissions in this group.
- *
- * @param isUsedFixed if the flag should be set or not
- */
- public void setUserFixed(boolean isUsedFixed) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- permission.setUserFixed(isUsedFixed);
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Mark this group as having been self-revoked.
- */
- public void setSelfRevoked() {
- mIsSelfRevoked = true;
- }
-
- /**
- * Set the one-time flag for all permissions in this group.
- *
- * @param isOneTime if the flag should be set or not
- */
- public void setOneTime(boolean isOneTime) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- permission.setOneTime(isOneTime);
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Set the user-set flag for all permissions in this group.
- *
- * @param isUserSet if the flag should be set or not
- */
- public void setUserSet(boolean isUserSet) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- permission.setUserSet(isUserSet);
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Get all permissions in the group.
- */
- public ArrayList<Permission> getPermissions() {
- return new ArrayList<>(mPermissions.values());
- }
-
- /**
- * @return An {@link AppPermissionGroup}-object that contains all background permissions for
- * this group.
- */
- public AppPermissionGroup getBackgroundPermissions() {
- return mBackgroundPermissions;
- }
-
- /**
- * @return {@code true} iff the app request at least one permission in this group that has a
- * background permission. It is possible that the app does not request the matching background
- * permission and hence will only ever get foreground access, never background access.
- */
- public boolean hasPermissionWithBackgroundMode() {
- return mHasPermissionWithBackgroundMode;
- }
-
- /**
- * Is the group a storage permission group that is referring to an app that does not have
- * isolated storage
- *
- * @return {@code true} iff this is a storage group on an app that does not have isolated
- * storage
- */
- public boolean isNonIsolatedStorage() {
- return mIsNonIsolatedStorage;
- }
-
- /**
- * Whether this is group that contains all the background permission for regular permission
- * group.
- *
- * @return {@code true} iff this is a background permission group.
- *
- * @see #getBackgroundPermissions()
- */
- public boolean isBackgroundGroup() {
- return mPermissions.valueAt(0).isBackgroundPermission();
- }
-
- /**
- * Whether this group supports one-time permissions
- * @return {@code true} iff this group supports one-time permissions
- */
- public boolean supportsOneTimeGrant() {
- return Utils.supportsOneTimeGrant(getName());
- }
-
- int getFlags() {
- int flags = 0;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- flags |= permission.getFlags();
- }
- return flags;
- }
-
- boolean isUserFixed() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isUserFixed()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check if there's a permission in the group is policy fixed.
- */
- public boolean isPolicyFixed() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isPolicyFixed()) {
- return true;
- }
- }
- return false;
- }
-
- boolean isUserSet() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isUserSet()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check if there's a permission in the group is system fixed.
- */
- public boolean isSystemFixed() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isSystemFixed()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @return Whether any of the permissions in this group is one-time
- */
- public boolean isOneTime() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isOneTime()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @return Whether at least one permission is granted and every granted permission is one-time
- */
- public boolean isStrictlyOneTime() {
- boolean oneTimePermissionFound = false;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isGranted()) {
- if (!permission.isOneTime()) {
- return false;
- }
- oneTimePermissionFound = true;
- }
- }
- return oneTimePermissionFound;
- }
-
- @Override
- public int compareTo(AppPermissionGroup another) {
- final int result = mCollator.compare(mLabel.toString(), another.mLabel.toString());
- if (result == 0) {
- // Unbadged before badged.
- return mPackageInfo.applicationInfo.uid
- - another.mPackageInfo.applicationInfo.uid;
- }
- return result;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof AppPermissionGroup)) {
- return false;
- }
-
- AppPermissionGroup other = (AppPermissionGroup) o;
-
- boolean equal = mName.equals(other.mName)
- && mPackageInfo.packageName.equals(other.mPackageInfo.packageName)
- && mUserHandle.equals(other.mUserHandle)
- && mPermissions.equals(other.mPermissions);
- if (!equal) {
- return false;
- }
-
- if (mBackgroundPermissions != null && other.getBackgroundPermissions() != null) {
- return mBackgroundPermissions.getPermissions().equals(
- other.getBackgroundPermissions().getPermissions());
- }
- return mBackgroundPermissions == other.getBackgroundPermissions();
- }
-
- @Override
- public int hashCode() {
- ArrayList<Permission> backgroundPermissions = new ArrayList<>();
- if (mBackgroundPermissions != null) {
- backgroundPermissions = mBackgroundPermissions.getPermissions();
- }
- return Objects.hash(mName, mPackageInfo.packageName, mUserHandle, mPermissions,
- backgroundPermissions);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(getClass().getSimpleName());
- builder.append("{name=").append(mName);
- if (mBackgroundPermissions != null) {
- builder.append(", <has background permissions>}");
- }
- if (!mPermissions.isEmpty()) {
- builder.append(", <has permissions>}");
- } else {
- builder.append('}');
- }
- return builder.toString();
- }
-
- private void addPermission(Permission permission) {
- mPermissions.put(permission.getName(), permission);
- if (permission.isEphemeral()) {
- mContainsEphemeralPermission = true;
- }
- if (!permission.isRuntimeOnly()) {
- mContainsPreRuntimePermission = true;
- }
- }
-
- /**
- * If the changes to this group were delayed, persist them to the platform.
- *
- * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
- * app ops change. If this is set to {@code false} the
- * caller has to make sure to kill the app if needed.
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
- persistChanges(mayKillBecauseOfAppOpsChange, null, null);
- }
-
- /**
- * If the changes to this group were delayed, persist them to the platform.
- *
- * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
- * app ops change. If this is set to {@code false} the
- * caller has to make sure to kill the app if needed.
- * @param revokeReason If any permissions are getting revoked, the reason for revoking them.
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason) {
- persistChanges(mayKillBecauseOfAppOpsChange, revokeReason, null);
- }
-
- /**
- * If the changes to this group were delayed, persist them to the platform.
- *
- * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
- * app ops change. If this is set to {@code false} the
- * caller has to make sure to kill the app if needed.
- * @param revokeReason If any permissions are getting revoked, the reason for revoking them.
- * @param filterPermissions If provided, only persist state for the given permissions
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason,
- Set<String> filterPermissions) {
- int uid = mPackageInfo.applicationInfo.uid;
-
- int numPermissions = mPermissions.size();
- boolean shouldKillApp = false;
-
- for (int i = 0; i < numPermissions; i++) {
- Permission permission = mPermissions.valueAt(i);
-
- if (filterPermissions != null && !filterPermissions.contains(permission.getName())) {
- continue;
- }
-
- if (!permission.isSystemFixed()) {
- if (permission.isGranted()) {
- mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
- permission.getName(), mUserHandle);
- } else {
- boolean isCurrentlyGranted = mContext.checkPermission(permission.getName(), -1,
- uid) == PERMISSION_GRANTED;
-
- if (isCurrentlyGranted) {
- if (revokeReason == null) {
- mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
- permission.getName(), mUserHandle);
- } else {
- mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
- permission.getName(), mUserHandle, revokeReason);
- }
- }
- }
- }
-
-// int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
-// | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
-// | (permission.isRevokedCompat()
-// ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0)
-// | (permission.isPolicyFixed() ?
-// PackageManager.FLAG_PERMISSION_POLICY_FIXED : 0)
-// | (permission.isReviewRequired()
-// ? PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED : 0)
-// | (permission.isOneTime() ? PackageManager.FLAG_PERMISSION_ONE_TIME : 0)
-// | (permission.isSelectedLocationAccuracy()
-// ? PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY : 0);
-
-// mPackageManager.updatePermissionFlags(permission.getName(),
-// mPackageInfo.packageName,
-// PackageManager.FLAG_PERMISSION_USER_SET
-// | PackageManager.FLAG_PERMISSION_USER_FIXED
-// | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
-// | PackageManager.FLAG_PERMISSION_POLICY_FIXED
-// | (permission.isReviewRequired()
-// ? 0 : PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
-// | PackageManager.FLAG_PERMISSION_ONE_TIME
-// | PackageManager.FLAG_PERMISSION_AUTO_REVOKED // clear auto revoke
-// | PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY,
-// flags, mUserHandle);
-
- if (permission.affectsAppOp()) {
- if (!permission.isSystemFixed()) {
- // Enabling/Disabling an app op may put the app in a situation in which it has
- // a handle to state it shouldn't have, so we have to kill the app. This matches
- // the revoke runtime permission behavior.
- if (permission.isAppOpAllowed()) {
- boolean wasChanged = allowAppOp(permission, uid);
- shouldKillApp |= wasChanged && !mAppSupportsRuntimePermissions;
- } else {
- shouldKillApp |= disallowAppOp(permission, uid);
- }
- }
- }
- }
-
- if (mayKillBecauseOfAppOpsChange && shouldKillApp) {
- killApp(KILL_REASON_APP_OP_CHANGE);
- }
-
-// if (mTriggerLocationAccessCheckOnPersist) {
-// new LocationAccessCheck(mContext, null).checkLocationAccessSoon();
-// mTriggerLocationAccessCheckOnPersist = false;
-// }
-
-// String packageName = mPackageInfo.packageName;
-// if (areRuntimePermissionsGranted(null, true, false)) {
-// // Required to read device config in Utils.getOneTimePermissions*().
-// final long token = Binder.clearCallingIdentity();
-// try {
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-// mContext.getSystemService(PermissionManager.class)
-// .startOneTimePermissionSession(packageName,
-// Utils.getOneTimePermissionsTimeout(),
-// Utils.getOneTimePermissionsKilledDelay(mIsSelfRevoked),
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE);
-// } else {
-// mContext.getSystemService(PermissionManager.class)
-// .startOneTimePermissionSession(packageName,
-// Utils.getOneTimePermissionsTimeout(),
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE);
-// }
-// } finally {
-// Binder.restoreCallingIdentity(token);
-// }
-// } else {
-// mContext.getSystemService(PermissionManager.class)
-// .stopOneTimePermissionSession(packageName);
-// }
- }
-
- /**
- * Check if permission group contains a runtime permission that split from an installed
- * permission and the split happened in an Android version higher than app's targetSdk.
- *
- * @return {@code true} if there is such permission, {@code false} otherwise
- */
- public boolean hasInstallToRuntimeSplit() {
- PermissionManager permissionManager =
- (PermissionManager) mContext.getSystemService(PermissionManager.class);
-
- int numSplitPerms = permissionManager.getSplitPermissions().size();
- for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
- PermissionManager.SplitPermissionInfo spi =
- permissionManager.getSplitPermissions().get(splitPermNum);
- String splitPerm = spi.getSplitPermission();
-
- PermissionInfo pi;
- try {
- pi = mPackageManager.getPermissionInfo(splitPerm, 0);
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, "No such permission: " + splitPerm, e);
- continue;
- }
-
- // Skip if split permission is not "install" permission.
- if (pi.getProtection() != pi.PROTECTION_NORMAL) {
- continue;
- }
-
- List<String> newPerms = spi.getNewPermissions();
- int numNewPerms = newPerms.size();
- for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
- String newPerm = newPerms.get(newPermNum);
-
- if (!hasPermission(newPerm)) {
- continue;
- }
-
- try {
- pi = mPackageManager.getPermissionInfo(newPerm, 0);
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, "No such permission: " + newPerm, e);
- continue;
- }
-
- // Skip if new permission is not "runtime" permission.
- if (pi.getProtection() != pi.PROTECTION_DANGEROUS) {
- continue;
- }
-
- if (mPackageInfo.applicationInfo.targetSdkVersion < spi.getTargetSdk()) {
- return true;
- }
- }
- }
- return false;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java
deleted file mode 100644
index 9ef8d532dbfc..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.model;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * An app that requests permissions.
- *
- * <p>Allows to query all permission groups of the app and which permission belongs to which group.
- */
-public final class AppPermissions {
- /**
- * All permission groups the app requests. Background permission groups are attached to their
- * foreground groups.
- */
- private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>();
-
- /** Cache: group name -> group */
- private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>();
-
- /** Cache: permission name -> group. Might point to background group */
- private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>();
-
- private final Context mContext;
-
- private final CharSequence mAppLabel;
-
- private final Runnable mOnErrorCallback;
-
- private final boolean mSortGroups;
-
- /** Do not actually commit changes to the platform until {@link #persistChanges} is called */
- private final boolean mDelayChanges;
-
- private PackageInfo mPackageInfo;
-
- public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
- Runnable onErrorCallback) {
- this(context, packageInfo, sortGroups, false, onErrorCallback);
- }
-
- public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
- boolean delayChanges, Runnable onErrorCallback) {
- mContext = context;
- mPackageInfo = packageInfo;
- mAppLabel = null; // doesn't matter for CDM
- mSortGroups = sortGroups;
- mDelayChanges = delayChanges;
- mOnErrorCallback = onErrorCallback;
- loadPermissionGroups();
- }
-
- public PackageInfo getPackageInfo() {
- return mPackageInfo;
- }
-
- /**
- * Refresh package info and permission groups.
- */
- public void refresh() {
- loadPackageInfo();
- loadPermissionGroups();
- }
-
- public CharSequence getAppLabel() {
- return mAppLabel;
- }
-
- /**
- * Get permission group by name.
- */
- public AppPermissionGroup getPermissionGroup(String name) {
- return mGroupNameToGroup.get(name);
- }
-
- public List<AppPermissionGroup> getPermissionGroups() {
- return mGroups;
- }
-
- /**
- * Check if the group is review required.
- */
- public boolean isReviewRequired() {
- final int groupCount = mGroups.size();
- for (int i = 0; i < groupCount; i++) {
- AppPermissionGroup group = mGroups.get(i);
- if (group.isReviewRequired()) {
- return true;
- }
- }
- return false;
- }
-
- private void loadPackageInfo() {
- try {
- mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0,
- UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid))
- .getPackageManager().getPackageInfo(mPackageInfo.packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- if (mOnErrorCallback != null) {
- mOnErrorCallback.run();
- }
- }
- }
-
- /**
- * Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup}
- * lookup table.
- *
- * @param group The group of permissions to add
- */
- private void addAllPermissions(AppPermissionGroup group) {
- ArrayList<Permission> perms = group.getPermissions();
-
- int numPerms = perms.size();
- for (int permNum = 0; permNum < numPerms; permNum++) {
- mPermissionNameToGroup.put(perms.get(permNum).getName(), group);
- }
- }
-
- private void loadPermissionGroups() {
- mGroups.clear();
- mGroupNameToGroup.clear();
- mPermissionNameToGroup.clear();
-
- if (mPackageInfo.requestedPermissions != null) {
- for (String requestedPerm : mPackageInfo.requestedPermissions) {
- if (getGroupForPermission(requestedPerm) == null) {
- AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo,
- requestedPerm, mDelayChanges);
- if (group == null) {
- continue;
- }
-
- mGroups.add(group);
- mGroupNameToGroup.put(group.getName(), group);
-
- addAllPermissions(group);
-
- AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
- if (backgroundGroup != null) {
- addAllPermissions(backgroundGroup);
- }
- }
- }
-
- if (mSortGroups) {
- Collections.sort(mGroups);
- }
- }
- }
-
- /**
- * Find the group a permission belongs to.
- *
- * <p>The group found might be a background group.
- *
- * @param permission The name of the permission
- *
- * @return The group the permission belongs to
- */
- public AppPermissionGroup getGroupForPermission(String permission) {
- return mPermissionNameToGroup.get(permission);
- }
-
- /**
- * If the changes to the permission groups were delayed, persist them now.
- *
- * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
- * set to {@code false} the caller has to make sure to kill
- * the app if needed.
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
- persistChanges(mayKillBecauseOfAppOpsChange, null);
- }
-
- /**
- * If the changes to the permission groups were delayed, persist them now.
- *
- * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
- * set to {@code false} the caller has to make sure to kill
- * the app if needed.
- * @param filterPermissions If provided, only persist state for the given permissions
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange,
- Set<String> filterPermissions) {
- if (mDelayChanges) {
- int numGroups = mGroups.size();
-
- for (int i = 0; i < numGroups; i++) {
- AppPermissionGroup group = mGroups.get(i);
- group.persistChanges(mayKillBecauseOfAppOpsChange, null, filterPermissions);
-
- AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
- if (backgroundGroup != null) {
- backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange, null,
- filterPermissions);
- }
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java
deleted file mode 100644
index 2bec970a1dea..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.model;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
-
-import java.util.ArrayList;
-import java.util.Objects;
-
-/**
- * A permission and its properties.
- *
- * @see AppPermissionGroup
- */
-public final class Permission {
- private final @NonNull PermissionInfo mPermissionInfo;
- private final String mName;
- private final String mBackgroundPermissionName;
- private final String mAppOp;
-
- private boolean mGranted;
- private boolean mAppOpAllowed;
- private int mFlags;
- private boolean mIsEphemeral;
- private boolean mIsRuntimeOnly;
- private Permission mBackgroundPermission;
- private ArrayList<Permission> mForegroundPermissions;
- private boolean mWhitelisted;
-
- public Permission(String name, @NonNull PermissionInfo permissionInfo, boolean granted,
- String appOp, boolean appOpAllowed, int flags) {
- mPermissionInfo = permissionInfo;
- mName = name;
- mBackgroundPermissionName = permissionInfo.backgroundPermission;
- mGranted = granted;
- mAppOp = appOp;
- mAppOpAllowed = appOpAllowed;
- mFlags = flags;
- mIsEphemeral =
- (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
- mIsRuntimeOnly =
- (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
- }
-
- /**
- * Mark this permission as background permission for {@code foregroundPermissions}.
- *
- * @param foregroundPermission The foreground permission
- */
- public void addForegroundPermissions(Permission foregroundPermission) {
- if (mForegroundPermissions == null) {
- mForegroundPermissions = new ArrayList<>(1);
- }
- mForegroundPermissions.add(foregroundPermission);
- }
-
- /**
- * Mark this permission as foreground permission for {@code backgroundPermission}.
- *
- * @param backgroundPermission The background permission
- */
- public void setBackgroundPermission(Permission backgroundPermission) {
- mBackgroundPermission = backgroundPermission;
- }
-
- public PermissionInfo getPermissionInfo() {
- return mPermissionInfo;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getAppOp() {
- return mAppOp;
- }
-
- public int getFlags() {
- return mFlags;
- }
-
- boolean isHardRestricted() {
- return (mPermissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0;
- }
-
- boolean isSoftRestricted() {
- return (mPermissionInfo.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0;
- }
-
- /**
- * Does this permission affect app ops.
- *
- * <p>I.e. does this permission have a matching app op or is this a background permission. All
- * background permissions affect the app op of its assigned foreground permission.
- *
- * @return {@code true} if this permission affects app ops
- */
- public boolean affectsAppOp() {
- return mAppOp != null || isBackgroundPermission();
- }
-
- /**
- * Check if the permission is granted.
- *
- * <p>This ignores the state of the app-op. I.e. for apps not handling runtime permissions, this
- * always returns {@code true}.
- *
- * @return If the permission is granted
- */
- public boolean isGranted() {
- return mGranted;
- }
-
- /**
- * Check if the permission is granted, also considering the state of the app-op.
- *
- * <p>For the UI, check the grant state of the whole group via
- * {@link AppPermissionGroup#areRuntimePermissionsGranted}.
- *
- * @return {@code true} if the permission (and the app-op) is granted.
- */
- public boolean isGrantedIncludingAppOp() {
- return mGranted && (!affectsAppOp() || isAppOpAllowed()) && !isReviewRequired();
- }
-
- public boolean isReviewRequired() {
- return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
- }
-
- /**
- * Unset review required flag.
- */
- public void unsetReviewRequired() {
- mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
- }
-
- public void setGranted(boolean mGranted) {
- this.mGranted = mGranted;
- }
-
- public boolean isAppOpAllowed() {
- return mAppOpAllowed;
- }
-
- /**
- * Check if it's user fixed.
- */
- public boolean isUserFixed() {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0;
- }
-
- /**
- * Set user fixed flag.
- */
- public void setUserFixed(boolean userFixed) {
- if (userFixed) {
- mFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_USER_FIXED;
- }
- }
-
- /**
- * Sets the one-time permission flag
- * @param oneTime true to set the flag, false to unset it
- */
- public void setOneTime(boolean oneTime) {
- if (oneTime) {
- mFlags |= PackageManager.FLAG_PERMISSION_ONE_TIME;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_ONE_TIME;
- }
- }
-
- public boolean isSelectedLocationAccuracy() {
- return (mFlags & PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY) != 0;
- }
-
- /**
- * Sets the selected-location-accuracy permission flag
- * @param selectedLocationAccuracy true to set the flag, false to unset it
- */
- public void setSelectedLocationAccuracy(boolean selectedLocationAccuracy) {
- if (selectedLocationAccuracy) {
- mFlags |= PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
- }
- }
-
- public boolean isSystemFixed() {
- return (mFlags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0;
- }
-
- public boolean isPolicyFixed() {
- return (mFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
- }
-
- public boolean isUserSet() {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
- }
-
- public boolean isGrantedByDefault() {
- return (mFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0;
- }
-
- /**
- * Is the permission user sensitive, i.e. should it always be shown to the user.
- *
- * <p>Non-sensitive permission are usually hidden behind a setting in an overflow menu or
- * some other kind of flag.
- *
- * @return {@code true} if the permission is user sensitive.
- */
- public boolean isUserSensitive() {
- if (isGrantedIncludingAppOp()) {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
- } else {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0;
- }
- }
-
- /**
- * If this permission is split into a foreground and background permission, this is the name
- * of the background permission.
- *
- * @return The name of the background permission or {@code null} if the permission is not split
- */
- public String getBackgroundPermissionName() {
- return mBackgroundPermissionName;
- }
-
- /**
- * @return If this permission is split into a foreground and background permission,
- * returns the background permission
- */
- public Permission getBackgroundPermission() {
- return mBackgroundPermission;
- }
-
- /**
- * @return If this permission is split into a foreground and background permission,
- * returns the foreground permission
- */
- public ArrayList<Permission> getForegroundPermissions() {
- return mForegroundPermissions;
- }
-
- /**
- * @return {@code true} iff this is the foreground permission of a background-foreground-split
- * permission
- */
- public boolean hasBackgroundPermission() {
- return mBackgroundPermissionName != null;
- }
-
- /**
- * @return {@code true} iff this is the background permission of a background-foreground-split
- * permission
- */
- public boolean isBackgroundPermission() {
- return mForegroundPermissions != null;
- }
-
- /**
- * @see PackageManager#FLAG_PERMISSION_ONE_TIME
- */
- public boolean isOneTime() {
- return (mFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0;
- }
-
- /**
- * Set userSet flag.
- */
- public void setUserSet(boolean userSet) {
- if (userSet) {
- mFlags |= PackageManager.FLAG_PERMISSION_USER_SET;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_USER_SET;
- }
- }
-
- /**
- * Set policy fixed flag.
- */
- public void setPolicyFixed(boolean policyFixed) {
- if (policyFixed) {
- mFlags |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
- }
- }
-
- /**
- * Check if the permission is revoke compat.
- */
- public boolean isRevokedCompat() {
- return (mFlags & PackageManager.FLAG_PERMISSION_REVOKED_COMPAT) != 0;
- }
-
- /**
- * Set revoke compat flag.
- */
- public void setRevokedCompat(boolean revokedCompat) {
- if (revokedCompat) {
- mFlags |= PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
- }
- }
-
- /**
- * Set app op allowed flag.
- */
- public void setAppOpAllowed(boolean mAppOpAllowed) {
- this.mAppOpAllowed = mAppOpAllowed;
- }
-
- /**
- * Check if it's ephemeral.
- */
- public boolean isEphemeral() {
- return mIsEphemeral;
- }
-
- /**
- * Check if it's runtime only.
- */
- public boolean isRuntimeOnly() {
- return mIsRuntimeOnly;
- }
-
- /**
- * Check if it's granting allowed.
- */
- public boolean isGrantingAllowed(boolean isEphemeralApp, boolean supportsRuntimePermissions) {
- return (!isEphemeralApp || isEphemeral())
- && (supportsRuntimePermissions || !isRuntimeOnly());
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Permission)) {
- return false;
- }
-
- Permission other = (Permission) o;
-
- if (!Objects.equals(getName(), other.getName()) || getFlags() != other.getFlags()
- || isGranted() != other.isGranted()) {
- return false;
- }
-
-
- // Only compare permission names, in order to avoid recursion
- if (getBackgroundPermission() != null && other.getBackgroundPermission() != null) {
- if (!Objects.equals(getBackgroundPermissionName(),
- other.getBackgroundPermissionName())) {
- return false;
- }
- } else if (getBackgroundPermission() != other.getBackgroundPermission()) {
- return false;
- }
-
- if (getForegroundPermissions() != null && other.getForegroundPermissions() != null) {
- ArrayList<Permission> others = other.getForegroundPermissions();
- if (getForegroundPermissions().size() != others.size()) {
- return false;
- }
- for (int i = 0; i < others.size(); i++) {
- if (!getForegroundPermissions().get(i).getName().equals(others.get(i).getName())) {
- return false;
- }
- }
- } else if (getForegroundPermissions() != null || other.getForegroundPermissions() != null) {
- return false;
- }
-
- return Objects.equals(getAppOp(), other.getAppOp())
- && isAppOpAllowed() == other.isAppOpAllowed();
- }
-
- @Override
- public int hashCode() {
- ArrayList<String> linkedPermissionNames = new ArrayList<>();
- if (mBackgroundPermission != null) {
- linkedPermissionNames.add(mBackgroundPermission.getName());
- }
- if (mForegroundPermissions != null) {
- for (Permission linkedPermission: mForegroundPermissions) {
- if (linkedPermission != null) {
- linkedPermissionNames.add(linkedPermission.getName());
- }
- }
- }
- return Objects.hash(mName, mFlags, mGranted, mAppOp, mAppOpAllowed, linkedPermissionNames);
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java
deleted file mode 100644
index 7027528fc203..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.utils;
-
-import android.annotation.Nullable;
-
-import java.util.Objects;
-
-/**
- * Utils for array manipulation.
- */
-public final class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
-
- /**
- * Checks if an array is null or has no elements.
- *
- * @param array the array to check for
- *
- * @return whether the array is null or has no elements.
- */
- public static <T> boolean isEmpty(@Nullable T[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * Checks that value is present as at least one of the elements of the array.
- * @param array the array to check in
- * @param value the value to check for
- * @return true if the value is present in the array
- */
- public static <T> boolean contains(T[] array, T value) {
- return indexOf(array, value) != -1;
- }
-
- /**
- * Return first index of {@code value} in {@code array}, or {@code -1} if
- * not found.
- */
- public static <T> int indexOf(T[] array, T value) {
- if (array == null) return -1;
- for (int i = 0; i < array.length; i++) {
- if (Objects.equals(array[i], value)) return i;
- }
- return -1;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java
deleted file mode 100644
index 9402e46d16d9..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.utils;
-
-import static android.location.LocationManager.EXTRA_LOCATION_ENABLED;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.location.LocationManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Utils for location service.
- */
-public class LocationUtils {
-
- public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION;
- public static final String ACTIVITY_RECOGNITION_PERMISSION =
- Manifest.permission_group.ACTIVITY_RECOGNITION;
-
- private static final String TAG = LocationUtils.class.getSimpleName();
- private static final long LOCATION_UPDATE_DELAY_MS = 1000;
- private static final Handler sMainHandler = new Handler(Looper.getMainLooper());
-
-
- /** Start the settings page for the location controller extra package. */
- public static void startLocationControllerExtraPackageSettings(@NonNull Context context,
- @NonNull UserHandle user) {
- try {
- context.startActivityAsUser(new Intent(
- Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS), user);
- } catch (ActivityNotFoundException e) {
- // In rare cases where location controller extra package is set, but
- // no activity exists to handle the location controller extra package settings
- // intent, log an error instead of crashing permission controller.
- Log.e(TAG, "No activity to handle "
- + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS");
- }
- }
-
- /**
- * Check if location is enabled.
- */
- public static boolean isLocationEnabled(Context context) {
- return context.getSystemService(LocationManager.class).isLocationEnabled();
- }
-
- /** Checks if the provided package is a location provider. */
- public static boolean isLocationProvider(Context context, String packageName) {
- return context.getSystemService(LocationManager.class).isProviderPackage(packageName);
- }
-
- /**
- * Check if group is location and the package is a location provider.
- */
- public static boolean isLocationGroupAndProvider(Context context, String groupName,
- String packageName) {
- return LOCATION_PERMISSION.equals(groupName) && isLocationProvider(context, packageName);
- }
-
- /**
- * Check if group is location and package is extra location controller.
- */
- public static boolean isLocationGroupAndControllerExtraPackage(@NonNull Context context,
- @NonNull String groupName, @NonNull String packageName) {
- return (LOCATION_PERMISSION.equals(groupName)
- || ACTIVITY_RECOGNITION_PERMISSION.equals(groupName))
- && packageName.equals(context.getSystemService(LocationManager.class)
- .getExtraLocationControllerPackage());
- }
-
- /** Returns whether the location controller extra package is enabled. */
- public static boolean isExtraLocationControllerPackageEnabled(Context context) {
- try {
- return context.getSystemService(LocationManager.class)
- .isExtraLocationControllerPackageEnabled();
- } catch (Exception e) {
- return false;
- }
-
- }
-
- /**
- * A Listener which responds to enabling or disabling of location on the device
- */
- public interface LocationListener {
-
- /**
- * A callback run any time we receive a broadcast stating the location enable state has
- * changed.
- * @param enabled Whether or not location is enabled
- */
- void onLocationStateChange(boolean enabled);
- }
-
- private static final ArrayList<LocationListener> sLocationListeners = new ArrayList<>();
-
- private static BroadcastReceiver sLocationBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- boolean isEnabled = intent.getBooleanExtra(EXTRA_LOCATION_ENABLED, true);
- sMainHandler.postDelayed(() -> {
- synchronized (sLocationListeners) {
- for (LocationListener l : sLocationListeners) {
- l.onLocationStateChange(isEnabled);
- }
- }
- }, LOCATION_UPDATE_DELAY_MS);
- }
- };
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java
deleted file mode 100644
index d4940502f1e2..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.utils;
-
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageInfo;
-import android.os.Build;
-
-import com.android.server.companion.datatransfer.permbackup.model.Permission;
-
-/**
- * The behavior of soft restricted permissions is different for each permission. This class collects
- * the policies in one place.
- *
- * This is the twin of {@link com.android.server.policy.SoftRestrictedPermissionPolicy}
- */
-public abstract class SoftRestrictedPermissionPolicy {
-
- /**
- * Check if the permission should be shown in the UI.
- *
- * @param pkg the package the permission belongs to
- * @param permission the permission
- *
- * @return {@code true} iff the permission should be shown in the UI.
- */
- public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull Permission permission) {
- switch (permission.getName()) {
- case READ_EXTERNAL_STORAGE:
- case WRITE_EXTERNAL_STORAGE: {
- boolean isWhiteListed =
- (permission.getFlags() & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)
- != 0;
- int targetSDK = pkg.applicationInfo.targetSdkVersion;
-
- return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
- }
- default:
- return true;
- }
- }
-
- /**
- * Check if the permission should be shown in the UI.
- *
- * @param pkg the LightPackageInfo the permission belongs to
- * @param permissionName the name of the permission
- * @param permissionFlags the PermissionController flags (not the PermissionInfo flags) for
- * the permission
- *
- * @return {@code true} iff the permission should be shown in the UI.
- */
- public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull String permissionName,
- int permissionFlags) {
- switch (permissionName) {
- case READ_EXTERNAL_STORAGE:
- case WRITE_EXTERNAL_STORAGE: {
- boolean isWhiteListed =
- (permissionFlags & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- return isWhiteListed || pkg.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.Q;
- }
- default:
- return true;
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java
deleted file mode 100644
index 9350549d233e..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java
+++ /dev/null
@@ -1,819 +0,0 @@
-/*
- * Copyright (C) 2022 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.server.companion.datatransfer.permbackup.utils;
-
-import static android.Manifest.permission_group.ACTIVITY_RECOGNITION;
-import static android.Manifest.permission_group.CALENDAR;
-import static android.Manifest.permission_group.CALL_LOG;
-import static android.Manifest.permission_group.CAMERA;
-import static android.Manifest.permission_group.CONTACTS;
-import static android.Manifest.permission_group.LOCATION;
-import static android.Manifest.permission_group.MICROPHONE;
-import static android.Manifest.permission_group.NEARBY_DEVICES;
-import static android.Manifest.permission_group.NOTIFICATIONS;
-import static android.Manifest.permission_group.PHONE;
-import static android.Manifest.permission_group.READ_MEDIA_AURAL;
-import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
-import static android.Manifest.permission_group.SENSORS;
-import static android.Manifest.permission_group.SMS;
-import static android.Manifest.permission_group.STORAGE;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.Manifest;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.app.Application;
-import android.app.role.RoleManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
-import android.hardware.SensorPrivacyManager;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup;
-
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Util class for BackupHelper
- */
-public final class Utils {
-
- @Retention(SOURCE)
- @IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY,
- LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D})
- public @interface AppPermsLastAccessType {}
- public static final int LAST_24H_SENSOR_TODAY = 1;
- public static final int LAST_24H_SENSOR_YESTERDAY = 2;
- public static final int LAST_24H_CONTENT_PROVIDER = 3;
- public static final int LAST_7D_SENSOR = 4;
- public static final int LAST_7D_CONTENT_PROVIDER = 5;
- public static final int NOT_IN_LAST_7D = 6;
-
- private static final List<String> SENSOR_DATA_PERMISSIONS = List.of(
- Manifest.permission_group.LOCATION,
- Manifest.permission_group.CAMERA,
- Manifest.permission_group.MICROPHONE
- );
-
- public static final List<String> STORAGE_SUPERGROUP_PERMISSIONS =
-// (SDK_INT < Build.VERSION_CODES.TIRAMISU) ? List.of() :
- List.of(
- Manifest.permission_group.STORAGE,
- Manifest.permission_group.READ_MEDIA_AURAL,
- Manifest.permission_group.READ_MEDIA_VISUAL
- );
-
- private static final String LOG_TAG = "Utils";
-
- public static final String OS_PKG = "android";
-
- public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
-
- /** The time an app needs to be unused in order to be hibernated */
- public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
- "auto_revoke_unused_threshold_millis2";
-
- /** The frequency of running the job for hibernating apps */
- public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS =
- "auto_revoke_check_frequency_millis";
-
- /** Whether hibernation targets apps that target a pre-S SDK */
- public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
- "app_hibernation_targets_pre_s_apps";
-
- /** Whether or not app hibernation is enabled on the device **/
- public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
-
- /** Whether to show the Permissions Hub. */
- private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
-
- /** The timeout for one-time permissions */
- private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS =
- "one_time_permissions_timeout_millis";
-
- /** The delay before ending a one-time permission session when all processes are dead */
- private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS =
- "one_time_permissions_killed_delay_millis";
-
- /** Whether to show location access check notifications. */
- private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
- "location_access_check_enabled";
-
- /** The time an app needs to be unused in order to be hibernated */
- public static final String PROPERTY_PERMISSION_DECISIONS_CHECK_OLD_FREQUENCY_MILLIS =
- "permission_decisions_check_old_frequency_millis";
-
- /** The time an app needs to be unused in order to be hibernated */
- public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS =
- "permission_decisions_max_data_age_millis";
-
- /** Whether or not warning banner is displayed when device sensors are off **/
- public static final String PROPERTY_WARNING_BANNER_DISPLAY_ENABLED = "warning_banner_enabled";
-
- /** All permission whitelists. */
- public static final int FLAGS_PERMISSION_WHITELIST_ALL =
- PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
-
- /** All permission restriction exemptions. */
- public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
- FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-
- /**
- * The default length of the timeout for one-time permissions
- */
- public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute
-
- /**
- * The default length to wait before ending a one-time permission session after all processes
- * are dead.
- */
- public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000;
-
- /** Mapping permission -> group for all dangerous platform permissions */
- private static final ArrayMap<String, String> PLATFORM_PERMISSIONS;
-
- /** Mapping group -> permissions for all dangerous platform permissions */
- private static final ArrayMap<String, ArrayList<String>> PLATFORM_PERMISSION_GROUPS;
-
- /** Set of groups that will be able to receive one-time grant */
- private static final ArraySet<String> ONE_TIME_PERMISSION_GROUPS;
-
- /** Permission -> Sensor codes */
- private static final ArrayMap<String, Integer> PERM_SENSOR_CODES;
-
- public static final int FLAGS_ALWAYS_USER_SENSITIVE =
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
- | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
-
- private static final String SYSTEM_PKG = "android";
-
- private static final String SYSTEM_AMBIENT_AUDIO_INTELLIGENCE =
- "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE";
- private static final String SYSTEM_UI_INTELLIGENCE =
- "android.app.role.SYSTEM_UI_INTELLIGENCE";
- private static final String SYSTEM_AUDIO_INTELLIGENCE =
- "android.app.role.SYSTEM_AUDIO_INTELLIGENCE";
- private static final String SYSTEM_NOTIFICATION_INTELLIGENCE =
- "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE";
- private static final String SYSTEM_TEXT_INTELLIGENCE =
- "android.app.role.SYSTEM_TEXT_INTELLIGENCE";
- private static final String SYSTEM_VISUAL_INTELLIGENCE =
- "android.app.role.SYSTEM_VISUAL_INTELLIGENCE";
-
- // TODO: theianchen Using hardcoded values here as a WIP solution for now.
- private static final String[] EXEMPTED_ROLES = {
- SYSTEM_AMBIENT_AUDIO_INTELLIGENCE,
- SYSTEM_UI_INTELLIGENCE,
- SYSTEM_AUDIO_INTELLIGENCE,
- SYSTEM_NOTIFICATION_INTELLIGENCE,
- SYSTEM_TEXT_INTELLIGENCE,
- SYSTEM_VISUAL_INTELLIGENCE,
- };
-
- static {
- PLATFORM_PERMISSIONS = new ArrayMap<>();
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS);
-
- // If permissions are added to the Storage group, they must be added to the
- // STORAGE_PERMISSIONS list in PermissionManagerService in frameworks/base
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE);
-// if (SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-// PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE);
-// }
-
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_AUDIO, READ_MEDIA_AURAL);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_IMAGES, READ_MEDIA_VISUAL);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_VIDEO, READ_MEDIA_VISUAL);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, READ_MEDIA_VISUAL);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION);
-
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_ADVERTISE, NEARBY_DEVICES);
- PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_CONNECT, NEARBY_DEVICES);
- PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_SCAN, NEARBY_DEVICES);
- PLATFORM_PERMISSIONS.put(Manifest.permission.UWB_RANGING, NEARBY_DEVICES);
-// }
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.NEARBY_WIFI_DEVICES, NEARBY_DEVICES);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG);
- PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE);
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_BACKGROUND_AUDIO, MICROPHONE);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA);
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.BACKGROUND_CAMERA, CAMERA);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS);
-
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.POST_NOTIFICATIONS, NOTIFICATIONS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS_BACKGROUND, SENSORS);
-// }
-
- PLATFORM_PERMISSION_GROUPS = new ArrayMap<>();
- int numPlatformPermissions = PLATFORM_PERMISSIONS.size();
- for (int i = 0; i < numPlatformPermissions; i++) {
- String permission = PLATFORM_PERMISSIONS.keyAt(i);
- String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i);
-
- ArrayList<String> permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get(
- permissionGroup);
- if (permissionsOfThisGroup == null) {
- permissionsOfThisGroup = new ArrayList<>();
- PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup);
- }
-
- permissionsOfThisGroup.add(permission);
- }
-
- ONE_TIME_PERMISSION_GROUPS = new ArraySet<>();
- ONE_TIME_PERMISSION_GROUPS.add(LOCATION);
- ONE_TIME_PERMISSION_GROUPS.add(CAMERA);
- ONE_TIME_PERMISSION_GROUPS.add(MICROPHONE);
-
- PERM_SENSOR_CODES = new ArrayMap<>();
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA);
- PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE);
-// }
-
- }
-
- private Utils() {
- /* do nothing - hide constructor */
- }
-
- private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>();
-
- /**
- * Creates and caches a PackageContext for the requested user, or returns the previously cached
- * value. The package of the PackageContext is the application's package.
- *
- * @param app The currently running application
- * @param user The desired user for the context
- *
- * @return The generated or cached Context for the requested user
- *
- * @throws PackageManager.NameNotFoundException If the app has no package name attached
- */
- public static @NonNull Context getUserContext(Application app, UserHandle user) throws
- PackageManager.NameNotFoundException {
- if (!sUserContexts.containsKey(user)) {
- sUserContexts.put(user, app.getApplicationContext()
- .createPackageContextAsUser(app.getPackageName(), 0, user));
- }
- return sUserContexts.get(user);
- }
-
- /**
- * Returns true if a permission is dangerous, installed, and not removed
- * @param permissionInfo The permission we wish to check
- * @return If all of the conditions are met
- */
- public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) {
- return permissionInfo != null
- && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
- && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
- && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0;
- }
-
- /**
- * Get permission group a platform permission belongs to, or null if the permission is not a
- * platform permission.
- *
- * @param permission the permission to resolve
- *
- * @return The group the permission belongs to
- */
- public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) {
- return PLATFORM_PERMISSIONS.get(permission);
- }
-
- /**
- * Get name of the permission group a permission belongs to.
- *
- * @param permission the {@link PermissionInfo info} of the permission to resolve
- *
- * @return The group the permission belongs to
- */
- public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) {
- String groupName = Utils.getGroupOfPlatformPermission(permission.name);
- if (groupName == null) {
- groupName = permission.group;
- }
-
- return groupName;
- }
-
- /**
- * Get the names for all platform permissions belonging to a group.
- *
- * @param group the group
- *
- * @return The permission names or an empty list if the
- * group is not does not have platform runtime permissions
- */
- public static @NonNull List<String> getPlatformPermissionNamesOfGroup(@NonNull String group) {
- final ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
- return (permissions != null) ? permissions : Collections.emptyList();
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all platform permissions belonging to a group.
- *
- * @param pm Package manager to use to resolve permission infos
- * @param group the group
- *
- * @return The infos for platform permissions belonging to the group or an empty list if the
- * group is not does not have platform runtime permissions
- */
- public static @NonNull List<PermissionInfo> getPlatformPermissionsOfGroup(
- @NonNull PackageManager pm, @NonNull String group) {
- ArrayList<PermissionInfo> permInfos = new ArrayList<>();
-
- ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
- if (permissions == null) {
- return Collections.emptyList();
- }
-
- int numPermissions = permissions.size();
- for (int i = 0; i < numPermissions; i++) {
- String permName = permissions.get(i);
- PermissionInfo permInfo;
- try {
- permInfo = pm.getPermissionInfo(permName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalStateException(permName + " not defined by platform", e);
- }
-
- permInfos.add(permInfo);
- }
-
- return permInfos;
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
- *
- * @param pm Package manager to use to resolve permission infos
- * @param group the group
- *
- * @return The infos of permissions belonging to the group or an empty list if the group
- * does not have runtime permissions
- */
- public static @NonNull List<PermissionInfo> getPermissionInfosForGroup(
- @NonNull PackageManager pm, @NonNull String group)
- throws PackageManager.NameNotFoundException {
- List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
- permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
-
- /*
- * If the undefined group is requested, the package manager will return all platform
- * permissions, since they are marked as Undefined in the manifest. Do not return these
- * permissions.
- */
- if (group.equals(Manifest.permission_group.UNDEFINED)) {
- List<PermissionInfo> undefinedPerms = new ArrayList<>();
- for (PermissionInfo permissionInfo : permissions) {
- String permGroup = getGroupOfPlatformPermission(permissionInfo.name);
- if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
- undefinedPerms.add(permissionInfo);
- }
- }
- return undefinedPerms;
- }
-
- return permissions;
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to
- * a group.
- *
- * @param pm Package manager to use to resolve permission infos
- * @param group the group
- *
- * @return The infos of installed runtime permissions belonging to the group or an empty list
- * if the group does not have runtime permissions
- */
- public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup(
- @NonNull PackageManager pm, @NonNull String group)
- throws PackageManager.NameNotFoundException {
- List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
- permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
-
- List<PermissionInfo> installedRuntime = new ArrayList<>();
- for (PermissionInfo permissionInfo: permissions) {
- if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
- && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
- && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) {
- installedRuntime.add(permissionInfo);
- }
- }
-
- /*
- * If the undefined group is requested, the package manager will return all platform
- * permissions, since they are marked as Undefined in the manifest. Do not return these
- * permissions.
- */
- if (group.equals(Manifest.permission_group.UNDEFINED)) {
- List<PermissionInfo> undefinedPerms = new ArrayList<>();
- for (PermissionInfo permissionInfo : installedRuntime) {
- String permGroup = getGroupOfPlatformPermission(permissionInfo.name);
- if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
- undefinedPerms.add(permissionInfo);
- }
- }
- return undefinedPerms;
- }
-
- return installedRuntime;
- }
-
- /**
- * Get the {@link PackageItemInfo infos} for the given permission group.
- *
- * @param groupName the group
- * @param context the {@code Context} to retrieve {@code PackageManager}
- *
- * @return The info of permission group or null if the group does not have runtime permissions.
- */
- public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName,
- @NonNull Context context) {
- try {
- return context.getPackageManager().getPermissionGroupInfo(groupName, 0);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- try {
- return context.getPackageManager().getPermissionInfo(groupName, 0);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- return null;
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
- *
- * @param groupName the group
- * @param context the {@code Context} to retrieve {@code PackageManager}
- *
- * @return The infos of permissions belonging to the group or null if the group does not have
- * runtime permissions.
- */
- public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName,
- @NonNull Context context) {
- try {
- return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- try {
- PermissionInfo permissionInfo = context.getPackageManager()
- .getPermissionInfo(groupName, 0);
- List<PermissionInfo> permissions = new ArrayList<>();
- permissions.add(permissionInfo);
- return permissions;
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- return null;
- }
-
- /**
- * Get the names of the platform permission groups.
- *
- * @return the names of the platform permission groups.
- */
- public static List<String> getPlatformPermissionGroups() {
- return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet());
- }
-
- /**
- * Get the names of the runtime platform permissions
- *
- * @return the names of the runtime platform permissions.
- */
- public static List<String> getRuntimePlatformPermissionNames() {
- return new ArrayList<>(PLATFORM_PERMISSIONS.keySet());
- }
-
- /**
- * Is the permissions a platform runtime permission
- *
- * @return the names of the runtime platform permissions.
- */
- public static boolean isRuntimePlatformPermission(@NonNull String permission) {
- return PLATFORM_PERMISSIONS.containsKey(permission);
- }
-
- /**
- * Is the group or background group user sensitive?
- *
- * @param group The group that might be user sensitive
- *
- * @return {@code true} if the group (or it's subgroup) is user sensitive.
- */
- public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) {
- return group.isUserSensitive() || (group.getBackgroundPermissions() != null
- && group.getBackgroundPermissions().isUserSensitive());
- }
-
- /**
- * Whether or not the given package has non-isolated storage permissions
- * @param context The current context
- * @param packageName The package name to check
- * @return True if the package has access to non-isolated storage, false otherwise
- * @throws NameNotFoundException
- */
- public static boolean isNonIsolatedStorage(@NonNull Context context,
- @NonNull String packageName) throws NameNotFoundException {
- PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
- AppOpsManager manager = context.getSystemService(AppOpsManager.class);
-
-
- return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P
- || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R
- && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
- packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
- }
-
- /**
- * Build a string representing the given time if it happened on the current day and the date
- * otherwise.
- *
- * @param context the context.
- * @param lastAccessTime the time in milliseconds.
- *
- * @return a string representing the time or date of the given time or null if the time is 0.
- */
- public static @Nullable String getAbsoluteTimeString(@NonNull Context context,
- long lastAccessTime) {
- if (lastAccessTime == 0) {
- return null;
- }
- if (isToday(lastAccessTime)) {
- return DateFormat.getTimeFormat(context).format(lastAccessTime);
- } else {
- return DateFormat.getMediumDateFormat(context).format(lastAccessTime);
- }
- }
-
- /**
- * Check whether the given time (in milliseconds) is in the current day.
- *
- * @param time the time in milliseconds
- *
- * @return whether the given time is in the current day.
- */
- private static boolean isToday(long time) {
- Calendar today = Calendar.getInstance(Locale.getDefault());
- today.setTimeInMillis(System.currentTimeMillis());
- today.set(Calendar.HOUR_OF_DAY, 0);
- today.set(Calendar.MINUTE, 0);
- today.set(Calendar.SECOND, 0);
- today.set(Calendar.MILLISECOND, 0);
-
- Calendar date = Calendar.getInstance(Locale.getDefault());
- date.setTimeInMillis(time);
- return !date.before(today);
- }
-
- /**
- * Whether the Location Access Check is enabled.
- *
- * @return {@code true} iff the Location Access Check is enabled.
- */
- public static boolean isLocationAccessCheckEnabled() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true);
- }
-
- /**
- * Get one time permissions timeout
- */
- public static long getOneTimePermissionsTimeout() {
- return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
- PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS);
- }
-
- /**
- * Returns the delay in milliseconds before revoking permissions at the end of a one-time
- * permission session if all processes have been killed.
- * If the session was triggered by a self-revocation, then revocation should happen
- * immediately. For a regular one-time permission session, a grace period allows a quick
- * app restart without losing the permission.
- * @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise,
- * return delay for a regular one-time permission session.
- */
- public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) {
- if (isSelfRevoked) {
- // For a self-revoked session, we revoke immediately when the process dies.
- return 0;
- }
- return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
- PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS,
- ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS);
- }
-
- /**
- * Whether the permission group supports one-time
- * @param permissionGroup The permission group to check
- * @return {@code true} iff the group supports one-time
- */
- public static boolean supportsOneTimeGrant(String permissionGroup) {
- return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup);
- }
-
- /**
- * Checks whether a package has an active one-time permission according to the system server's
- * flags
- *
- * @param context the {@code Context} to retrieve {@code PackageManager}
- * @param packageName The package to check for
- * @return Whether a package has an active one-time permission
- */
- public static boolean hasOneTimePermissions(Context context, String packageName) {
- String[] permissions;
- PackageManager pm = context.getPackageManager();
- try {
- permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
- .requestedPermissions;
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package");
- return false;
- }
- if (permissions == null) {
- return false;
- }
- for (String permissionName : permissions) {
- if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle())
- & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0
- && pm.checkPermission(permissionName, packageName)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Gets the label of the Settings application
- *
- * @param pm The packageManager used to get the activity resolution
- *
- * @return The CharSequence title of the settings app
- */
- @Nullable
- public static CharSequence getSettingsLabelForNotifications(PackageManager pm) {
- // We pretend we're the Settings app sending the notification, so figure out its name.
- Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS);
- ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY);
- if (resolveInfo == null) {
- return null;
- }
- return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo);
- }
-
- /**
- * Get all the exempted packages.
- */
- public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) {
- Set<String> exemptedPackages = new HashSet<>();
-
- exemptedPackages.add(SYSTEM_PKG);
- for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
- exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i]));
- }
-
- return exemptedPackages;
- }
-
- /**
- * Returns if the permission group is Camera or Microphone (status bar indicators).
- **/
- public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) {
- return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName);
- }
-
- /**
- * Navigate to notification settings for all apps
- * @param context The current Context
- */
- public static void navigateToNotificationSettings(@NonNull Context context) {
- Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
- context.startActivity(notificationIntent);
- }
-
- /**
- * Navigate to notification settings for an app
- * @param context The current Context
- * @param packageName The package to navigate to
- * @param user Specifies the user of the package which should be navigated to. If null, the
- * current user is used.
- */
- public static void navigateToAppNotificationSettings(@NonNull Context context,
- @NonNull String packageName, @NonNull UserHandle user) {
- Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- context.startActivityAsUser(notificationIntent, user);
- }
-
- /**
- * Returns if a card should be shown if the sensor is blocked
- **/
- public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {
- return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_WARNING_BANNER_DISPLAY_ENABLED, true) && (
- CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName)
- || LOCATION.equals(permissionGroupName));
- }
-}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a01942d0dfa8..bb23d89d218f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1346,7 +1346,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
private String getDeviceOwnerDeletedPackageMsg() {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
return dpm.getResources().getString(PACKAGE_DELETED_BY_DO,
- () -> mContext.getString(R.string.package_updated_device_owner));
+ () -> mContext.getString(R.string.package_deleted_device_owner));
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index f242fda15f06..c80547ce61c0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -213,7 +213,7 @@ public class VirtualDeviceManagerServiceTest {
mContext.getSystemService(WindowManager.class), threadVerifier);
mAssociationInfo = new AssociationInfo(1, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
+ MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0);
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 0ddd52dfc76d..64a86db38396 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -22,6 +22,7 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.app.UiModeManager;
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -47,7 +48,7 @@ import java.util.List;
* in a call. It also provides the user with a means to initiate calls and see a history of calls
* on their device. A device is bundled with a system provided default dialer/phone app. The user
* may choose a single app to take over this role from the system app. An app which wishes to
- * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * fulfill this role uses the {@link android.app.role.RoleManager} to request that they fill the
* {@link android.app.role.RoleManager#ROLE_DIALER} role.
* <p>
* The default phone app provides a user interface while the device is in a call, and the device is
@@ -63,13 +64,23 @@ import java.util.List;
* UI, as well as an ongoing call UI.</li>
* </ul>
* <p>
- * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
- * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
- * dialer app pre-loaded on the device. The system will display a notification to the user to let
- * them know that the app has crashed and that their call was continued using the pre-loaded dialer
- * app.
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} returns a
+ * {@code null} {@link InCallService} during binding, the Telecom framework will automatically fall
+ * back to using the dialer app preloaded on the device. The system will display a notification to
+ * the user to let them know that their call was continued using the preloaded dialer app. Your
+ * app should never return a {@code null} binding; doing so means it does not fulfil the
+ * requirements of {@link android.app.role.RoleManager#ROLE_DIALER}.
* <p>
- * The pre-loaded dialer will ALWAYS be used when the user places an emergency call, even if your
+ * Note: If your app fills {@link android.app.role.RoleManager#ROLE_DIALER} and makes changes at
+ * runtime which cause it to no longer fulfil the requirements of this role,
+ * {@link android.app.role.RoleManager} will automatically remove your app from the role and close
+ * your app. For example, if you use
+ * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)} to
+ * programmatically disable the {@link InCallService} your app declares in its manifest, your app
+ * will no longer fulfil the requirements expected of
+ * {@link android.app.role.RoleManager#ROLE_DIALER}.
+ * <p>
+ * The preloaded dialer will ALWAYS be used when the user places an emergency call, even if your
* app fills the {@link android.app.role.RoleManager#ROLE_DIALER} role. To ensure an optimal
* experience when placing an emergency call, the default dialer should ALWAYS use
* {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls (including
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 0e5a1775bd6a..315c40ffa9ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -21,8 +21,10 @@ import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.FlickerComponentName
-val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher",
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+val LAUNCHER_COMPONENT = FlickerComponentName(
+ "com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+)
/**
* Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in
@@ -110,9 +112,9 @@ fun FlickerTestParameter.statusBarLayerIsVisible() {
fun FlickerTestParameter.navBarLayerPositionStart() {
assertLayersStart {
val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ ?: throw RuntimeException("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
}
@@ -123,9 +125,9 @@ fun FlickerTestParameter.navBarLayerPositionStart() {
fun FlickerTestParameter.navBarLayerPositionEnd() {
assertLayersEnd {
val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ ?: throw RuntimeException("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
}
@@ -244,11 +246,11 @@ fun FlickerTestParameter.replacesLayer(
assertLayersStart {
this.isVisible(originalLayer)
- .isInvisible(newLayer)
+ .isInvisible(newLayer)
}
assertLayersEnd {
this.isInvisible(originalLayer)
- .isVisible(newLayer)
+ .isVisible(newLayer)
}
}