Merge "Fix broken link in https://developer.android.com/reference/android/app/Notification#contentIntent." into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 119e0ad..6062f79 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3875,6 +3875,7 @@
method public void setInstallAsInstantApp(boolean);
method public void setInstallAsVirtualPreload();
method public void setRequestDowngrade(boolean);
+ method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index cd8938d..cbb20e0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2535,6 +2535,8 @@
public DataLoaderParams dataLoaderParams;
/** {@hide} */
public int rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+ /** @hide */
+ public long rollbackLifetimeMillis = 0;
/** {@hide} */
public boolean forceQueryableOverride;
/** {@hide} */
@@ -2589,6 +2591,7 @@
dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
}
rollbackDataPolicy = source.readInt();
+ rollbackLifetimeMillis = source.readLong();
requireUserAction = source.readInt();
packageSource = source.readInt();
applicationEnabledSettingPersistent = source.readBoolean();
@@ -2621,6 +2624,7 @@
ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
ret.dataLoaderParams = dataLoaderParams;
ret.rollbackDataPolicy = rollbackDataPolicy;
+ ret.rollbackLifetimeMillis = rollbackLifetimeMillis;
ret.requireUserAction = requireUserAction;
ret.packageSource = packageSource;
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
@@ -2902,12 +2906,7 @@
*/
@SystemApi
public void setEnableRollback(boolean enable) {
- if (enable) {
- installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
- } else {
- installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
- }
- rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+ setEnableRollback(enable, PackageManager.ROLLBACK_DATA_POLICY_RESTORE);
}
/**
@@ -2931,10 +2930,36 @@
installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
} else {
installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+ rollbackLifetimeMillis = 0;
}
rollbackDataPolicy = dataPolicy;
}
+ /**
+ * If rollback enabled for this session (via {@link #setEnableRollback}, set time
+ * after which rollback will no longer be possible
+ *
+ * <p>For multi-package installs, this value must be set on the parent session.
+ * Child session rollback lifetime will be ignored.
+ *
+ * @param lifetimeMillis time after which rollback expires
+ * @throws IllegalArgumentException if lifetimeMillis is negative or rollback is not
+ * enabled via setEnableRollback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
+ @FlaggedApi(Flags.FLAG_ROLLBACK_LIFETIME)
+ public void setRollbackLifetimeMillis(@DurationMillisLong long lifetimeMillis) {
+ if (lifetimeMillis < 0) {
+ throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative.");
+ }
+ if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackLifetimeMillis when rollback is not enabled");
+ }
+ rollbackLifetimeMillis = lifetimeMillis;
+ }
/**
* @deprecated use {@link #setRequestDowngrade(boolean)}.
@@ -3295,6 +3320,7 @@
pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
pw.printPair("dataLoaderParams", dataLoaderParams);
pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+ pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis);
pw.printPair("applicationEnabledSettingPersistent",
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
@@ -3336,6 +3362,7 @@
dest.writeParcelable(null, flags);
}
dest.writeInt(rollbackDataPolicy);
+ dest.writeLong(rollbackLifetimeMillis);
dest.writeInt(requireUserAction);
dest.writeInt(packageSource);
dest.writeBoolean(applicationEnabledSettingPersistent);
@@ -3529,6 +3556,9 @@
/** {@hide} */
public int rollbackDataPolicy;
+ /** @hide */
+ public long rollbackLifetimeMillis;
+
/** {@hide} */
public int requireUserAction;
@@ -3596,6 +3626,7 @@
isCommitted = source.readBoolean();
isPreapprovalRequested = source.readBoolean();
rollbackDataPolicy = source.readInt();
+ rollbackLifetimeMillis = source.readLong();
createdMillis = source.readLong();
requireUserAction = source.readInt();
installerUid = source.readInt();
@@ -4220,6 +4251,7 @@
dest.writeBoolean(isCommitted);
dest.writeBoolean(isPreapprovalRequested);
dest.writeInt(rollbackDataPolicy);
+ dest.writeLong(rollbackLifetimeMillis);
dest.writeLong(createdMillis);
dest.writeInt(requireUserAction);
dest.writeInt(installerUid);
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 96609ad..9ad66ce 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -58,3 +58,11 @@
bug: "295827951"
is_fixed_read_only: true
}
+
+flag {
+ name: "rollback_lifetime"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable custom rollback lifetime during install."
+ bug: "299670324"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 06b62f8..dd116b5 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -33,6 +33,7 @@
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -119,6 +120,11 @@
@Before
public void setUp() throws Exception {
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+ assumeFalse("AccessibilityShortcutChooserActivity not supported on watch",
+ pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
+
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mDevice.wakeUp();
when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
@@ -138,7 +144,9 @@
@After
public void cleanUp() {
- mScenario.close();
+ if (mScenario != null) {
+ mScenario.close();
+ }
}
@Test
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index bddcb6a..cf301c9 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -79,7 +79,4 @@
android:tint="@color/accent_tint_color_selector"
android:soundEffectsEnabled="false" />
</LinearLayout>
-
- <include layout="@layout/volume_dnd_icon"/>
-
</FrameLayout>
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index 3b70dc0..5ce2601 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -70,12 +70,6 @@
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="center"
android:soundEffectsEnabled="false" />
-
- <include layout="@layout/volume_dnd_icon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/volume_dialog_stream_padding"
- android:layout_marginTop="6dp"/>
</FrameLayout>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 6a192d4..39a1f1f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -69,12 +69,6 @@
android:tint="?android:attr/textColorPrimary"
android:layout_gravity="center"
android:soundEffectsEnabled="false" />
-
- <include layout="@layout/volume_dnd_icon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/volume_dialog_stream_padding"
- android:layout_marginTop="6dp"/>
</FrameLayout>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index c9256ae..f35de05 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -62,7 +62,6 @@
android:background="@null"
android:layoutDirection="ltr"
android:rotation="270" />
- <include layout="@layout/volume_dnd_icon"/>
</FrameLayout>
<com.android.keyguard.AlphaOptimizedImageButton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0ff308e..280c66a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -99,7 +99,6 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
-import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -256,7 +255,6 @@
private CaptionsToggleImageButton mODICaptionsIcon;
private View mSettingsView;
private ImageButton mSettingsIcon;
- private FrameLayout mZenIcon;
private final List<VolumeRow> mRows = new ArrayList<>();
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
@@ -633,7 +631,6 @@
mRinger = mDialog.findViewById(R.id.ringer);
if (mRinger != null) {
mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
- mZenIcon = mRinger.findViewById(R.id.dnd_icon);
}
mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon);
@@ -847,7 +844,6 @@
if (stream == STREAM_ACCESSIBILITY) {
row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
}
- row.dndIcon = row.view.findViewById(R.id.dnd_icon);
row.slider = row.view.findViewById(R.id.volume_row_slider);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
@@ -1791,27 +1787,13 @@
}
/**
- * Toggles enable state of views in a VolumeRow (not including seekbar or icon)
- * Hides/shows zen icon
- * @param enable whether to enable volume row views and hide dnd icon
- */
- private void enableVolumeRowViewsH(VolumeRow row, boolean enable) {
- boolean showDndIcon = !enable;
- row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE);
- }
-
- /**
* Toggles enable state of footer/ringer views
- * Hides/shows zen icon
- * @param enable whether to enable ringer views and hide dnd icon
+ * @param enable whether to enable ringer views
*/
private void enableRingerViewsH(boolean enable) {
if (mRingerIcon != null) {
mRingerIcon.setEnabled(enable);
}
- if (mZenIcon != null) {
- mZenIcon.setVisibility(enable ? GONE : VISIBLE);
- }
}
private void trimObsoleteH() {
@@ -1937,9 +1919,11 @@
// update icon
final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
final int iconRes;
- if (isRingVibrate) {
+ if (zenMuted) {
+ iconRes = com.android.internal.R.drawable.ic_qs_dnd;
+ } else if (isRingVibrate) {
iconRes = R.drawable.ic_volume_ringer_vibrate;
- } else if (isRingSilent || zenMuted) {
+ } else if (isRingSilent) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
if (isVoiceCallStream) {
@@ -2011,7 +1995,6 @@
if (zenMuted) {
row.tracking = false;
}
- enableVolumeRowViewsH(row, !zenMuted);
// update slider
final boolean enableSlider = !zenMuted;
@@ -2582,7 +2565,6 @@
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
- private FrameLayout dndIcon;
void setIcon(int iconRes, Resources.Theme theme) {
if (icon != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c4c7472..7456e00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -26,6 +26,8 @@
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
@@ -40,6 +42,9 @@
import android.app.KeyguardManager;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.SystemClock;
import android.provider.Settings;
@@ -52,6 +57,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageButton;
import androidx.test.core.view.MotionEventBuilder;
import androidx.test.filters.SmallTest;
@@ -90,6 +96,7 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
import java.util.function.Predicate;
@SmallTest
@@ -757,6 +764,86 @@
foundCaptionLog);
}
+ @Test
+ public void turnOnDnD_volumeSliderIconChangesToDnd() {
+ State state = createShellState();
+ state.zenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
+
+ mDialog.onStateChangedH(state);
+ mTestableLooper.processAllMessages();
+
+ boolean foundDnDIcon = findDndIconAmongVolumeRows();
+ assertTrue(foundDnDIcon);
+ }
+
+ @Test
+ public void turnOffDnD_volumeSliderIconIsNotDnd() {
+ State state = createShellState();
+ state.zenMode = Settings.Global.ZEN_MODE_OFF;
+
+ mDialog.onStateChangedH(state);
+ mTestableLooper.processAllMessages();
+
+ boolean foundDnDIcon = findDndIconAmongVolumeRows();
+ assertFalse(foundDnDIcon);
+ }
+
+ /**
+ * @return true if at least one volume row has the DND icon
+ */
+ private boolean findDndIconAmongVolumeRows() {
+ ViewGroup volumeDialogRows = mDialog.getDialogView().findViewById(R.id.volume_dialog_rows);
+ assumeNotNull(volumeDialogRows);
+ Drawable expected = getContext().getDrawable(com.android.internal.R.drawable.ic_qs_dnd);
+ boolean foundDnDIcon = false;
+ final int rowCount = volumeDialogRows.getChildCount();
+ // we don't make assumptions about the position of the dnd row
+ for (int i = 0; i < rowCount && !foundDnDIcon; i++) {
+ View volumeRow = volumeDialogRows.getChildAt(i);
+ ImageButton rowIcon = volumeRow.findViewById(R.id.volume_row_icon);
+ assertNotNull(rowIcon);
+
+ // VolumeDialogImpl changes tint and alpha in a private method, so we clear those here.
+ rowIcon.setImageTintList(null);
+ rowIcon.setAlpha(0xFF);
+
+ Drawable actual = rowIcon.getDrawable();
+ foundDnDIcon |= areDrawablesEqual(expected, actual);
+ }
+ return foundDnDIcon;
+ }
+
+ private boolean areDrawablesEqual(Drawable drawable1, Drawable drawable2) {
+ int size = 100;
+ Bitmap bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Bitmap bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+
+ Canvas canvas1 = new Canvas(bm1);
+ Canvas canvas2 = new Canvas(bm2);
+
+ drawable1.setBounds(0, 0, size, size);
+ drawable2.setBounds(0, 0, size, size);
+
+ drawable1.draw(canvas1);
+ drawable2.draw(canvas2);
+
+ boolean areBitmapsEqual = areBitmapsEqual(bm1, bm2);
+ bm1.recycle();
+ bm2.recycle();
+ return areBitmapsEqual;
+ }
+
+ private boolean areBitmapsEqual(Bitmap a, Bitmap b) {
+ if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;
+ int w = a.getWidth();
+ int h = a.getHeight();
+ int[] aPix = new int[w * h];
+ int[] bPix = new int[w * h];
+ a.getPixels(aPix, 0, w, 0, 0, w, h);
+ b.getPixels(bPix, 0, w, 0, 0, w, h);
+ return Arrays.equals(aPix, bPix);
+ }
+
@After
public void teardown() {
// Detailed logs to track down timeout issues in b/299491332
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b9b5908..305e353 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -44,6 +44,7 @@
import android.content.IntentSender.SendIntentException;
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedPackageParcel;
+import android.content.pm.Flags;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
@@ -745,6 +746,22 @@
params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
}
+ if (Flags.rollbackLifetime()) {
+ if (params.rollbackLifetimeMillis > 0) {
+ if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackLifetimeMillis when rollback is not enabled");
+ }
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Setting rollback lifetime requires the MANAGE_ROLLBACKS permission");
+ }
+ } else if (params.rollbackLifetimeMillis < 0) {
+ throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative.");
+ }
+ }
+
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (isApex) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5dc7dab..1be28ca 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1282,6 +1282,7 @@
info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions;
info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
info.installFlags = params.installFlags;
+ info.rollbackLifetimeMillis = params.rollbackLifetimeMillis;
info.isMultiPackage = params.isMultiPackage;
info.isStaged = params.isStaged;
info.rollbackDataPolicy = params.rollbackDataPolicy;
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 85d93f4..a5b90f1 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -174,6 +174,11 @@
@Nullable private final String mInstallerPackageName;
/**
+ * Time after which rollback expires.
+ */
+ private long mRollbackLifetimeMillis = 0;
+
+ /**
* Session ids for all packages in the install. For multi-package sessions, this is the list
* of child session ids. For normal sessions, this list is a single element with the normal
* session id.
@@ -286,6 +291,24 @@
}
/**
+ * Sets rollback lifetime in milliseconds, for purposes of expiring rollback data.
+ */
+ @WorkerThread
+ void setRollbackLifetimeMillis(long lifetimeMillis) {
+ assertInWorkerThread();
+ mRollbackLifetimeMillis = lifetimeMillis;
+ }
+
+ /**
+ * Returns rollback lifetime in milliseconds, for purposes of expiring rollback data.
+ */
+ @WorkerThread
+ long getRollbackLifetimeMillis() {
+ assertInWorkerThread();
+ return mRollbackLifetimeMillis;
+ }
+
+ /**
* Returns the session ID associated with this rollback, or {@code -1} if unknown.
*/
@AnyThread
@@ -930,6 +953,7 @@
ipw.println("-state: " + getStateAsString());
ipw.println("-stateDescription: " + mStateDescription);
ipw.println("-timestamp: " + getTimestamp());
+ ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
ipw.println("-isStaged: " + isStaged());
ipw.println("-originalSessionId: " + getOriginalSessionId());
ipw.println("-packages:");
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 720c773..8d93408 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -28,6 +28,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -702,6 +703,15 @@
// Schedules future expiration as appropriate.
@WorkerThread
private void runExpiration() {
+ if (Flags.rollbackLifetime()) {
+ runExpirationCustomRollbackLifetime();
+ } else {
+ runExpirationDefaultRollbackLifetime();
+ }
+ }
+
+ @WorkerThread
+ private void runExpirationDefaultRollbackLifetime() {
getHandler().removeCallbacks(mRunExpiration);
assertInWorkerThread();
Instant now = Instant.now();
@@ -729,6 +739,44 @@
}
}
+ @WorkerThread
+ private void runExpirationCustomRollbackLifetime() {
+ getHandler().removeCallbacks(mRunExpiration);
+ assertInWorkerThread();
+ Instant now = Instant.now();
+ long minDelay = 0;
+ Iterator<Rollback> iter = mRollbacks.iterator();
+ while (iter.hasNext()) {
+ Rollback rollback = iter.next();
+ if (!rollback.isAvailable() && !rollback.isCommitted()) {
+ continue;
+ }
+ long rollbackLifetimeMillis = rollback.getRollbackLifetimeMillis();
+ if (rollbackLifetimeMillis <= 0) {
+ rollbackLifetimeMillis = mRollbackLifetimeDurationInMillis;
+ }
+
+ Instant rollbackExpiryTimestamp = rollback.getTimestamp()
+ .plusMillis(rollbackLifetimeMillis);
+ if (!now.isBefore(rollbackExpiryTimestamp)) {
+ Slog.i(TAG, "runExpiration id=" + rollback.info.getRollbackId());
+ iter.remove();
+ deleteRollback(rollback, "Expired by timeout");
+ continue;
+ }
+
+ long delay = now.until(
+ rollbackExpiryTimestamp, ChronoUnit.MILLIS);
+ if (minDelay == 0 || delay < minDelay) {
+ minDelay = delay;
+ }
+ }
+
+ if (minDelay != 0) {
+ getHandler().postDelayed(mRunExpiration, minDelay);
+ }
+ }
+
@AnyThread
private Handler getHandler() {
return mHandler;
@@ -1277,6 +1325,7 @@
}
final Rollback rollback;
+
if (parentSession.isStaged()) {
rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId,
installerPackageName, packageSessionIds, getExtensionVersions());
@@ -1285,6 +1334,11 @@
installerPackageName, packageSessionIds, getExtensionVersions());
}
+ if (Flags.rollbackLifetime()) {
+ rollback.setRollbackLifetimeMillis(parentSession.rollbackLifetimeMillis);
+ }
+
+
mRollbacks.add(rollback);
return rollback;
}
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 8068c6f..0af137f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -19,6 +19,7 @@
import static com.android.server.rollback.Rollback.rollbackStateFromString;
import android.annotation.NonNull;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -312,6 +313,9 @@
JSONObject dataJson = new JSONObject();
dataJson.put("info", rollbackInfoToJson(rollback.info));
dataJson.put("timestamp", rollback.getTimestamp().toString());
+ if (Flags.rollbackLifetime()) {
+ dataJson.put("rollbackLifetimeMillis", rollback.getRollbackLifetimeMillis());
+ }
dataJson.put("originalSessionId", rollback.getOriginalSessionId());
dataJson.put("state", rollback.getStateAsString());
dataJson.put("stateDescription", rollback.getStateDescription());
@@ -375,7 +379,7 @@
@VisibleForTesting
static Rollback rollbackFromJson(JSONObject dataJson, File backupDir)
throws JSONException, ParseException {
- return new Rollback(
+ Rollback rollback = new Rollback(
rollbackInfoFromJson(dataJson.getJSONObject("info")),
backupDir,
Instant.parse(dataJson.getString("timestamp")),
@@ -388,6 +392,10 @@
dataJson.optInt("userId", UserHandle.SYSTEM.getIdentifier()),
dataJson.optString("installerPackageName", ""),
extensionVersionsFromJson(dataJson.optJSONArray("extensionVersions")));
+ if (Flags.rollbackLifetime()) {
+ rollback.setRollbackLifetimeMillis(dataJson.optLong("rollbackLifetimeMillis"));
+ }
+ return rollback;
}
private static JSONObject toJson(VersionedPackage pkg) throws JSONException {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fa104bb..6cad16c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -486,8 +486,6 @@
private boolean mForceShowForAllUsers;
- private boolean mForceTranslucent = false;
-
// The display category name for this task.
String mRequiredDisplayCategory;
@@ -4502,10 +4500,6 @@
return true;
}
- void setForceTranslucent(boolean set) {
- mForceTranslucent = set;
- }
-
@Override
public boolean isAlwaysOnTop() {
return !isForceHidden() && super.isAlwaysOnTop();
@@ -4523,11 +4517,6 @@
}
@Override
- protected boolean isForceTranslucent() {
- return mForceTranslucent;
- }
-
- @Override
long getProtoFieldId() {
return TASK;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2fc531a..00f2b89 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -374,6 +374,8 @@
@interface FlagForceHidden {}
protected int mForceHiddenFlags = 0;
+ private boolean mForceTranslucent = false;
+
final Point mLastSurfaceSize = new Point();
private final Rect mTmpBounds = new Rect();
@@ -843,8 +845,12 @@
return true;
}
- protected boolean isForceTranslucent() {
- return false;
+ boolean isForceTranslucent() {
+ return mForceTranslucent;
+ }
+
+ void setForceTranslucent(boolean set) {
+ mForceTranslucent = set;
}
boolean isLeafTaskFragment() {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index eb60aab..a8b9417 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -37,6 +37,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
+import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
@@ -768,8 +769,7 @@
}
}
- if ((c.getChangeMask()
- & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) {
+ if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
tr.setForceTranslucent(c.getForceTranslucent());
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -877,6 +877,11 @@
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
+ if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
+ taskFragment.setForceTranslucent(c.getForceTranslucent());
+ effects = TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
effects |= applyChanges(taskFragment, c);
if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
@@ -2022,9 +2027,11 @@
if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
// System organizer is allowed to update the hidden and focusable state.
- // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here.
+ // We unset the CHANGE_HIDDEN, CHANGE_FOCUSABLE, and CHANGE_FORCE_TRANSLUCENT bits
+ // because they are checked here.
changeMaskToBeChecked &= ~CHANGE_HIDDEN;
changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;
+ changeMaskToBeChecked &= ~CHANGE_FORCE_TRANSLUCENT;
}
// setRelativeBounds is allowed.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index cd3fef6..699580a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -101,6 +101,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.function.BiConsumer;
/**
* Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}.
@@ -583,7 +584,7 @@
}
@Test
- public void testTaskFragmentHiddenAndFocusableChanges() {
+ public void testTaskFragmentHiddenFocusableTranslucentChanges() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
@@ -605,10 +606,12 @@
assertTrue(taskFragment.shouldBeVisible(null));
assertTrue(taskFragment.isFocusable());
assertTrue(taskFragment.isTopActivityFocusable());
+ assertFalse(taskFragment.isForceTranslucent());
// Apply transaction to the TaskFragment hidden and not focusable.
t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+ t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
@@ -617,10 +620,12 @@
assertFalse(taskFragment.shouldBeVisible(null));
assertFalse(taskFragment.isFocusable());
assertFalse(taskFragment.isTopActivityFocusable());
+ assertTrue(taskFragment.isForceTranslucent());
// Apply transaction to the TaskFragment not hidden and focusable.
t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false);
t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+ t.setForceTranslucent(taskFragment.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
@@ -629,10 +634,32 @@
assertTrue(taskFragment.shouldBeVisible(null));
assertTrue(taskFragment.isFocusable());
assertTrue(taskFragment.isTopActivityFocusable());
+ assertFalse(taskFragment.isForceTranslucent());
}
@Test
- public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() {
+ public void testTaskFragmentChangeHidden_throwsWhenNotSystemOrganizer() {
+ // Non-system organizers are not allow to update the hidden state.
+ testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ (t, windowContainerToken) -> t.setHidden(windowContainerToken, true));
+ }
+
+ @Test
+ public void testTaskFragmentChangeFocusable_throwsWhenNotSystemOrganizer() {
+ // Non-system organizers are not allow to update the focusable state.
+ testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ (t, windowContainerToken) -> t.setFocusable(windowContainerToken, false));
+ }
+
+ @Test
+ public void testTaskFragmentChangeTranslucent_throwsWhenNotSystemOrganizer() {
+ // Non-system organizers are not allow to update the translucent state.
+ testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ (t, windowContainerToken) -> t.setForceTranslucent(windowContainerToken, true));
+ }
+
+ private void testTaskFragmentChangesWithoutSystemOrganizerThrowException(
+ BiConsumer<WindowContainerTransaction, WindowContainerToken> addOp) {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
@@ -641,21 +668,15 @@
final TaskFragmentOrganizer organizer =
createTaskFragmentOrganizer(t, false /* isSystemOrganizer */);
- final IBinder token = new Binder();
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(rootTask)
- .setFragmentToken(token)
+ .setFragmentToken(new Binder())
.setOrganizer(organizer)
.createActivityCount(1)
.build();
- assertTrue(rootTask.shouldBeVisible(null));
- assertTrue(taskFragment.shouldBeVisible(null));
+ addOp.accept(t, taskFragment.mRemoteToken.toWindowContainerToken());
- t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
- t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
-
- // Non-system organizers are not allow to update the hidden and focusable states.
assertThrows(SecurityException.class, () ->
mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,