Merge "Update boot image and system server profiles [M24C20P47S0PP]" into udc-dev
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7be00a0..3487e0b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -827,6 +827,12 @@
*/
public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
+ /**
+ * Whether AbiOverride was used when installing this application.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
PRIVATE_FLAG_EXT_PROFILEABLE,
@@ -834,6 +840,7 @@
PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
+ PRIVATE_FLAG_EXT_CPU_OVERRIDE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlagsExt {}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 931adb5..ef274a5 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.Application.ActivityLifecycleCallbacks;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -535,7 +536,11 @@
return null;
}
try {
- mContext.startIntentSender(intent, null, 0, 0, 0);
+ ActivityOptions activityOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(intent, null, 0, 0, 0,
+ activityOptions.toBundle());
return new PrintJob(printJob, this);
} catch (SendIntentException sie) {
Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7e4e402..5019b85 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1099,21 +1099,25 @@
// TODO: Support a ResultReceiver for IME.
// TODO(b/123718661): Make show() work for multi-session IME.
int typesReady = 0;
+ final boolean imeVisible = mState.isSourceOrDefaultVisible(
+ mImeSourceConsumer.getId(), ime());
for (int type = FIRST; type <= LAST; type = type << 1) {
if ((types & type) == 0) {
continue;
}
@AnimationType final int animationType = getAnimationType(type);
final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
- final boolean isImeAnimation = type == ime();
- if (requestedVisible && animationType == ANIMATION_TYPE_NONE
- || animationType == ANIMATION_TYPE_SHOW) {
+ final boolean isIme = type == ime();
+ var alreadyVisible = requestedVisible && (!isIme || imeVisible)
+ && animationType == ANIMATION_TYPE_NONE;
+ var alreadyAnimatingShow = animationType == ANIMATION_TYPE_SHOW;
+ if (alreadyVisible || alreadyAnimatingShow) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
if (DEBUG) Log.d(TAG, String.format(
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
@@ -1121,13 +1125,13 @@
}
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onProgress(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 3d95dd3..c9e7600 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,6 +81,10 @@
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
+
+ /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
+ public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
+ devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
}
//// == End of flags. Everything below this line is the implementation. == ////
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fb3acbe..a5b2b85 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5035,8 +5035,8 @@
<string name="display_rotation_camera_compat_toast_after_rotation">Rotate for a better view</string>
<!-- Text on a toast shown when a camera view is started within the app that may not be able
- to display the camera preview correctly while in split screen. [CHAR LIMIT=NONE] -->
- <string name="display_rotation_camera_compat_toast_in_split_screen">Exit split screen for a better view</string>
+ to display the camera preview correctly while in multi-window. [CHAR LIMIT=NONE] -->
+ <string name="display_rotation_camera_compat_toast_in_multi_window">Open <xliff:g id="name" example="MyApp">%s</xliff:g> in full screen for a better view</string>
<!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] -->
<string name="done_label">Done</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6ab671a..a8518af 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2533,7 +2533,7 @@
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
- <java-symbol type="string" name="display_rotation_camera_compat_toast_in_split_screen" />
+ <java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" />
<java-symbol type="array" name="config_system_condition_providers" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="zen_mode_alarm" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index e4defcf..93e44f1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1813,6 +1813,12 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-479665533": {
+ "message": "DisplayRotationCompatPolicy: Multi-window toast not shown as package '%s' cannot be found.",
+ "level": "ERROR",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-464564167": {
"message": "Current transition prevents automatic focus change",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index 82fe38c..7971c04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -135,14 +135,14 @@
maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+ shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
+ aspectRatio));
- maxHeight = (int) (maxWidth / aspectRatio);
+ maxHeight = Math.round(maxWidth / aspectRatio);
} else {
if (aspectRatio > 1f) {
maxWidth = shorterLength;
- maxHeight = (int) (maxWidth / aspectRatio);
+ maxHeight = Math.round(maxWidth / aspectRatio);
} else {
maxHeight = shorterLength;
- maxWidth = (int) (maxHeight * aspectRatio);
+ maxWidth = Math.round(maxHeight * aspectRatio);
}
}
@@ -165,10 +165,9 @@
Size maxSize = this.getMaxSize(aspectRatio);
- int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
+ int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
minSize.getWidth());
- int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
- minSize.getHeight());
+ int defaultHeight = Math.round(defaultWidth / aspectRatio);
return new Size(defaultWidth, defaultHeight);
}
@@ -188,16 +187,16 @@
Size maxSize = this.getMaxSize(aspectRatio);
- int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
- int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
+ int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
+ int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
// make sure the calculated min size is not smaller than the allowed default min size
if (aspectRatio > 1f) {
- minHeight = (int) Math.max(minHeight, mDefaultMinSize);
- minWidth = (int) (minHeight * aspectRatio);
+ minHeight = Math.max(minHeight, mDefaultMinSize);
+ minWidth = Math.round(minHeight * aspectRatio);
} else {
- minWidth = (int) Math.max(minWidth, mDefaultMinSize);
- minHeight = (int) (minWidth / aspectRatio);
+ minWidth = Math.max(minWidth, mDefaultMinSize);
+ minHeight = Math.round(minWidth / aspectRatio);
}
return new Size(minWidth, minHeight);
}
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 e616965..38911db 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
@@ -1482,6 +1482,10 @@
}
// If running background, we need to reparent current top visible task to main stage.
if (!isSplitScreenVisible()) {
+ // Ensure to evict old splitting tasks because the new split pair might be composed by
+ // one of the splitting tasks, evicting the task when finishing entering transition
+ // won't guarantee to put the task to the indicated new position.
+ mMainStage.evictAllChildren(wct);
mMainStage.reparentTopTask(wct);
prepareSplitLayout(wct);
}
@@ -2311,8 +2315,9 @@
mSplitTransitions.setDismissTransition(transition, dismissTop,
EXIT_REASON_APP_FINISHED);
} else if (!isSplitScreenVisible() && isOpening) {
- // If split running backgroud and trigger task is appearing into split,
- // prepare to enter split screen.
+ // If split is running in the background and the trigger task is appearing into
+ // split, prepare to enter split screen.
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, out);
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
@@ -2338,6 +2343,7 @@
if (isOpening && getStageOfTask(triggerTask) != null) {
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, out);
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
@@ -2901,7 +2907,8 @@
}
setSplitsVisible(false);
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
+ true /* reparentLeafTaskIfRelaunch */);
logExit(EXIT_REASON_UNKNOWN);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 425bbf0..1379aed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -106,21 +106,21 @@
sExpectedDefaultSizes = new HashMap<>();
sExpectedMinSizes = new HashMap<>();
- sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
- sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
- sExpectedMinSizes.put(16f / 9, new Size(499, 281));
+ sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+ sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
+ sExpectedMinSizes.put(16f / 9, new Size(501, 282));
sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
- sExpectedMinSizes.put(4f / 3, new Size(445, 334));
+ sExpectedMinSizes.put(4f / 3, new Size(447, 335));
sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
- sExpectedMinSizes.put(3f / 4, new Size(334, 445));
+ sExpectedMinSizes.put(3f / 4, new Size(335, 447));
sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
- sExpectedMinSizes.put(9f / 16, new Size(281, 499));
+ sExpectedMinSizes.put(9f / 16, new Size(281, 500));
}
private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 3527eee..2a6dc7b 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -108,7 +108,7 @@
{
// Migrate existing contents into new ashmem region
- uint32_t slotsSize = mSize - mSlotsOffset;
+ uint32_t slotsSize = sizeOfSlots();
uint32_t newSlotsOffset = mInflatedSize - slotsSize;
memcpy(static_cast<uint8_t*>(newData),
static_cast<uint8_t*>(mData), mAllocOffset);
@@ -216,11 +216,9 @@
if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
} else {
// Since we know we're going to be read-only on the remote side,
- // we can compact ourselves on the wire, with just enough padding
- // to ensure our slots stay aligned
- size_t slotsSize = mSize - mSlotsOffset;
- size_t compactedSize = mAllocOffset + slotsSize;
- compactedSize = (compactedSize + 3) & ~3;
+ // we can compact ourselves on the wire.
+ size_t slotsSize = sizeOfSlots();
+ size_t compactedSize = sizeInUse();
if (parcel->writeUint32(compactedSize)) goto fail;
if (parcel->writeBool(false)) goto fail;
void* dest = parcel->writeInplace(compactedSize);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index 6e55a9a..9ec026a 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -90,6 +90,9 @@
inline uint32_t getNumRows() { return mNumRows; }
inline uint32_t getNumColumns() { return mNumColumns; }
+ inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; }
+ inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); }
+
status_t clear();
status_t setNumColumns(uint32_t numColumns);
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index d1cfd03..3cdb31e 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -21,9 +21,16 @@
#include "TestHelpers.h"
+// Verify that the memory in use is a multiple of 4 bytes
+#define ASSERT_ALIGNED(w) \
+ ASSERT_EQ(((w)->sizeInUse() & 3), 0); \
+ ASSERT_EQ(((w)->freeSpace() & 3), 0); \
+ ASSERT_EQ(((w)->sizeOfSlots() & 3), 0)
+
#define CREATE_WINDOW_1K \
CursorWindow* w; \
- CursorWindow::create(String8("test"), 1 << 10, &w);
+ CursorWindow::create(String8("test"), 1 << 10, &w); \
+ ASSERT_ALIGNED(w);
#define CREATE_WINDOW_1K_3X3 \
CursorWindow* w; \
@@ -31,11 +38,13 @@
ASSERT_EQ(w->setNumColumns(3), OK); \
ASSERT_EQ(w->allocRow(), OK); \
ASSERT_EQ(w->allocRow(), OK); \
- ASSERT_EQ(w->allocRow(), OK);
+ ASSERT_EQ(w->allocRow(), OK); \
+ ASSERT_ALIGNED(w);
#define CREATE_WINDOW_2M \
CursorWindow* w; \
- CursorWindow::create(String8("test"), 1 << 21, &w);
+ CursorWindow::create(String8("test"), 1 << 21, &w); \
+ ASSERT_ALIGNED(w);
static constexpr const size_t kHalfInlineSize = 8192;
static constexpr const size_t kGiantSize = 1048576;
@@ -49,6 +58,7 @@
ASSERT_EQ(w->getNumColumns(), 0);
ASSERT_EQ(w->size(), 1 << 10);
ASSERT_EQ(w->freeSpace(), 1 << 10);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, SetNumColumns) {
@@ -60,6 +70,7 @@
ASSERT_NE(w->setNumColumns(5), OK);
ASSERT_NE(w->setNumColumns(3), OK);
ASSERT_EQ(w->getNumColumns(), 4);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, SetNumColumnsAfterRow) {
@@ -70,6 +81,7 @@
ASSERT_EQ(w->allocRow(), OK);
ASSERT_NE(w->setNumColumns(4), OK);
ASSERT_EQ(w->getNumColumns(), 0);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, AllocRow) {
@@ -83,14 +95,17 @@
ASSERT_EQ(w->allocRow(), OK);
ASSERT_LT(w->freeSpace(), before);
ASSERT_EQ(w->getNumRows(), 1);
+ ASSERT_ALIGNED(w);
// Verify we can unwind
ASSERT_EQ(w->freeLastRow(), OK);
ASSERT_EQ(w->freeSpace(), before);
ASSERT_EQ(w->getNumRows(), 0);
+ ASSERT_ALIGNED(w);
// Can't unwind when no rows left
ASSERT_NE(w->freeLastRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, AllocRowBounds) {
@@ -100,6 +115,7 @@
ASSERT_EQ(w->setNumColumns(60), OK);
ASSERT_EQ(w->allocRow(), OK);
ASSERT_NE(w->allocRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreNull) {
@@ -116,6 +132,7 @@
auto field = w->getFieldSlot(0, 0);
ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreLong) {
@@ -134,6 +151,7 @@
ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreString) {
@@ -155,6 +173,7 @@
auto actual = w->getFieldSlotValueString(field, &size);
ASSERT_EQ(std::string(actual), "cafe");
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreBounds) {
@@ -175,6 +194,7 @@
ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr);
ASSERT_EQ(w->getFieldSlot(0, -1), nullptr);
ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, Inflate) {
@@ -234,6 +254,7 @@
ASSERT_NE(actual, buf);
ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelEmpty) {
@@ -249,10 +270,12 @@
ASSERT_EQ(w->getNumColumns(), 0);
ASSERT_EQ(w->size(), 0);
ASSERT_EQ(w->freeSpace(), 0);
+ ASSERT_ALIGNED(w);
// We can't mutate the window after parceling
ASSERT_NE(w->setNumColumns(4), OK);
ASSERT_NE(w->allocRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelSmall) {
@@ -311,6 +334,7 @@
ASSERT_EQ(actualSize, 0);
ASSERT_NE(actual, nullptr);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelLarge) {
@@ -364,6 +388,7 @@
ASSERT_EQ(actualSize, 0);
ASSERT_NE(actual, nullptr);
}
+ ASSERT_ALIGNED(w);
}
} // android
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dac7f8d..b44c0e0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -929,7 +929,7 @@
<!-- UI debug setting: show all ANRs summary [CHAR LIMIT=100] -->
<string name="show_all_anrs_summary">Display App Not Responding dialog for background apps</string>
- <!-- UI debug setting: show missing channel toasts? [CHAR LIMIT=25] -->
+ <!-- UI debug setting: show missing channel toasts? [CHAR LIMIT=30] -->
<string name="show_notification_channel_warnings">Show notification channel warnings</string>
<!-- UI debug setting: show missing channel toasts summary [CHAR LIMIT=50] -->
<string name="show_notification_channel_warnings_summary">Displays on-screen warning when an app posts a notification without a valid channel</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fe90caf..0a9a184 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -913,8 +913,9 @@
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
- android:visibleToInstantApps="true">
- </activity>
+ android:visibleToInstantApps="true"
+ android:exported="true"
+ />
<activity android:name=".controls.management.ControlsEditingActivity"
android:label="@string/controls_menu_edit"
@@ -947,8 +948,9 @@
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|orientation"
- android:visibleToInstantApps="true">
- </activity>
+ android:visibleToInstantApps="true"
+ android:exported="true"
+ />
<activity android:name=".wallet.ui.WalletActivity"
android:label="@string/wallet_title"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 89871fa7..2cd587f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -56,7 +56,19 @@
val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
val pixelDensity: Float = 1f,
val blendMode: BlendMode = DEFAULT_BLEND_MODE,
- val onAnimationEnd: Runnable? = null
+ val onAnimationEnd: Runnable? = null,
+ /**
+ * Variants in noise. Higher number means more contrast; lower number means less contrast but
+ * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate.
+ * Expected range [0, 1].
+ */
+ val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR,
+ /**
+ * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may
+ * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected
+ * range [0, 1].
+ */
+ val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS
) {
companion object {
const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -66,6 +78,8 @@
const val DEFAULT_NOISE_SPEED_Z = 0.3f
const val DEFAULT_OPACITY = 150 // full opacity is 255.
const val DEFAULT_COLOR = Color.WHITE
+ const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
+ const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index d1ba7c4..d3c57c9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -37,6 +37,8 @@
uniform float in_opacity;
uniform float in_pixelDensity;
uniform float in_inverseLuma;
+ uniform half in_lumaMatteBlendFactor;
+ uniform half in_lumaMatteOverallBrightness;
layout(color) uniform vec4 in_color;
layout(color) uniform vec4 in_backgroundColor;
"""
@@ -48,18 +50,21 @@
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity;
+ // Bring it to [0, 1] range.
+ float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+ luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
vec3 mask = maskLuminosity(in_color.rgb, luma);
vec3 color = in_backgroundColor.rgb + mask * 0.6;
- // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // Add dither with triangle distribution to avoid color banding. Dither in the
// shader here as we are in gamma space.
float dither = triangleNoise(p * in_pixelDensity) / 255.;
// The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
// multiply rgb with a to get the correct result.
- color = (color + dither.rrr) * in_color.a;
- return vec4(color, in_color.a);
+ color = (color + dither.rrr) * in_opacity;
+ return vec4(color, in_opacity);
}
"""
@@ -70,12 +75,15 @@
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity;
+ // Bring it to [0, 1] range.
+ float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+ luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
vec3 mask = maskLuminosity(in_color.rgb, luma);
vec3 color = in_backgroundColor.rgb + mask * 0.6;
// Skip dithering.
- return vec4(color * in_color.a, in_color.a);
+ return vec4(color * in_opacity, in_opacity);
}
"""
@@ -125,6 +133,28 @@
}
/**
+ * Sets blend and brightness factors of the luma matte.
+ *
+ * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
+ * this a lower number removes variations. I.e. the turbulence noise will look more blended.
+ * Expected input range is [0, 1]. more dimmed.
+ * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
+ * Expected input range is [0, 1].
+ *
+ * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2,
+ * which makes the noise look softer. However it makes the overall noise look dim, so you want
+ * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall
+ * brightness.
+ */
+ fun setLumaMatteFactors(
+ lumaMatteBlendFactor: Float = 1f,
+ lumaMatteOverallBrightness: Float = 0f
+ ) {
+ setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor)
+ setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness)
+ }
+
+ /**
* Sets whether to inverse the luminosity of the noise.
*
* By default noise will be used as a luma matte as is. This means that you will see color in
@@ -132,7 +162,7 @@
* true.
*/
fun setInverseNoiseLuminosity(inverse: Boolean) {
- setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f)
+ setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f)
}
/** Current noise movements in x, y, and z axes. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c3e8478..43d6504 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -215,10 +215,12 @@
noiseConfig = config
with(turbulenceNoiseShader) {
setGridCount(config.gridCount)
- setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setColor(config.color)
setBackgroundColor(config.backgroundColor)
setSize(config.width, config.height)
setPixelDensity(config.pixelDensity)
+ setInverseNoiseLuminosity(inverse = false)
+ setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
}
paint.blendMode = config.blendMode
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index cd9fb88..519ae0a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -163,15 +163,14 @@
*/
val enablementActionText: String? = null,
/**
- * If the affordance is disabled, this is a "component name" of the format
- * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
- * (shown together with the set of instruction messages when the disabled affordance is
- * selected) is clicked by the user. The button should help send the user to a flow that
- * would help them achieve the instructions and re-enable this affordance.
+ * If the affordance is disabled, this is an [Intent] to be used with `startActivity` when
+ * the action button (shown together with the set of instruction messages when the disabled
+ * affordance is selected) is clicked by the user. The button should help send the user to a
+ * flow that would help them achieve the instructions and re-enable this affordance.
*
* If `null`, the button should not be shown.
*/
- val enablementActionComponentName: String? = null,
+ val enablementActionIntent: Intent? = null,
/** Optional [Intent] to use to start an activity to configure this affordance. */
val configureIntent: Intent? = null,
)
@@ -337,10 +336,10 @@
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_ACTION_TEXT
)
- val enablementComponentNameColumnIndex =
+ val enablementActionIntentColumnIndex =
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
- .ENABLEMENT_COMPONENT_NAME
+ .ENABLEMENT_ACTION_INTENT
)
val configureIntentColumnIndex =
cursor.getColumnIndex(
@@ -354,7 +353,7 @@
isEnabledColumnIndex == -1 ||
enablementInstructionsColumnIndex == -1 ||
enablementActionTextColumnIndex == -1 ||
- enablementComponentNameColumnIndex == -1 ||
+ enablementActionIntentColumnIndex == -1 ||
configureIntentColumnIndex == -1
) {
return@buildList
@@ -377,12 +376,18 @@
),
enablementActionText =
cursor.getString(enablementActionTextColumnIndex),
- enablementActionComponentName =
- cursor.getString(enablementComponentNameColumnIndex),
+ enablementActionIntent =
+ cursor
+ .getString(enablementActionIntentColumnIndex)
+ ?.toIntent(
+ affordanceId = affordanceId,
+ ),
configureIntent =
cursor
.getString(configureIntentColumnIndex)
- ?.toIntent(affordanceId = affordanceId),
+ ?.toIntent(
+ affordanceId = affordanceId,
+ ),
)
)
}
@@ -524,7 +529,7 @@
affordanceId: String,
): Intent? {
return try {
- Intent.parseUri(this, 0)
+ Intent.parseUri(this, Intent.URI_INTENT_SCHEME)
} catch (e: URISyntaxException) {
Log.w(TAG, "Cannot parse Uri into Intent for affordance with ID \"$affordanceId\"!")
null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index f9e8aaf..7f5fb25 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -82,7 +82,6 @@
val URI: Uri =
LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build()
const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
- const val COMPONENT_NAME_SEPARATOR = "/"
object Columns {
/** String. Unique ID for this affordance. */
@@ -108,11 +107,11 @@
*/
const val ENABLEMENT_ACTION_TEXT = "enablement_action_text"
/**
- * String. Optional package name and activity action string, delimited by
- * [COMPONENT_NAME_SEPARATOR] to use with an `Intent` to start an activity that
- * opens a destination where the user can re-enable the disabled affordance.
+ * String. Optional URI-formatted `Intent` (formatted using
+ * `Intent#toUri(Intent.URI_INTENT_SCHEME)` used to start an activity that opens a
+ * destination where the user can re-enable the disabled affordance.
*/
- const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent"
+ const val ENABLEMENT_ACTION_INTENT = "enablement_action_intent"
/**
* Byte array. Optional parcelled `Intent` to use to start an activity that can be
* used to configure the affordance.
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index a8f0cc3..4a9d41f 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,20 +14,6 @@
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp">
- <vector android:width="18dp"
- android:height="18dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16,16L2.81,2.81L1.39,4.22l4.85,4.85C6.09,9.68 6,10.33 6,11v6H4v2h12.17l3.61,3.61l1.41,-1.41L16,16zM8,17c0,0 0.01,-6.11 0.01,-6.16L14.17,17H8z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,6.5c2.49,0 4,2.02 4,4.5v2.17l2,2V11c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.72,4.86 9.05,5.2 8.46,5.63L9.93,7.1C10.51,6.73 11.2,6.5 12,6.5z"/>
- </vector>
-</inset>
+ android:insetLeft="3dp"
+ android:insetRight="3dp"
+ android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c3651cf..166bd2a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -902,4 +902,16 @@
<!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
device entry. -->
<integer name="primary_bouncer_passive_auth_delay">500</integer>
+
+ <!--
+ The package name of the app store app. If empty, features using this should be gracefully
+ disabled.
+ -->
+ <string name="config_appStorePackageName" translatable="false"></string>
+
+ <!-- Template for a link that leads to an app page in the relevant app store. If empty,
+ features using this should be gracefully disabled. If not empty, it must include a
+ "$packageName" part that will be replaced by the code with the package name of the target app.
+ -->
+ <string name="config_appStoreAppLinkTemplate" translatable="false"></string>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8d3ba36..4f768cc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -572,6 +572,7 @@
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
+ <item name="qqs_expand_clock_scale" format="float" type="dimen">2.57</item>
<!-- Most of the time it should be the same as notification_side_paddings as it's vertically
aligned with notifications. The exception is split shade when this value becomes
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b48296fe..e779672 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2724,8 +2724,8 @@
<string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
<!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
<string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
- <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
- <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
+ <!-- The hint message when Broadcast edit is more than 16/254 characters [CHAR LIMIT=60] -->
+ <string name="media_output_broadcast_edit_hint_no_more_than_max">Use fewer than <xliff:g id="length" example="16">%1$d</xliff:g> characters</string>
<!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_clip_data_label">Build number</string>
@@ -3153,4 +3153,11 @@
<!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
<string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
+
+ <!--
+ Label for a button that, when clicked, sends the user to the app store to install an app.
+
+ [CHAR LIMIT=64].
+ -->
+ <string name="install_app">Install app</string>
</resources>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 52a98984..8039c68 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -43,8 +43,8 @@
app:layout_constraintBottom_toBottomOf="@id/carrier_group"
/>
<Transform
- android:scaleX="2.57"
- android:scaleY="2.57"
+ android:scaleX="@dimen/qqs_expand_clock_scale"
+ android:scaleY="@dimen/qqs_expand_clock_scale"
/>
</Constraint>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1721891..83c317f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3565,7 +3565,6 @@
*/
private void handleTimeUpdate() {
Assert.isMainThread();
- mLogger.d("handleTimeUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -3630,9 +3629,9 @@
private void handleBatteryUpdate(BatteryStatus status) {
Assert.isMainThread();
final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
- mLogger.logHandleBatteryUpdate(batteryUpdateInteresting);
mBatteryStatus = status;
if (batteryUpdateInteresting) {
+ mLogger.logHandleBatteryUpdate(mBatteryStatus);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 4923ab0..b596331 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -30,7 +30,7 @@
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.TrustGrantFlags
-import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.settingslib.fuelgauge.BatteryStatus
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogLevel.DEBUG
@@ -38,6 +38,7 @@
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -683,8 +684,27 @@
)
}
- fun logHandleBatteryUpdate(isInteresting: Boolean) {
- logBuffer.log(TAG, DEBUG, { bool1 = isInteresting }, { "handleBatteryUpdate: $bool1" })
+ fun logHandleBatteryUpdate(batteryStatus: BatteryStatus?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = batteryStatus != null
+ int1 = batteryStatus?.status ?: -1
+ int2 = batteryStatus?.chargingStatus ?: -1
+ long1 = (batteryStatus?.level ?: -1).toLong()
+ long2 = (batteryStatus?.maxChargingWattage ?: -1).toLong()
+ str1 = "${batteryStatus?.plugged ?: -1}"
+ },
+ {
+ "handleBatteryUpdate: isNotNull: $bool1 " +
+ "BatteryStatus{status= $int1, " +
+ "level=$long1, " +
+ "plugged=$str1, " +
+ "chargingStatus=$int2, " +
+ "maxChargingWattage= $long2}"
+ }
+ )
}
fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f86e2ed..7f70685 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -614,7 +614,11 @@
return ((AuthBiometricFingerprintView) view).isUdfps();
}
if (view instanceof BiometricPromptLayout) {
- return ((BiometricPromptLayout) view).isUdfps();
+ // this will force the prompt to align itself on the edge of the screen
+ // instead of centering (temporary workaround to prevent small implicit view
+ // from breaking due to the way gravity / margins are set in the legacy
+ // AuthPanelController
+ return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index baaa96e..d48b9c33 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -35,6 +35,7 @@
import android.util.Log
import android.util.RotationUtils
import android.view.Display
+import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
@@ -58,6 +59,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import java.io.PrintWriter
@@ -129,6 +131,8 @@
}
@VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+ private val displayInfo = DisplayInfo()
+
private val overlayViewParams =
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
@@ -214,6 +218,23 @@
for (requestSource in requests) {
pw.println(" $requestSource.name")
}
+
+ pw.println("overlayView:")
+ pw.println(" width=${overlayView?.width}")
+ pw.println(" height=${overlayView?.height}")
+ pw.println(" boundsOnScreen=${overlayView?.boundsOnScreen}")
+
+ pw.println("displayStateInteractor:")
+ pw.println(" isInRearDisplayMode=${displayStateInteractor?.isInRearDisplayMode?.value}")
+
+ pw.println("sensorProps:")
+ pw.println(" displayId=${displayInfo.uniqueId}")
+ pw.println(" sensorType=${sensorProps?.sensorType}")
+ pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+
+ pw.println("overlayOffsets=$overlayOffsets")
+ pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
+ pw.println("currentRotation=${displayInfo.rotation}")
}
private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
@@ -226,6 +247,8 @@
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
val display = context.display!!
+ // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
val offsets =
sensorProps.getLocation(display.uniqueId).let { location ->
if (location == null) {
@@ -239,12 +262,12 @@
view.rotation =
display.asSideFpsAnimationRotation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
lottie.setAnimation(
display.asSideFpsAnimation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
)
lottie.addLottieOnCompositionLoadedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e4c4e9a..1dffa80 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,8 +19,11 @@
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import androidx.core.animation.addListener
@@ -52,7 +55,9 @@
panelViewController: AuthPanelController,
jankListener: BiometricJankListener,
) {
- val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+ val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
+ val accessibilityManager =
+ requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
fun notifyAccessibilityChanged() {
Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
}
@@ -102,15 +107,26 @@
when {
size.isSmall -> {
iconHolderView.alpha = 1f
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
iconHolderView.y =
- view.height - iconHolderView.height - iconPadding
+ if (view.isLandscape()) {
+ (view.height - iconHolderView.height - bottomInset) / 2f
+ } else {
+ view.height -
+ iconHolderView.height -
+ iconPadding -
+ bottomInset
+ }
val newHeight =
- iconHolderView.height + 2 * iconPadding.toInt() -
+ iconHolderView.height + (2 * iconPadding.toInt()) -
iconHolderView.paddingTop -
iconHolderView.paddingBottom
panelViewController.updateForContentDimensions(
width,
- newHeight,
+ newHeight + bottomInset,
0, /* animateDurationMs */
)
}
@@ -181,6 +197,11 @@
}
}
+private fun View.isLandscape(): Boolean {
+ val r = context.display.rotation
+ return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+}
+
private fun TextView.showTextOrHide(forceHide: Boolean = false) {
visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
index d7d1700..c921806 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.controller
interface ControlsTileResourceConfiguration {
+ fun getPackageName(): String?
fun getTileTitleId(): Int
fun getTileImageId(): Int
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
index c96d3d4..0249060 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt
@@ -20,12 +20,14 @@
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
-/**
- * Default Instance for ControlsTileResourceConfiguration.
- */
+/** Default Instance for ControlsTileResourceConfiguration. */
@SysUISingleton
-class ControlsTileResourceConfigurationImpl @Inject constructor()
- : ControlsTileResourceConfiguration {
+class ControlsTileResourceConfigurationImpl @Inject constructor() :
+ ControlsTileResourceConfiguration {
+ override fun getPackageName(): String? {
+ return null
+ }
+
override fun getTileTitleId(): Int {
return R.string.quick_controls_title
}
@@ -33,4 +35,4 @@
override fun getTileImageId(): Int {
return R.drawable.controls_icon
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 7509a8a..94e5633 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -29,9 +29,9 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
-import kotlinx.coroutines.flow.StateFlow
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -40,26 +40,26 @@
* instantiated if `featureEnabled` is true. Can also be queried for the availability of controls.
*/
@SysUISingleton
-class ControlsComponent @Inject constructor(
- @ControlsFeatureEnabled private val featureEnabled: Boolean,
- private val context: Context,
- private val lazyControlsController: Lazy<ControlsController>,
- private val lazyControlsUiController: Lazy<ControlsUiController>,
- private val lazyControlsListingController: Lazy<ControlsListingController>,
- private val lockPatternUtils: LockPatternUtils,
- private val keyguardStateController: KeyguardStateController,
- private val userTracker: UserTracker,
- controlsSettingsRepository: ControlsSettingsRepository,
- optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
+class ControlsComponent
+@Inject
+constructor(
+ @ControlsFeatureEnabled private val featureEnabled: Boolean,
+ private val context: Context,
+ private val lazyControlsController: Lazy<ControlsController>,
+ private val lazyControlsUiController: Lazy<ControlsUiController>,
+ private val lazyControlsListingController: Lazy<ControlsListingController>,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ controlsSettingsRepository: ControlsSettingsRepository,
+ optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
) {
val canShowWhileLockedSetting: StateFlow<Boolean> =
- controlsSettingsRepository.canShowControlsInLockscreen
+ controlsSettingsRepository.canShowControlsInLockscreen
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
- optionalControlsTileResourceConfiguration.orElse(
- ControlsTileResourceConfigurationImpl()
- )
+ optionalControlsTileResourceConfiguration.orElse(ControlsTileResourceConfigurationImpl())
fun getControlsController(): Optional<ControlsController> {
return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
@@ -77,9 +77,7 @@
}
}
- /**
- * @return true if controls are feature-enabled and the user has the setting enabled
- */
+ /** @return true if controls are feature-enabled and the user has the setting enabled */
fun isEnabled() = featureEnabled
/**
@@ -90,8 +88,10 @@
*/
fun getVisibility(): Visibility {
if (!isEnabled()) return Visibility.UNAVAILABLE
- if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
- == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
+ if (
+ lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier) ==
+ STRONG_AUTH_REQUIRED_AFTER_BOOT
+ ) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
@@ -102,7 +102,13 @@
}
enum class Visibility {
- AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
+ AVAILABLE,
+ AVAILABLE_AFTER_UNLOCK,
+ UNAVAILABLE
+ }
+
+ fun getPackageName(): String? {
+ return controlsTileResourceConfiguration.getPackageName()
}
fun getTileTitleId(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5fa209..a7e5c22 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -728,6 +728,13 @@
@JvmField
val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
+ // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name>
+
+ // TODO:(b/285623104): Tracking bug
+ @JvmField
+ val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
+ releasedFlag(2900, "zj_285570694_lockscreen_transition_from_aod")
+
// TODO(b/283084712): Tracking Bug
@JvmField
val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 27a5974..064a44a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -20,6 +20,7 @@
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
+import android.content.Intent
import android.content.UriMatcher
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
@@ -286,7 +287,7 @@
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_ACTION_TEXT,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
- .ENABLEMENT_COMPONENT_NAME,
+ .ENABLEMENT_ACTION_INTENT,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.CONFIGURE_INTENT,
)
)
@@ -303,8 +304,8 @@
.ENABLEMENT_INSTRUCTIONS_DELIMITER
),
representation.actionText,
- representation.actionComponentName,
- representation.configureIntent?.toUri(0),
+ representation.actionIntent?.toUri(Intent.URI_INTENT_SCHEME),
+ representation.configureIntent?.toUri(Intent.URI_INTENT_SCHEME),
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index abb63c4..0991b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.annotation.DrawableRes
@@ -34,6 +35,7 @@
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.appStoreIntent
import com.android.systemui.util.kotlin.getOrNull
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -76,18 +78,31 @@
component.getControlsListingController().getOrNull()?.getCurrentServices()
val hasFavorites =
component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
- if (currentServices.isNullOrEmpty() || !hasFavorites) {
- return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- instructions =
- listOf(
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_home_instruction_1
- ),
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_home_instruction_2
- ),
- ),
- )
+ val componentPackageName = component.getPackageName()
+ when {
+ currentServices.isNullOrEmpty() && !componentPackageName.isNullOrEmpty() -> {
+ // No home app installed but we know which app we want to install.
+ return disabledPickerState(
+ actionText = context.getString(R.string.install_app),
+ actionIntent = appStoreIntent(context, componentPackageName),
+ )
+ }
+ currentServices.isNullOrEmpty() && componentPackageName.isNullOrEmpty() -> {
+ // No home app installed and we don't know which app we want to install.
+ return disabledPickerState()
+ }
+ !hasFavorites -> {
+ // Home app installed but no favorites selected.
+ val activityClass = component.getControlsUiController().get().resolveActivity()
+ return disabledPickerState(
+ actionText = context.getString(R.string.controls_open_app),
+ actionIntent =
+ Intent().apply {
+ component = ComponentName(context, activityClass)
+ putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ },
+ )
+ }
}
return KeyguardQuickAffordanceConfig.PickerScreenState.Default()
@@ -172,6 +187,27 @@
}
}
+ private fun disabledPickerState(
+ actionText: String? = null,
+ actionIntent: Intent? = null,
+ ): KeyguardQuickAffordanceConfig.PickerScreenState.Disabled {
+ check(actionIntent == null || actionText != null)
+
+ return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_home_instruction_1
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_home_instruction_2
+ ),
+ ),
+ actionText = actionText,
+ actionIntent = actionIntent,
+ )
+ }
+
companion object {
private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 28dc5bd..42fe9bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -18,11 +18,13 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.AlertDialog
+import android.content.Context
import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -80,9 +82,9 @@
/**
* The picker shows the item for selecting this affordance as disabled. Clicking on it will
- * show the given instructions to the user. If [actionText] and [actionComponentName] are
- * provided (optional) a button will be shown to open an activity to help the user complete
- * the steps described in the instructions.
+ * show the given instructions to the user. If [actionText] and [actionIntent] are provided
+ * (optional) a button will be shown to open an activity to help the user complete the steps
+ * described in the instructions.
*/
data class Disabled(
/** List of human-readable instructions for setting up the quick affordance. */
@@ -93,24 +95,22 @@
*/
val actionText: String? = null,
/**
- * Optional component name to be able to build an `Intent` that opens an `Activity` for
- * the user to be able to set up the quick affordance and make it enabled.
- *
- * This is either just an action for the `Intent` or a package name and action,
- * separated by
- * [Contract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR] for
- * convenience, you can use the [componentName] function.
+ * Optional [Intent] that opens an `Activity` for the user to be able to set up the
+ * quick affordance and make it enabled.
*/
- val actionComponentName: String? = null,
+ val actionIntent: Intent? = null,
) : PickerScreenState() {
init {
check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
check(
- (actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) ||
- (!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty())
+ (actionText.isNullOrEmpty() && actionIntent == null) ||
+ (!actionText.isNullOrEmpty() && actionIntent != null)
) {
- "actionText and actionComponentName must either both be null/empty or both be" +
- " non-empty!"
+ """
+ actionText and actionIntent must either both be null/empty or both be
+ non-null and non-empty!
+ """
+ .trimIndent()
}
}
}
@@ -163,17 +163,33 @@
}
companion object {
- fun componentName(
- packageName: String? = null,
- action: String?,
- ): String? {
- return when {
- action.isNullOrEmpty() -> null
- !packageName.isNullOrEmpty() ->
- "$packageName${Contract.LockScreenQuickAffordances.AffordanceTable
- .COMPONENT_NAME_SEPARATOR}$action"
- else -> action
+
+ /**
+ * Returns an [Intent] that can be used to start an activity that opens the app store app to
+ * a page showing the app with the passed-in [packageName].
+ *
+ * If the feature isn't enabled on this device/variant/configuration, a `null` will be
+ * returned.
+ */
+ fun appStoreIntent(context: Context, packageName: String?): Intent? {
+ if (packageName.isNullOrEmpty()) {
+ return null
+ }
+
+ val appStorePackageName = context.getString(R.string.config_appStorePackageName)
+ val linkTemplate = context.getString(R.string.config_appStoreAppLinkTemplate)
+ if (appStorePackageName.isEmpty() || linkTemplate.isEmpty()) {
+ return null
+ }
+
+ check(linkTemplate.contains(APP_PACKAGE_NAME_PLACEHOLDER))
+
+ return Intent(Intent.ACTION_VIEW).apply {
+ setPackage(appStorePackageName)
+ data = Uri.parse(linkTemplate.replace(APP_PACKAGE_NAME_PLACEHOLDER, packageName))
}
}
+
+ private const val APP_PACKAGE_NAME_PLACEHOLDER = "\$packageName"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 44e74e7..9db3c22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -63,7 +63,7 @@
val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
- isFeatureEnabled = walletController.isWalletEnabled,
+ isFeatureEnabled = isWalletAvailable(),
hasCard = hasCards,
tileIcon = walletController.walletClient.tileIcon,
),
@@ -100,7 +100,7 @@
return when {
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !walletController.isWalletEnabled || queryCards().isEmpty() -> {
+ !isWalletAvailable() || queryCards().isEmpty() -> {
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
@@ -146,6 +146,11 @@
}
}
+ private fun isWalletAvailable() =
+ with(walletController.walletClient) {
+ isWalletServiceAvailable && isWalletFeatureAvailable
+ }
+
private fun state(
isFeatureEnabled: Boolean,
hasCard: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 96c94d7..bd3b83c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.content.Context
+import android.content.Intent
import android.os.UserHandle
import android.util.LayoutDirection
import com.android.systemui.Dumpable
@@ -176,8 +177,18 @@
pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
instructions = disabledPickerState?.instructions,
actionText = disabledPickerState?.actionText,
- actionComponentName = disabledPickerState?.actionComponentName,
- configureIntent = defaultPickerState?.configureIntent,
+ actionIntent =
+ disabledPickerState?.actionIntent?.apply {
+ addFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ )
+ },
+ configureIntent =
+ defaultPickerState?.configureIntent?.apply {
+ addFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ )
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index e7e9159..3c96eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -41,11 +41,8 @@
*/
val actionText: String? = null,
- /**
- * If not enabled, an optional component name (package and action) for a button that takes the
- * user to a destination where they can re-enable it.
- */
- val actionComponentName: String? = null,
+ /** Optional [Intent] to use to start an activity to re-enable this affordance. */
+ val actionIntent: Intent? = null,
/** Optional [Intent] to use to start an activity to configure this affordance. */
val configureIntent: Intent? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 516fbf5..14386c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -76,7 +76,6 @@
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.widget.CachingIconView;
@@ -1211,24 +1210,24 @@
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
return new TurbulenceNoiseAnimationConfig(
- TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ /* gridCount= */ 2.14f,
TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
- /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedX= */ 0.42f,
/* noiseMoveSpeedY= */ 0f,
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
- // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
- // Thus, set the background color with alpha 0.
- /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
- TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* backgroundColor= */ Color.BLACK,
+ /* opacity= */ 51,
/* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
/* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
- /* easeInDuration= */ 2500f,
- /* easeOutDuration= */ 2500f,
+ /* easeInDuration= */ 1350f,
+ /* easeOutDuration= */ 1350f,
this.getContext().getResources().getDisplayMetrics().density,
- BlendMode.PLUS,
- /* onAnimationEnd= */ null
+ BlendMode.SCREEN,
+ /* onAnimationEnd= */ null,
+ /* lumaMatteBlendFactor= */ 0.26f,
+ /* lumaMatteOverallBrightness= */ 0.09f
);
}
private void clearButton(final ImageButton button) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 529b980..b4578e9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -59,6 +59,17 @@
public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private static final String TAG = "MediaOutputBroadcastDialog";
+ static final int METADATA_BROADCAST_NAME = 0;
+ static final int METADATA_BROADCAST_CODE = 1;
+
+ private static final int MAX_BROADCAST_INFO_UPDATE = 3;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MAX_LENGTH = 16;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MIN_LENGTH = 4;
+ @VisibleForTesting
+ static final int BROADCAST_NAME_MAX_LENGTH = 254;
+
private ViewStub mBroadcastInfoArea;
private ImageView mBroadcastQrCodeView;
private ImageView mBroadcastNotify;
@@ -68,14 +79,16 @@
private ImageView mBroadcastCodeEye;
private Boolean mIsPasswordHide = true;
private ImageView mBroadcastCodeEdit;
- private AlertDialog mAlertDialog;
+ @VisibleForTesting
+ AlertDialog mAlertDialog;
private TextView mBroadcastErrorMessage;
private int mRetryCount = 0;
private String mCurrentBroadcastName;
private String mCurrentBroadcastCode;
private boolean mIsStopbyUpdateBroadcastCode = false;
+ private boolean mIsLeBroadcastAssistantCallbackRegistered;
- private TextWatcher mTextWatcher = new TextWatcher() {
+ private TextWatcher mBroadcastCodeTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
@@ -103,7 +116,9 @@
R.string.media_output_broadcast_code_hint_no_less_than_min);
} else if (breakBroadcastCodeRuleTextLengthMoreThanMax) {
mBroadcastErrorMessage.setText(
- R.string.media_output_broadcast_code_hint_no_more_than_max);
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_CODE_MAX_LENGTH));
}
mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE);
@@ -114,7 +129,40 @@
}
};
- private boolean mIsLeBroadcastAssistantCallbackRegistered;
+ private TextWatcher mBroadcastNameTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mAlertDialog == null || mBroadcastErrorMessage == null) {
+ return;
+ }
+ boolean breakBroadcastNameRuleTextLengthMoreThanMax =
+ s.length() > BROADCAST_NAME_MAX_LENGTH;
+ boolean breakRule = breakBroadcastNameRuleTextLengthMoreThanMax || (s.length() == 0);
+
+ if (breakBroadcastNameRuleTextLengthMoreThanMax) {
+ mBroadcastErrorMessage.setText(
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_NAME_MAX_LENGTH));
+ }
+ mBroadcastErrorMessage.setVisibility(
+ breakBroadcastNameRuleTextLengthMoreThanMax ? View.VISIBLE : View.INVISIBLE);
+ Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveBtn != null) {
+ positiveBtn.setEnabled(breakRule ? false : true);
+ }
+ }
+ };
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@@ -187,13 +235,6 @@
}
};
- static final int METADATA_BROADCAST_NAME = 0;
- static final int METADATA_BROADCAST_CODE = 1;
-
- private static final int MAX_BROADCAST_INFO_UPDATE = 3;
- private static final int BROADCAST_CODE_MAX_LENGTH = 16;
- private static final int BROADCAST_CODE_MIN_LENGTH = 4;
-
MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
super(context, broadcastSender, mediaOutputController);
@@ -392,13 +433,12 @@
R.layout.media_output_broadcast_update_dialog, null);
final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
editText.setText(editString);
- if (isBroadcastCode) {
- editText.addTextChangedListener(mTextWatcher);
- }
+ editText.addTextChangedListener(
+ isBroadcastCode ? mBroadcastCodeTextWatcher : mBroadcastNameTextWatcher);
mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message);
mAlertDialog = new Builder(mContext)
.setTitle(isBroadcastCode ? R.string.media_output_broadcast_code
- : R.string.media_output_broadcast_name)
+ : R.string.media_output_broadcast_name)
.setView(layout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.media_output_broadcast_dialog_save,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 0ce20cd..8da7cec 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -19,6 +19,7 @@
import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
import android.content.Context
+import android.content.Intent
import android.hardware.input.InputSettings
import android.os.Build
import android.os.UserHandle
@@ -42,7 +43,6 @@
import com.android.systemui.notetask.NoteTaskEnabledKey
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskInfoResolver
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
import com.android.systemui.stylus.StylusManager
import dagger.Lazy
import java.util.concurrent.Executor
@@ -130,8 +130,9 @@
context.getString(
R.string.keyguard_affordance_enablement_dialog_notes_app_action
),
- "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
- "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE",
+ Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
+ setPackage(context.packageName)
+ }
)
}
else -> PickerScreenState.UnavailableOnDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 897b0e7..5d02830 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -65,14 +65,12 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.DisplayTracker;
-import dagger.Lazy;
-
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
-
+import dagger.Lazy;
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index a066242..d7ae575 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,6 +20,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -45,6 +47,11 @@
): CurrentTilesInteractor
@Binds
+ abstract fun provideInstalledTilesPackageRepository(
+ impl: InstalledTilesComponentRepositoryImpl
+ ): InstalledTilesComponentRepository
+
+ @Binds
@IntoMap
@ClassKey(PrototypeCoreStartable::class)
abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
new file mode 100644
index 0000000..498f403
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.annotation.WorkerThread
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.isComponentActuallyEnabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+interface InstalledTilesComponentRepository {
+
+ fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
+}
+
+@SysUISingleton
+class InstalledTilesComponentRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val packageManager: PackageManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : InstalledTilesComponentRepository {
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+ conflatedCallbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ trySend(Unit)
+ }
+ }
+ applicationContext.registerReceiverAsUser(
+ receiver,
+ UserHandle.of(userId),
+ INTENT_FILTER,
+ /* broadcastPermission = */ null,
+ /* scheduler = */ null
+ )
+
+ awaitClose { applicationContext.unregisterReceiver(receiver) }
+ }
+ .onStart { emit(Unit) }
+ .map { reloadComponents(userId) }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ @WorkerThread
+ private fun reloadComponents(userId: Int): Set<ComponentName> {
+ return packageManager
+ .queryIntentServicesAsUser(INTENT, FLAGS, userId)
+ .mapNotNull { it.serviceInfo }
+ .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
+ .filter { packageManager.isComponentActuallyEnabled(it) }
+ .mapTo(mutableSetOf()) { it.componentName }
+ }
+
+ companion object {
+ private val INTENT_FILTER =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_PACKAGE_ADDED)
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addAction(Intent.ACTION_PACKAGE_REMOVED)
+ addAction(Intent.ACTION_PACKAGE_REPLACED)
+ addDataScheme("package")
+ }
+ private val INTENT = Intent(TileService.ACTION_QS_TILE)
+ private val FLAGS =
+ ResolveInfoFlags.of(
+ (PackageManager.GET_SERVICES or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
+ .toLong()
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 3b2362f..a162d11 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -42,6 +42,8 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
/** Repository that tracks the current tiles. */
@@ -104,6 +106,8 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : TileSpecRepository {
+ private val mutex = Mutex()
+
private val retailModeTiles by lazy {
resources
.getString(R.string.quick_settings_tiles_retail_mode)
@@ -145,37 +149,40 @@
.flowOn(backgroundDispatcher)
}
- override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
- if (tile == TileSpec.Invalid) {
- return
- }
- val tilesList = loadTiles(userId).toMutableList()
- if (tile !in tilesList) {
- if (position < 0 || position >= tilesList.size) {
- tilesList.add(tile)
- } else {
- tilesList.add(position, tile)
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
+ mutex.withLock {
+ if (tile == TileSpec.Invalid) {
+ return
}
- storeTiles(userId, tilesList)
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tile !in tilesList) {
+ if (position < 0 || position >= tilesList.size) {
+ tilesList.add(tile)
+ } else {
+ tilesList.add(position, tile)
+ }
+ storeTiles(userId, tilesList)
+ }
}
- }
- override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
- if (tiles.all { it == TileSpec.Invalid }) {
- return
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
+ mutex.withLock {
+ if (tiles.all { it == TileSpec.Invalid }) {
+ return
+ }
+ val tilesList = loadTiles(userId).toMutableList()
+ if (tilesList.removeAll(tiles)) {
+ storeTiles(userId, tilesList.toList())
+ }
}
- val tilesList = loadTiles(userId).toMutableList()
- if (tilesList.removeAll(tiles)) {
- storeTiles(userId, tilesList.toList())
- }
- }
- override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
- val filtered = tiles.filter { it != TileSpec.Invalid }
- if (filtered.isNotEmpty()) {
- storeTiles(userId, filtered)
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
+ mutex.withLock {
+ val filtered = tiles.filter { it != TileSpec.Invalid }
+ if (filtered.isNotEmpty()) {
+ storeTiles(userId, filtered)
+ }
}
- }
private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
return withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index c579f5c..ff881f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -36,6 +36,7 @@
import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -52,6 +53,8 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
@@ -117,11 +120,13 @@
* * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
* * [CustomTile]s will only be destroyed if the user changes.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CurrentTilesInteractorImpl
@Inject
constructor(
private val tileSpecRepository: TileSpecRepository,
+ private val installedTilesComponentRepository: InstalledTilesComponentRepository,
private val userRepository: UserRepository,
private val customTileStatePersister: CustomTileStatePersister,
private val tileFactory: QSFactory,
@@ -141,7 +146,7 @@
override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
// This variable should only be accessed inside the collect of `startTileCollection`.
- private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+ private val specsToTiles = mutableMapOf<TileSpec, TileOrNotInstalled>()
private val currentUser = MutableStateFlow(userTracker.userId)
override val userId = currentUser.asStateFlow()
@@ -149,6 +154,20 @@
private val _userContext = MutableStateFlow(userTracker.userContext)
override val userContext = _userContext.asStateFlow()
+ private val userAndTiles =
+ currentUser
+ .flatMapLatest { userId ->
+ tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) }
+ }
+ .distinctUntilChanged()
+ .pairwise(UserAndTiles(-1, emptyList()))
+ .flowOn(backgroundDispatcher)
+
+ private val installedPackagesWithTiles =
+ currentUser.flatMapLatest {
+ installedTilesComponentRepository.getInstalledTilesComponents(it)
+ }
+
init {
if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
startTileCollection()
@@ -158,68 +177,98 @@
@OptIn(ExperimentalCoroutinesApi::class)
private fun startTileCollection() {
scope.launch {
- userRepository.selectedUserInfo
- .flatMapLatest { user ->
+ launch {
+ userRepository.selectedUserInfo.collect { user ->
currentUser.value = user.id
_userContext.value = userTracker.userContext
- tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
}
- .distinctUntilChanged()
- .pairwise(-1 to emptyList())
- .flowOn(backgroundDispatcher)
- .collect { (old, new) ->
- val newTileList = new.second
- val userChanged = old.first != new.first
- val newUser = new.first
+ }
- // Destroy all tiles that are not in the new set
- specsToTiles
- .filter { it.key !in newTileList }
- .forEach { entry ->
- logger.logTileDestroyed(
- entry.key,
- if (userChanged) {
- QSPipelineLogger.TileDestroyedReason
- .TILE_NOT_PRESENT_IN_NEW_USER
- } else {
- QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
- }
- )
- entry.value.destroy()
- }
- // MutableMap will keep the insertion order
- val newTileMap = mutableMapOf<TileSpec, QSTile>()
+ launch(backgroundDispatcher) {
+ userAndTiles
+ .combine(installedPackagesWithTiles) { usersAndTiles, packages ->
+ Data(
+ usersAndTiles.previousValue,
+ usersAndTiles.newValue,
+ packages,
+ )
+ }
+ .collectLatest {
+ val newTileList = it.newData.tiles
+ val userChanged = it.oldData.userId != it.newData.userId
+ val newUser = it.newData.userId
+ val components = it.installedComponents
- newTileList.forEach { tileSpec ->
- if (tileSpec !in newTileMap) {
- val newTile =
- if (tileSpec in specsToTiles) {
- processExistingTile(
- tileSpec,
- specsToTiles.getValue(tileSpec),
- userChanged,
- newUser
- )
- ?: createTile(tileSpec)
+ // Destroy all tiles that are not in the new set
+ specsToTiles
+ .filter {
+ it.key !in newTileList && it.value is TileOrNotInstalled.Tile
+ }
+ .forEach { entry ->
+ logger.logTileDestroyed(
+ entry.key,
+ if (userChanged) {
+ QSPipelineLogger.TileDestroyedReason
+ .TILE_NOT_PRESENT_IN_NEW_USER
+ } else {
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ }
+ )
+ (entry.value as TileOrNotInstalled.Tile).tile.destroy()
+ }
+ // MutableMap will keep the insertion order
+ val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()
+
+ newTileList.forEach { tileSpec ->
+ if (tileSpec !in newTileMap) {
+ if (
+ tileSpec is TileSpec.CustomTileSpec &&
+ tileSpec.componentName !in components
+ ) {
+ newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
} else {
- createTile(tileSpec)
+ // Create tile here will never try to create a CustomTile that
+ // is not installed
+ val newTile =
+ if (tileSpec in specsToTiles) {
+ processExistingTile(
+ tileSpec,
+ specsToTiles.getValue(tileSpec),
+ userChanged,
+ newUser
+ )
+ ?: createTile(tileSpec)
+ } else {
+ createTile(tileSpec)
+ }
+ if (newTile != null) {
+ newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
+ }
}
- if (newTile != null) {
- newTileMap[tileSpec] = newTile
}
}
- }
- val resolvedSpecs = newTileMap.keys.toList()
- specsToTiles.clear()
- specsToTiles.putAll(newTileMap)
- _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
- if (resolvedSpecs != newTileList) {
- // There were some tiles that couldn't be created. Change the value in the
- // repository
- launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ val resolvedSpecs = newTileMap.keys.toList()
+ specsToTiles.clear()
+ specsToTiles.putAll(newTileMap)
+ _currentSpecsAndTiles.value =
+ newTileMap
+ .filter { it.value is TileOrNotInstalled.Tile }
+ .map {
+ TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
+ }
+ logger.logTilesNotInstalled(
+ newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
+ newUser
+ )
+ if (resolvedSpecs != newTileList) {
+ // There were some tiles that couldn't be created. Change the value in
+ // the
+ // repository
+ launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ }
}
- }
+ }
}
}
@@ -301,42 +350,66 @@
private fun processExistingTile(
tileSpec: TileSpec,
- qsTile: QSTile,
+ tileOrNotInstalled: TileOrNotInstalled,
userChanged: Boolean,
user: Int,
): QSTile? {
- return when {
- !qsTile.isAvailable -> {
- logger.logTileDestroyed(
- tileSpec,
- QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
- )
- qsTile.destroy()
- null
- }
- // Tile is in the current list of tiles and available.
- // We have a handful of different cases
- qsTile !is CustomTile -> {
- // The tile is not a custom tile. Make sure they are reset to the correct user
- if (userChanged) {
- qsTile.userSwitch(user)
- logger.logTileUserChanged(tileSpec, user)
+ return when (tileOrNotInstalled) {
+ is TileOrNotInstalled.NotInstalled -> null
+ is TileOrNotInstalled.Tile -> {
+ val qsTile = tileOrNotInstalled.tile
+ when {
+ !qsTile.isAvailable -> {
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+ qsTile.destroy()
+ null
+ }
+ // Tile is in the current list of tiles and available.
+ // We have a handful of different cases
+ qsTile !is CustomTile -> {
+ // The tile is not a custom tile. Make sure they are reset to the correct
+ // user
+ if (userChanged) {
+ qsTile.userSwitch(user)
+ logger.logTileUserChanged(tileSpec, user)
+ }
+ qsTile
+ }
+ qsTile.user == user -> {
+ // The tile is a custom tile for the same user, just return it
+ qsTile
+ }
+ else -> {
+ // The tile is a custom tile and the user has changed. Destroy it
+ qsTile.destroy()
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+ )
+ null
+ }
}
- qsTile
- }
- qsTile.user == user -> {
- // The tile is a custom tile for the same user, just return it
- qsTile
- }
- else -> {
- // The tile is a custom tile and the user has changed. Destroy it
- qsTile.destroy()
- logger.logTileDestroyed(
- tileSpec,
- QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
- )
- null
}
}
}
+
+ private sealed interface TileOrNotInstalled {
+ object NotInstalled : TileOrNotInstalled
+
+ @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled
+ }
+
+ private data class UserAndTiles(
+ val userId: Int,
+ val tiles: List<TileSpec>,
+ )
+
+ private data class Data(
+ val oldData: UserAndTiles,
+ val newData: UserAndTiles,
+ val installedComponents: Set<ComponentName>,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index ff7d206..8318ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -124,6 +124,18 @@
tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
}
+ fun logTilesNotInstalled(tiles: Collection<TileSpec>, user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tiles.toString()
+ int1 = user
+ },
+ { "Tiles kept for not installed packages for user $int1: $str1" }
+ )
+ }
+
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index f080d3d..3af75ce 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,6 +34,7 @@
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.doOnLayout
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
@@ -220,6 +221,7 @@
override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
override fun dispatchDemoCommand(command: String, args: Bundle) =
clock.dispatchDemoCommand(command, args)
+
override fun onDemoModeStarted() = clock.onDemoModeStarted()
override fun onDemoModeFinished() = clock.onDemoModeFinished()
}
@@ -259,6 +261,7 @@
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
lastInsets?.let { updateConstraintsForInsets(header, it) }
updateResources()
+ updateCarrierGroupPadding()
}
}
@@ -291,6 +294,7 @@
privacyIconsController.chipVisibilityListener = chipVisibilityListener
updateVisibility()
updateTransition()
+ updateCarrierGroupPadding()
header.setOnApplyWindowInsetsListener(insetListener)
@@ -298,8 +302,6 @@
val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
v.pivotX = newPivot
v.pivotY = v.height.toFloat() / 2
-
- mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
}
clock.setOnClickListener { launchClockActivity() }
@@ -359,6 +361,14 @@
.load(context, resources.getXml(R.xml.large_screen_shade_header))
}
+ private fun updateCarrierGroupPadding() {
+ clock.doOnLayout {
+ val maxClockWidth =
+ (clock.width * resources.getFloat(R.dimen.qqs_expand_clock_scale)).toInt()
+ mShadeCarrierGroup.setPaddingRelative(maxClockWidth, 0, 0, 0)
+ }
+ }
+
private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
val cutout = insets.displayCutout.also { this.cutout = it }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
new file mode 100644
index 0000000..891ee0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.annotation.WorkerThread
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import com.android.systemui.util.Assert
+
+@WorkerThread
+fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
+ Assert.isNotMainThread()
+ return when (getComponentEnabledSetting(componentInfo.componentName)) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> componentInfo.isEnabled
+ else -> false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b24a692..b848d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2006,14 +2006,14 @@
if (row.anim == null) {
row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
row.anim.setInterpolator(new DecelerateInterpolator());
+ row.anim.addListener(
+ getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
- row.anim.addListener(
- getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
row.anim.start();
} else {
// update slider directly to clamped value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
index 7fb088e..bd4e8da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -27,16 +28,20 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ControlsTileResourceConfigurationImplTest : SysuiTestCase() {
+
+ @Test
+ fun getPackageName() {
+ assertThat(ControlsTileResourceConfigurationImpl().getPackageName()).isNull()
+ }
+
@Test
fun getTileImageId() {
val instance = ControlsTileResourceConfigurationImpl()
- assertEquals(instance.getTileImageId(),
- R.drawable.controls_icon)
+ assertEquals(instance.getTileImageId(), R.drawable.controls_icon)
}
@Test
fun getTileTitleId() {
val instance = ControlsTileResourceConfigurationImpl()
- assertEquals(instance.getTileTitleId(),
- R.string.quick_controls_title)
+ assertEquals(instance.getTileTitleId(), R.string.quick_controls_title)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 9144b13..0b27bc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -22,10 +22,10 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,32 +40,25 @@
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlsComponentTest : SysuiTestCase() {
- @Mock
- private lateinit var controller: ControlsController
- @Mock
- private lateinit var uiController: ControlsUiController
- @Mock
- private lateinit var listingController: ControlsListingController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var controller: ControlsController
+ @Mock private lateinit var uiController: ControlsUiController
+ @Mock private lateinit var listingController: ControlsListingController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var userTracker: UserTracker
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock
private lateinit var optionalControlsTileResourceConfiguration:
- Optional<ControlsTileResourceConfiguration>
- @Mock
- private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
+ Optional<ControlsTileResourceConfiguration>
+ @Mock private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
@@ -123,8 +116,7 @@
@Test
fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() {
- `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
- .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
controlsSettingsRepository.setCanShowControlsInLockscreen(false)
val component = setupComponent(true)
@@ -134,8 +126,7 @@
@Test
fun testFeatureEnabledAndCanShowOnLockScreenVisibility() {
- `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
- .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
controlsSettingsRepository.setCanShowControlsInLockscreen(true)
val component = setupComponent(true)
@@ -146,8 +137,7 @@
@Test
fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
controlsSettingsRepository.setCanShowControlsInLockscreen(false)
- `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
- .thenReturn(STRONG_AUTH_NOT_REQUIRED)
+ `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(true)
val component = setupComponent(true)
@@ -158,8 +148,7 @@
fun testGetTileImageId() {
val tileImageId = 0
- `when`(controlsTileResourceConfiguration.getTileImageId())
- .thenReturn(tileImageId)
+ `when`(controlsTileResourceConfiguration.getTileImageId()).thenReturn(tileImageId)
val component = setupComponent(true)
assertEquals(component.getTileImageId(), tileImageId)
}
@@ -168,12 +157,19 @@
fun testGetTileTitleId() {
val tileTitleId = 0
- `when`(controlsTileResourceConfiguration.getTileTitleId())
- .thenReturn(tileTitleId)
+ `when`(controlsTileResourceConfiguration.getTileTitleId()).thenReturn(tileTitleId)
val component = setupComponent(true)
assertEquals(component.getTileTitleId(), tileTitleId)
}
+ @Test
+ fun getPackageName() {
+ val packageName = "packageName"
+ `when`(controlsTileResourceConfiguration.getPackageName()).thenReturn(packageName)
+ val component = setupComponent(true)
+ assertEquals(component.getPackageName(), packageName)
+ }
+
private fun setupComponent(enabled: Boolean): ControlsComponent {
return ControlsComponent(
enabled,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index f8cb408..1815ea9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.app.Activity
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -24,6 +25,7 @@
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -75,6 +77,7 @@
@Mock private lateinit var component: ControlsComponent
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
+ @Mock private lateinit var controlsUiController: ControlsUiController
@Mock private lateinit var controlsServiceInfo: ControlsServiceInfo
@Captor
private lateinit var callbackCaptor:
@@ -98,6 +101,8 @@
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ whenever(controlsUiController.resolveActivity()).thenReturn(FakeActivity::class.java)
+ whenever(component.getControlsUiController()).thenReturn(Optional.of(controlsUiController))
if (hasPanels) {
whenever(controlsServiceInfo.panelActivity).thenReturn(mock())
}
@@ -178,4 +183,6 @@
)
job.cancel()
}
+
+ private class FakeActivity : Activity()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..7941a23
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Test
+ fun appStoreIntent() {
+ overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+ overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName")
+ val packageName = "com.app.package.name"
+
+ val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+
+ assertThat(intent).isNotNull()
+ assertThat(intent?.`package`).isEqualTo("app.store.package.name")
+ assertThat(intent?.action).isEqualTo(Intent.ACTION_VIEW)
+ assertThat(intent?.data).isEqualTo(Uri.parse("prefix?id=$packageName"))
+ }
+
+ @Test
+ fun appStoreIntent_packageNameNotConfigured_returnNull() {
+ overrideResource(R.string.config_appStorePackageName, "")
+ overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName")
+ val packageName = "com.app.package.name"
+
+ val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+
+ assertThat(intent).isNull()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun appStoreIntent_packageNameMisconfigured_throwsIllegalStateException() {
+ overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+ overrideResource(
+ R.string.config_appStoreAppLinkTemplate,
+ "prefix?id=\$misconfiguredPackageName"
+ )
+ val packageName = "com.app.package.name"
+
+ KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+ }
+
+ @Test
+ fun appStoreIntent_linkTemplateNotConfigured_returnNull() {
+ overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+ overrideResource(R.string.config_appStoreAppLinkTemplate, "")
+ val packageName = "com.app.package.name"
+
+ val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName)
+
+ assertThat(intent).isNull()
+ }
+
+ @Test
+ fun appStoreIntent_appPackageNameNull_returnNull() {
+ overrideResource(R.string.config_appStorePackageName, "app.store.package.name")
+ overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName")
+
+ val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, null)
+
+ assertThat(intent).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 111b8e8..d36e778 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -92,8 +92,8 @@
}
@Test
- fun affordance_walletNotEnabled_modelIsNone() = runBlockingTest {
- setUpState(isWalletEnabled = false)
+ fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
+ setUpState(isWalletFeatureAvailable = false)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +165,7 @@
@Test
fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
setUpState(
- isWalletEnabled = false,
+ isWalletFeatureAvailable = false,
)
assertThat(underTest.getPickerScreenState())
@@ -183,16 +183,15 @@
}
private fun setUpState(
- isWalletEnabled: Boolean = true,
+ isWalletFeatureAvailable: Boolean = true,
isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
hasSelectedCard: Boolean = true,
) {
- whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
-
val walletClient: QuickAccessWalletClient = mock()
whenever(walletClient.tileIcon).thenReturn(ICON)
whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+ whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable)
whenever(walletController.walletClient).thenReturn(walletClient)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 205fa0f..9dba9b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -35,6 +35,8 @@
import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
@@ -58,6 +60,8 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.google.common.base.Strings;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -252,4 +256,106 @@
mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
}
+
+ @Test
+ public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String testString = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2);
+ editText.setText(testString);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+
+ // input the valid text
+ testString = Strings.repeat("b",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100);
+ editText.setText(testString);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index 4526580..cc64cc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -40,7 +40,6 @@
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.notetask.NoteTaskInfoResolver
-import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
import com.android.systemui.stylus.StylusManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -283,7 +282,7 @@
}
@Test
- fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest {
+ fun getPickerScreenState_noDefaultNoteAppSet_shouldReturnDisabled() = runTest {
val underTest = createUnderTest(isEnabled = true)
whenever(
roleManager.getRoleHoldersAsUser(
@@ -293,16 +292,16 @@
)
.thenReturn(emptyList())
- assertThat(underTest.getPickerScreenState())
- .isEqualTo(
- KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- listOf("Select a default notes app to use the notetaking shortcut"),
- actionText = "Select app",
- actionComponentName =
- "${context.packageName}$COMPONENT_NAME_SEPARATOR" +
- "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
- )
- )
+ val pickerScreenState = underTest.getPickerScreenState()
+ assertThat(pickerScreenState is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled)
+ .isTrue()
+ val disabled = pickerScreenState as KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
+ assertThat(disabled.instructions)
+ .isEqualTo(listOf("Select a default notes app to use the notetaking shortcut"))
+ assertThat(disabled.actionText).isEqualTo("Select app")
+ assertThat(disabled.actionIntent?.action)
+ .isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)
+ assertThat(disabled.actionIntent?.`package`).isEqualTo(context.packageName)
}
// endregion
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
new file mode 100644
index 0000000..18f3837
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.quicksettings.TileService
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+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.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var packageManager: PackageManager
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+
+ private lateinit var underTest: InstalledTilesComponentRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // Use the default value set in the ServiceInfo
+ whenever(packageManager.getComponentEnabledSetting(any()))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+
+ // Return empty by default
+ whenever(packageManager.queryIntentServicesAsUser(any(), any<ResolveInfoFlags>(), anyInt()))
+ .thenReturn(emptyList())
+
+ underTest =
+ InstalledTilesComponentRepositoryImpl(
+ context,
+ packageManager,
+ testDispatcher,
+ )
+ }
+
+ @Test
+ fun registersAndUnregistersBroadcastReceiver() =
+ testScope.runTest {
+ val user = 10
+ val job = launch { underTest.getInstalledTilesComponents(user).collect {} }
+ runCurrent()
+
+ verify(context)
+ .registerReceiverAsUser(
+ capture(receiverCaptor),
+ eq(UserHandle.of(user)),
+ any(),
+ nullable(),
+ nullable(),
+ )
+
+ verify(context, never()).unregisterReceiver(receiverCaptor.value)
+
+ job.cancel()
+ runCurrent()
+ verify(context).unregisterReceiver(receiverCaptor.value)
+ }
+
+ @Test
+ fun intentFilterForCorrectActionsAndScheme() =
+ testScope.runTest {
+ val filterCaptor = argumentCaptor<IntentFilter>()
+
+ backgroundScope.launch { underTest.getInstalledTilesComponents(0).collect {} }
+ runCurrent()
+
+ verify(context)
+ .registerReceiverAsUser(
+ any(),
+ any(),
+ capture(filterCaptor),
+ nullable(),
+ nullable(),
+ )
+
+ with(filterCaptor.value) {
+ assertThat(matchAction(Intent.ACTION_PACKAGE_CHANGED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_ADDED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue()
+ assertThat(matchAction(Intent.ACTION_PACKAGE_REPLACED)).isTrue()
+ assertThat(countActions()).isEqualTo(4)
+
+ assertThat(hasDataScheme("package")).isTrue()
+ assertThat(countDataSchemes()).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun componentsLoadedOnStart() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+ assertThat(componentNames).containsExactly(TEST_COMPONENT)
+ }
+
+ @Test
+ fun componentAdded_foundAfterBroadcast() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+ getRegisteredReceiver().onReceive(context, Intent(Intent.ACTION_PACKAGE_ADDED))
+
+ assertThat(componentNames).containsExactly(TEST_COMPONENT)
+ }
+
+ @Test
+ fun componentWithoutPermission_notValid() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = false, defaultEnabled = true)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+ }
+
+ @Test
+ fun componentNotEnabled_notValid() =
+ testScope.runTest {
+ val userId = 0
+ val resolveInfo =
+ ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = false)
+ whenever(
+ packageManager.queryIntentServicesAsUser(
+ matchIntent(),
+ matchFlags(),
+ eq(userId)
+ )
+ )
+ .thenReturn(listOf(resolveInfo))
+
+ val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ assertThat(componentNames).isEmpty()
+ }
+
+ private fun getRegisteredReceiver(): BroadcastReceiver {
+ verify(context)
+ .registerReceiverAsUser(
+ capture(receiverCaptor),
+ any(),
+ any(),
+ nullable(),
+ nullable(),
+ )
+
+ return receiverCaptor.value
+ }
+
+ companion object {
+ private val INTENT = Intent(TileService.ACTION_QS_TILE)
+ private val FLAGS =
+ ResolveInfoFlags.of(
+ (PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.GET_SERVICES)
+ .toLong()
+ )
+ private val PERMISSION = BIND_QUICK_SETTINGS_TILE
+
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+
+ private fun matchFlags() =
+ argThat<ResolveInfoFlags> { flags -> flags?.value == FLAGS.value }
+ private fun matchIntent() = argThat<Intent> { intent -> intent.action == INTENT.action }
+
+ private fun ResolveInfo(
+ componentName: ComponentName,
+ hasPermission: Boolean,
+ defaultEnabled: Boolean
+ ): ResolveInfo {
+ val applicationInfo = ApplicationInfo().apply { enabled = true }
+ val serviceInfo =
+ ServiceInfo().apply {
+ packageName = componentName.packageName
+ name = componentName.className
+ if (hasPermission) {
+ permission = PERMISSION
+ }
+ enabled = defaultEnabled
+ this.applicationInfo = applicationInfo
+ }
+ val resolveInfo = ResolveInfo()
+ resolveInfo.serviceInfo = serviceInfo
+ return resolveInfo
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 426ff67..e7ad489 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
@@ -73,6 +74,7 @@
private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
private val userRepository = FakeUserRepository()
+ private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
private val tileFactory = FakeQSFactory(::tileCreator)
private val customTileAddedRepository: CustomTileAddedRepository =
FakeCustomTileAddedRepository()
@@ -100,11 +102,13 @@
featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+
setUserTracker(0)
underTest =
CurrentTilesInteractorImpl(
tileSpecRepository = tileSpecRepository,
+ installedTilesComponentRepository = installedTilesPackageRepository,
userRepository = userRepository,
customTileStatePersister = customTileStatePersister,
tileFactory = tileFactory,
@@ -609,6 +613,40 @@
assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
}
+ @Test
+ fun packageNotInstalled_customTileNotVisible() =
+ testScope.runTest(USER_INFO_0) {
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles!!.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(specs[0])
+ }
+
+ @Test
+ fun packageInstalledLater_customTileAdded() =
+ testScope.runTest(USER_INFO_0) {
+ installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
+
+ val tiles by collectLastValue(underTest.currentTiles)
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles!!.size).isEqualTo(2)
+
+ installedTilesPackageRepository.setInstalledPackagesForUser(
+ USER_INFO_0.id,
+ setOf(TEST_COMPONENT)
+ )
+
+ assertThat(tiles!!.size).isEqualTo(3)
+ assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
+ }
+
private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
this.state = state
this.label = label
@@ -654,6 +692,7 @@
private suspend fun switchUser(user: UserInfo) {
setUserTracker(user.id)
+ installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
userRepository.setSelectedUserInfo(user)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 20da8a6..2da2e92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -78,6 +78,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -387,7 +388,7 @@
whenever(clock.isLayoutRtl).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
verify(clock).pivotX = 0f
@@ -400,7 +401,7 @@
whenever(clock.isLayoutRtl).thenReturn(true)
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
verify(clock).pivotX = width.toFloat()
@@ -793,7 +794,7 @@
@Test
fun clockPivotYInCenter() {
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
- verify(clock).addOnLayoutChangeListener(capture(captor))
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
var height = 100
val width = 50
@@ -825,16 +826,17 @@
}
@Test
- fun carrierLeftPaddingIsSetWhenClockLayoutChanges() {
- val width = 200
- whenever(clock.width).thenReturn(width)
- whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml
+ fun carrierStartPaddingIsSetOnClockLayout() {
+ val clockWidth = 200
+ val maxClockScale = context.resources.getFloat(R.dimen.qqs_expand_clock_scale)
+ val expectedStartPadding = (clockWidth * maxClockScale).toInt()
+ whenever(clock.width).thenReturn(clockWidth)
+
val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock, times(2)).addOnLayoutChangeListener(capture(captor))
+ captor.allValues.forEach { clock.executeLayoutChange(0, 0, clockWidth, 0, it) }
- verify(clock).addOnLayoutChangeListener(capture(captor))
- captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0)
-
- verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
+ verify(carrierGroup).setPaddingRelative(expectedStartPadding, 0, 0, 0)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
new file mode 100644
index 0000000..2013bb0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.content.ComponentName
+import android.content.pm.ComponentInfo
+import android.content.pm.PackageManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+
+ @Mock private lateinit var packageManager: PackageManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testComponentActuallyEnabled() {
+ whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+ .thenReturn(testCase.componentEnabledSetting)
+ val componentInfo =
+ mock<ComponentInfo>() {
+ whenever(isEnabled).thenReturn(testCase.componentIsEnabled)
+ whenever(componentName).thenReturn(TEST_COMPONENT)
+ }
+
+ assertThat(packageManager.isComponentActuallyEnabled(componentInfo))
+ .isEqualTo(testCase.expected)
+ }
+
+ internal data class TestCase(
+ @PackageManager.EnabledState val componentEnabledSetting: Int,
+ val componentIsEnabled: Boolean,
+ val expected: Boolean,
+ ) {
+ override fun toString(): String {
+ return "WHEN(" +
+ "componentIsEnabled = $componentIsEnabled, " +
+ "componentEnabledSetting = ${enabledStateToString()}) then " +
+ "EXPECTED = $expected"
+ }
+
+ private fun enabledStateToString() =
+ when (componentEnabledSetting) {
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> "STATE_DEFAULT"
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> "STATE_DISABLED"
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED -> {
+ "STATE_DISABLED_UNTIL_USED"
+ }
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> "STATE_DISABLED_USER"
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> "STATE_ENABLED"
+ else -> "INVALID STATE"
+ }
+ }
+
+ companion object {
+ @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData
+
+ private val testDataComponentIsEnabled =
+ listOf(
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ componentIsEnabled = true,
+ expected = true,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting =
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ componentIsEnabled = true,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ componentIsEnabled = true,
+ expected = true,
+ ),
+ )
+
+ private val testDataComponentIsDisabled =
+ listOf(
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ componentIsEnabled = false,
+ expected = true,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting =
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ TestCase(
+ componentEnabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ componentIsEnabled = false,
+ expected = false,
+ ),
+ )
+
+ private val testData = testDataComponentIsDisabled + testDataComponentIsEnabled
+
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
new file mode 100644
index 0000000..ff6b7d0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.ComponentName
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository {
+
+ private val installedComponentsPerUser =
+ mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>()
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+ return getFlow(userId).asStateFlow()
+ }
+
+ fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) {
+ getFlow(userId).value = components
+ }
+
+ private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> =
+ installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ede461..6f20563 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2156,9 +2156,7 @@
}
}
- final boolean fgsTypeChangingFromShortFgs = r.isForeground && isOldTypeShortFgs;
-
- if (fgsTypeChangingFromShortFgs) {
+ if (r.isForeground && isOldTypeShortFgs) {
// If we get here, that means startForeground(SHORT_SERVICE) is called again
// on a SHORT_SERVICE FGS.
@@ -2211,9 +2209,7 @@
// "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
}
}
- }
-
- if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount == 0) {
+ } else if (r.mStartForegroundCount == 0) {
/*
If the service was started with startService(), not
startForegroundService(), and if startForeground() isn't called within
@@ -2244,7 +2240,7 @@
r.mLoggedInfoAllowStartForeground = false;
}
}
- } else if (!fgsTypeChangingFromShortFgs && r.mStartForegroundCount >= 1) {
+ } else if (r.mStartForegroundCount >= 1) {
// We get here if startForeground() is called multiple times
// on the same service after it's created, regardless of whether
// stopForeground() has been called or not.
@@ -7436,14 +7432,10 @@
r.mAllowWhileInUsePermissionInFgs = true;
}
- // Either (or both) mAllowWhileInUsePermissionInFgs or mAllowStartForeground is
- // newly allowed?
- boolean newlyAllowed = false;
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
- callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
- isBindService);
+ callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -7477,7 +7469,7 @@
}
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, null /* targetProcess */,
- BackgroundStartPrivileges.NONE, false);
+ BackgroundStartPrivileges.NONE);
@ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
BackgroundStartPrivileges.NONE);
@@ -7501,19 +7493,15 @@
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+ BackgroundStartPrivileges backgroundStartPrivileges) {
int ret = REASON_DENIED;
- final boolean forStartForeground = !isBindService;
-
- if (forStartForeground) {
- final int uidState = mAm.getUidStateLocked(callingUid);
- if (ret == REASON_DENIED) {
- // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
- // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
- if (uidState <= PROCESS_STATE_TOP) {
- ret = getReasonCodeFromProcState(uidState);
- }
+ final int uidState = mAm.getUidStateLocked(callingUid);
+ if (ret == REASON_DENIED) {
+ // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
+ // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
+ if (uidState <= PROCESS_STATE_TOP) {
+ ret = getReasonCodeFromProcState(uidState);
}
}
@@ -7734,7 +7722,7 @@
shouldAllowFgsWhileInUsePermissionLocked(
clientPackageName,
clientPid, clientUid, null /* targetProcess */,
- BackgroundStartPrivileges.NONE, false);
+ BackgroundStartPrivileges.NONE);
final @ReasonCode int allowStartFgs =
shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse2,
@@ -8163,7 +8151,7 @@
String callingPackage) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
/* targetProcess */ null,
- BackgroundStartPrivileges.NONE, false)
+ BackgroundStartPrivileges.NONE)
!= REASON_DENIED;
}
@@ -8171,7 +8159,7 @@
String callingPackage, @Nullable ProcessRecord targetProcess,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
- targetProcess, backgroundStartPrivileges, false) != REASON_DENIED;
+ targetProcess, backgroundStartPrivileges) != REASON_DENIED;
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0b5b1cb..4b6d324 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -25,6 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.SystemClock;
@@ -257,7 +258,10 @@
deferredStatesApplyConsumer.accept(record, recordIndex);
}
- if (record.isReplacePending()) {
+ // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
+ // BroadcastOptions delivery group APIs.
+ if (record.isReplacePending()
+ && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
return replacedBroadcastRecord;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index f6004d7..8688f25 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -269,7 +269,7 @@
Activity.RESULT_CANCELED, null, null,
false, false, oldRecord.shareIdentity, oldRecord.userId,
oldRecord.callingUid, r.callingUid, r.callerPackage,
- SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
+ SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failure ["
+ mQueueName + "] sending broadcast result of "
@@ -339,7 +339,7 @@
private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
BroadcastRecord r, String typeForLogging) {
final Intent intent = r.intent;
- for (int i = queue.size() - 1; i > 0; i--) {
+ for (int i = queue.size() - 1; i >= 0; i--) {
final BroadcastRecord old = queue.get(i);
if (old.userId == r.userId && intent.filterEquals(old.intent)) {
if (DEBUG_BROADCAST) {
@@ -617,7 +617,10 @@
r.curApp.info.packageName,
r.callerPackage,
r.calculateTypeForLogging(),
- r.getDeliveryGroupPolicy());
+ r.getDeliveryGroupPolicy(),
+ r.intent.getFlags(),
+ BroadcastRecord.getReceiverPriority(curReceiver),
+ r.callerProcState);
}
if (state == BroadcastRecord.IDLE) {
Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -748,7 +751,7 @@
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
int receiverUid, int callingUid, String callingPackage,
- long dispatchDelay, long receiveDelay) throws RemoteException {
+ long dispatchDelay, long receiveDelay, int priority) throws RemoteException {
// If the broadcaster opted-in to sharing their identity, then expose package visibility for
// the receiver.
if (shareIdentity) {
@@ -798,7 +801,8 @@
dispatchDelay, receiveDelay, 0 /* finish_delay */,
SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
app != null ? app.info.packageName : null, callingPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
+ r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
+ priority, r.callerProcState);
}
}
@@ -879,7 +883,7 @@
r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
filter.receiverList.uid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime);
+ r.receiverTime - r.dispatchTime, filter.getPriority());
// parallel broadcasts are fire-and-forget, not bookended by a call to
// finishReceiverLocked(), so we manage their activity-start token here
if (filter.receiverList.app != null
@@ -1170,7 +1174,7 @@
r.resultData, r.resultExtras, false, false, r.shareIdentity,
r.userId, r.callingUid, r.callingUid, r.callerPackage,
r.dispatchTime - r.enqueueTime,
- now - r.dispatchTime);
+ now - r.dispatchTime, 0);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
// Set this to null so that the reference
// (local and remote) isn't kept in the mBroadcastHistory.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index cbc7540..8380308 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -719,11 +719,12 @@
private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) {
for (int i = 0; i < replacedBroadcasts.size(); ++i) {
final BroadcastRecord r = replacedBroadcasts.valueAt(i);
- r.resultCode = Activity.RESULT_CANCELED;
- r.resultData = null;
- r.resultExtras = null;
- scheduleResultTo(r);
- notifyFinishBroadcast(r);
+ // Skip all the receivers in the replaced broadcast
+ for (int rcvrIdx = 0; rcvrIdx < r.receivers.size(); ++rcvrIdx) {
+ if (!isDeliveryStateTerminal(r.getDeliveryState(rcvrIdx))) {
+ mBroadcastConsumerSkipAndCanceled.accept(r, rcvrIdx);
+ }
+ }
}
}
@@ -1932,7 +1933,8 @@
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
app != null ? app.info.packageName : null, r.callerPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
+ r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
+ BroadcastRecord.getReceiverPriority(receiver), r.callerProcState);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index cfdb133..67d43fd 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,6 +44,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.app.ActivityManager;
+import android.app.ActivityManager.ProcessState;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
@@ -93,6 +95,7 @@
final @Nullable String callerFeatureId; // which feature in the package sent this
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
+ final @ProcessState int callerProcState; // Procstate of the caller process at enqueue time.
final int originalStickyCallingUid;
// if this is a sticky broadcast, the Uid of the original sender
@@ -462,6 +465,8 @@
callerFeatureId = _callerFeatureId;
callingPid = _callingPid;
callingUid = _callingUid;
+ callerProcState = callerApp == null ? ActivityManager.PROCESS_STATE_UNKNOWN
+ : callerApp.getCurProcState();
callerInstantApp = _callerInstantApp;
callerInstrumented = isCallerInstrumented(_callerApp, _callingUid);
resolvedType = _resolvedType;
@@ -515,6 +520,7 @@
callerFeatureId = from.callerFeatureId;
callingPid = from.callingPid;
callingUid = from.callingUid;
+ callerProcState = from.callerProcState;
callerInstantApp = from.callerInstantApp;
callerInstrumented = from.callerInstrumented;
ordered = from.ordered;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index d6495c7..267d246 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -669,6 +669,11 @@
return mOnewayThread;
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getCurProcState() {
+ return mState.getCurProcState();
+ }
+
@GuardedBy({"mService", "mProcLock"})
public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d0b6cdc..336f0fb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3277,6 +3277,7 @@
if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
streamType = mVolumeControlStream;
} else {
+ // TODO discard activity on a muted stream?
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
if (maybeActiveStreamType == AudioSystem.STREAM_RING
@@ -3480,9 +3481,10 @@
}
} else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) {
// if the stream is currently muted streams by ringer/zen mode
- // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES)
+ // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES) with an unmute or raise
if (direction == AudioManager.ADJUST_TOGGLE_MUTE
- || direction == AudioManager.ADJUST_UNMUTE) {
+ || direction == AudioManager.ADJUST_UNMUTE
+ || direction == AudioManager.ADJUST_RAISE) {
adjustVolume = false;
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index aa5f9fa..7fa4d6c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -573,7 +573,7 @@
final BiometricSchedulerOperation operation = mCurrentOperation;
mHandler.postDelayed(() -> {
if (operation == mCurrentOperation) {
- Counter.logIncrement("biometric.scheduler_watchdog_triggered_count");
+ Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count");
clearScheduler();
}
}, 10000);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index aeff2b0..b39e860 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -119,6 +119,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -223,6 +224,8 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -234,6 +237,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.VibrationEffect;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -559,6 +563,7 @@
private PermissionHelper mPermissionHelper;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private TelecomManager mTelecomManager;
+ private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
final IBinder mForegroundToken = new Binder();
@@ -923,7 +928,7 @@
if (oldFlags != flags) {
summary.getSbn().getNotification().flags = flags;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -1457,7 +1462,7 @@
// want to adjust the flag behaviour.
mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
r, true /* isAppForeground*/,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -1488,7 +1493,7 @@
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
/* foreground= */ true,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -2233,7 +2238,7 @@
UsageStatsManagerInternal usageStatsManagerInternal,
TelecomManager telecomManager, NotificationChannelLogger channelLogger,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
- PermissionManager permissionManager,
+ PermissionManager permissionManager, PowerManager powerManager,
PostNotificationTrackerFactory postNotificationTrackerFactory) {
mHandler = handler;
Resources resources = getContext().getResources();
@@ -2265,6 +2270,7 @@
mDpm = dpm;
mUm = userManager;
mTelecomManager = telecomManager;
+ mPowerManager = powerManager;
mPostNotificationTrackerFactory = postNotificationTrackerFactory;
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2568,6 +2574,7 @@
getContext().getSystemService(TelecomManager.class),
new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
getContext().getSystemService(PermissionManager.class),
+ getContext().getSystemService(PowerManager.class),
new PostNotificationTrackerFactory() {});
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -2684,7 +2691,7 @@
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -6577,7 +6584,7 @@
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int incomingUserId, boolean postSilently) {
- PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker();
+ PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
boolean enqueued = false;
try {
enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
@@ -6589,6 +6596,22 @@
}
}
+ private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
+ if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) {
+ // The package probably doesn't have WAKE_LOCK permission and should not require it.
+ return Binder.withCleanCallingIdentity(() -> {
+ WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "NotificationManagerService:post:" + pkg);
+ wakeLock.setWorkSource(new WorkSource(uid, pkg));
+ // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
+ wakeLock.acquire(30_000);
+ return mPostNotificationTrackerFactory.newTracker(wakeLock);
+ });
+ } else {
+ return mPostNotificationTrackerFactory.newTracker(null);
+ }
+ }
+
/**
* @return True if we successfully processed the notification and handed off the task of
* enqueueing it to a background thread; false otherwise.
@@ -7106,7 +7129,7 @@
mHandler.post(
new NotificationManagerService.EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -12168,20 +12191,20 @@
}
interface PostNotificationTrackerFactory {
- default PostNotificationTracker newTracker() {
- return new PostNotificationTracker();
+ default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) {
+ return new PostNotificationTracker(optionalWakelock);
}
}
static class PostNotificationTracker {
@ElapsedRealtimeLong private final long mStartTime;
- @Nullable private NotificationRecordLogger.NotificationReported mReport;
+ @Nullable private final WakeLock mWakeLock;
private boolean mOngoing;
@VisibleForTesting
- PostNotificationTracker() {
- // TODO(b/275044361): (Conditionally) receive a wakelock.
+ PostNotificationTracker(@Nullable WakeLock wakeLock) {
mStartTime = SystemClock.elapsedRealtime();
+ mWakeLock = wakeLock;
mOngoing = true;
if (DBG) {
Slog.d(TAG, "PostNotification: Started");
@@ -12199,9 +12222,8 @@
}
/**
- * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either
- * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before
- * it's discarded.
+ * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or
+ * {@link #cancel} (exclusively) should be called on this object before it's discarded.
*/
void cancel() {
if (!isOngoing()) {
@@ -12209,9 +12231,9 @@
return;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
long elapsedTime = SystemClock.elapsedRealtime() - mStartTime;
Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms",
@@ -12220,9 +12242,9 @@
}
/**
- * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the
- * time elapsed since the operation started, in milliseconds. Either {@link #finish} or
- * {@link #cancel} (exclusively) should be called on this object before it's discarded.
+ * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since
+ * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel}
+ * (exclusively) should be called on this object before it's discarded.
*/
@DurationMillisLong
long finish() {
@@ -12232,9 +12254,9 @@
return elapsedTime;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
Slog.d(TAG,
TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 06db5be..50f1673 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2145,7 +2145,7 @@
final String pkgName = pkg.getPackageName();
final int[] installedForUsers = installRequest.getOriginUsers();
final int installReason = installRequest.getInstallReason();
- final String installerPackageName = installRequest.getSourceInstallerPackageName();
+ final String installerPackageName = installRequest.getInstallerPackageName();
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 95e7904..3464874 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -366,12 +366,6 @@
public String getApexModuleName() {
return mApexModuleName;
}
-
- @Nullable
- public String getSourceInstallerPackageName() {
- return mInstallArgs.mInstallSource.mInstallerPackageName;
- }
-
public boolean isRollback() {
return mInstallArgs != null
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6491fd1..a911537 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -511,7 +511,7 @@
}
} catch (FileNotFoundException e) {
// Missing sessions are okay, probably first boot
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
Slog.wtf(TAG, "Failed reading install sessions", e);
} finally {
IoUtils.closeQuietly(fis);
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 19aa4f8..54ca426 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -230,7 +230,9 @@
+ Log.getStackTraceString(e));
}
- mCurrentFile.delete();
+ if (!mCurrentFile.delete()) {
+ throw new IllegalStateException("Failed to remove " + mCurrentFile);
+ }
mCurrentFile = null;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d108e14..d55f85c 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -986,11 +986,14 @@
}
/** @see ApplicationInfo#privateFlagsExt */
- public static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
+ private static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
@Nullable PackageStateInternal pkgSetting) {
// @formatter:off
- // TODO: Add state specific flags
- return pkgWithoutStateFlags;
+ int flags = pkgWithoutStateFlags;
+ if (pkgSetting != null) {
+ flags |= flag(pkgSetting.getCpuAbiOverride() != null, ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE);
+ }
+ return flags;
// @formatter:on
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b2100e..9f16a844 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4281,6 +4281,7 @@
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
+ mLetterboxUiController.destroy();
waitingToShow = false;
// Defer removal of this activity when either a child is animating, or app transition is on
@@ -4350,8 +4351,6 @@
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
}
- mLetterboxUiController.destroy();
-
if (!delayed) {
updateReportedVisibilityLocked();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0ebec11..a0a45e6 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1573,8 +1573,10 @@
// existence change.
transitionController.collectExistenceChange(started);
} else if (result == START_DELIVERED_TO_TOP && newTransition != null
- // An activity has changed order/visibility so this isn't just deliver-to-top
- && mMovedToTopActivity == null) {
+ // An activity has changed order/visibility or the task is occluded by a transient
+ // activity, so this isn't just deliver-to-top
+ && mMovedToTopActivity == null
+ && !transitionController.isTransientHide(startedActivityRootTask)) {
// We just delivered to top, so there isn't an actual transition here.
if (!forceTransientTransition) {
newTransition.abort();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a650d77..d332dfd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2012,7 +2012,7 @@
synchronized (mGlobalLock) {
final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
if (dc == null) return;
- final Task task = dc.getTask((t) -> t.isLeafTask() && t.isFocusable(),
+ final Task task = dc.getTask((t) -> t.isLeafTask() && t.isTopActivityFocusable(),
true /* traverseTopToBottom */);
if (task == null) return;
setFocusedTask(task.mTaskId, null /* touchedActivity */);
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 123a74d..f7ccc0d 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -178,6 +179,14 @@
* @param r activity record for which the warning may be displayed
*/
public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
+ final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
+ & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
+ if (isUsingAbiOverride) {
+ // The abiOverride flag was specified during installation, which means that if the app
+ // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
+ return;
+ }
+ // The warning dialog can also be disabled for debugging purpose
final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
"debug.wm.disable_deprecated_abi_dialog", false);
if (disableDeprecatedAbiDialog) {
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 31b1069..db4762e 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -111,9 +111,13 @@
}
/**
- * @return true if the rotation direction on the Z axis should be reversed.
+ * @return true if the rotation direction on the Z axis should be reversed for the default
+ * display.
*/
- boolean shouldReverseRotationDirectionAroundZAxis() {
+ boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) {
+ if (!displayContent.isDefaultDisplay) {
+ return false;
+ }
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8be36f0..b681c19 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -950,7 +950,7 @@
}
void freezeRotation(int rotation) {
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
}
@@ -1225,7 +1225,7 @@
if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
sensorRotation = -1;
}
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 1fbf593..2b34bb2 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -42,6 +42,7 @@
import android.app.servertransaction.RefreshCallbackItem;
import android.app.servertransaction.ResumeActivityItem;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
@@ -423,7 +424,18 @@
// for the activity embedding case.
if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
&& isTreatmentEnabledForActivity(topActivity, /* mustBeFullscreen */ false)) {
- showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
+ final PackageManager packageManager = mWmService.mContext.getPackageManager();
+ try {
+ showToast(
+ R.string.display_rotation_camera_compat_toast_in_multi_window,
+ (String) packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(packageName, /* flags */ 0)));
+ } catch (PackageManager.NameNotFoundException e) {
+ ProtoLog.e(WM_DEBUG_ORIENTATION,
+ "DisplayRotationCompatPolicy: Multi-window toast not shown as "
+ + "package '%s' cannot be found.",
+ packageName);
+ }
}
}
}
@@ -434,6 +446,15 @@
() -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
}
+ @VisibleForTesting
+ void showToast(@StringRes int stringRes, @NonNull String applicationLabel) {
+ UiThread.getHandler().post(
+ () -> Toast.makeText(
+ mWmService.mContext,
+ mWmService.mContext.getString(stringRes, applicationLabel),
+ Toast.LENGTH_LONG).show());
+ }
+
private synchronized void notifyCameraClosed(@NonNull String cameraId) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c6c3b14..0c1f33c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1017,8 +1017,11 @@
if (isTopActivityLaunchedBehind()) {
return TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
+ final WindowContainer<?> parent = getParent();
final Task thisTask = asTask();
- if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
+ if (thisTask != null && parent.asTask() == null
+ && mTransitionController.isTransientHide(thisTask)) {
+ // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule.
return TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
@@ -1028,7 +1031,6 @@
// This TaskFragment is only considered visible if all its parent TaskFragments are
// considered visible, so check the visibility of all ancestor TaskFragment first.
- final WindowContainer parent = getParent();
if (parent.asTaskFragment() != null) {
final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 774f62d..fd478dc 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -31,6 +31,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -245,10 +246,13 @@
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ ActivityOptions activityOptions = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- null, new UserHandle(mUserId)) .getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ activityOptions.toBundle(), new UserHandle(mUserId)).getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376..ca4a404 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -66,6 +66,7 @@
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
@@ -2506,6 +2507,7 @@
}
@LargeTest
+ @FlakyTest(bugId = 283797480)
public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
// checkSignatures should be successful when both apps have been signed with the same
// rotated key since the initial signature comparison between the two apps should
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 948687a..582685c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1407,7 +1407,7 @@
eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
- anyString(), anyInt(), anyInt()),
+ anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
times(1));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 4989f84..03231ec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1904,6 +1904,34 @@
}
@Test
+ public void testReplacePending_diffReceivers() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5),
+ withPriority(receiverYellow, 0))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5))));
+
+ waitForIdle();
+
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index f44c1d1..4315254 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
@@ -36,6 +37,7 @@
<uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9166b3d..2a0c745 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -61,6 +61,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -80,6 +81,7 @@
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
@@ -119,12 +121,14 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -181,11 +185,14 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -351,6 +358,9 @@
private PermissionManager mPermissionManager;
@Mock
private DevicePolicyManagerInternal mDevicePolicyManager;
+ @Mock
+ private PowerManager mPowerManager;
+ private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>();
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
@@ -431,8 +441,9 @@
private final List<PostNotificationTracker> mCreatedTrackers = new ArrayList<>();
@Override
- public PostNotificationTracker newTracker() {
- PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker();
+ public PostNotificationTracker newTracker(@Nullable WakeLock optionalWakeLock) {
+ PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker(
+ optionalWakeLock);
mCreatedTrackers.add(tracker);
return tracker;
}
@@ -563,6 +574,20 @@
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
+ // Use the real PowerManager to back up the mock w.r.t. creating WakeLocks.
+ // This is because 1) we need a mock to verify() calls and tracking the created WakeLocks,
+ // but 2) PowerManager and WakeLock perform their own checks (e.g. correct arguments, don't
+ // call release twice, etc) and we want the test to fail if such misuse happens, too.
+ PowerManager realPowerManager = mContext.getSystemService(PowerManager.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).then(
+ (Answer<WakeLock>) invocation -> {
+ WakeLock wl = realPowerManager.newWakeLock(invocation.getArgument(0),
+ invocation.getArgument(1));
+ mAcquiredWakeLocks.add(wl);
+ return wl;
+ });
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+
// apps allowed as convos
mService.setStringArrayResourceValue(PKG_O);
@@ -579,7 +604,7 @@
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
- mPostNotificationTrackerFactory);
+ mPowerManager, mPostNotificationTrackerFactory);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -686,6 +711,13 @@
}
@After
+ public void assertAllWakeLocksReleased() {
+ for (WakeLock wakeLock : mAcquiredWakeLocks) {
+ assertThat(wakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @After
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
@@ -1486,7 +1518,7 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1507,7 +1539,7 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1803,6 +1835,112 @@
}
@Test
+ public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_throws_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to rejected inputs.
+ assertThrows(Exception.class,
+ () -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
+ /* notification= */ null, 0));
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to snoozing inputs.
+ when(mSnoozeHelper.getSnoozeContextForUnpostedNotification(anyInt(), any(), any()))
+ .thenReturn("zzzzzzz");
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notPosted_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate enqueued but not posted due to missing small icon.
+ Notification notif = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .build();
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
+ notif, 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ // NLSes were not called.
+ verify(mListeners, never()).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_setsWakeLockWorkSource() throws Exception {
+ // Use a "full" mock for the PowerManager (instead of the one that delegates to the real
+ // service) so we can return a mocked WakeLock that we can verify() on.
+ reset(mPowerManager);
+ WakeLock wakeLock = mock(WakeLock.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(wakeLock);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ InOrder inOrder = inOrder(mPowerManager, wakeLock);
+ inOrder.verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ inOrder.verify(wakeLock).setWorkSource(eq(new WorkSource(mUid, PKG)));
+ inOrder.verify(wakeLock).acquire(anyLong());
+ inOrder.verify(wakeLock).release();
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception {
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ verifyZeroInteractions(mPowerManager);
+ }
+
+ @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
@@ -4361,7 +4499,7 @@
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4380,7 +4518,7 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4400,7 +4538,7 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4420,7 +4558,7 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
- update.getUid(), mPostNotificationTrackerFactory.newTracker());
+ update.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4434,13 +4572,13 @@
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
r.setCriticality(CriticalNotificationExtractor.CRITICAL);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
mService.addEnqueuedNotification(r);
runnable.run();
@@ -5090,7 +5228,7 @@
mService.new PostNotificationRunnable(original.getKey(),
original.getSbn().getPackageName(),
original.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -5114,7 +5252,7 @@
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -7536,7 +7674,7 @@
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10234,7 +10372,7 @@
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10251,7 +10389,7 @@
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10268,7 +10406,7 @@
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10361,7 +10499,7 @@
// normal blocked notifications - blocked
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10379,7 +10517,7 @@
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10392,7 +10530,7 @@
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10406,7 +10544,7 @@
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10420,7 +10558,7 @@
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10435,7 +10573,7 @@
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10448,7 +10586,7 @@
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 66c1e35..81c573d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,6 +48,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
@@ -169,6 +170,7 @@
mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
mock(NotificationChannelLogger.class), new TestableFlagResolver(),
mock(PermissionManager.class),
+ mock(PowerManager.class),
new NotificationManagerService.PostNotificationTrackerFactory() {});
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 0044e2e..4290f4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -1059,4 +1060,18 @@
assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
mAtm.mInternal.unregisterActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID);
}
+
+ @Test
+ public void testFocusTopTask() {
+ final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
+ .setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
+ .build();
+ final Task pinnedTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_PINNED)
+ .build();
+ mAtm.focusTopTask(mDisplayContent.mDisplayId);
+
+ assertTrue(homeActivity.getTask().isFocused());
+ assertFalse(pinnedTask.isFocused());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 3ca35ef..8e015d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -40,7 +40,9 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -51,6 +53,8 @@
import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Configuration.Orientation;
import android.hardware.camera2.CameraManager;
@@ -84,7 +88,7 @@
private static final String TEST_PACKAGE_2 = "com.test.package.two";
private static final String CAMERA_ID_1 = "camera-1";
private static final String CAMERA_ID_2 = "camera-2";
-
+ private static final String TEST_PACKAGE_1_LABEL = "testPackage1";
private CameraManager mMockCameraManager;
private Handler mMockHandler;
private LetterboxConfiguration mLetterboxConfiguration;
@@ -133,17 +137,27 @@
}
@Test
- public void testOpenedCameraInSplitScreen_showToast() {
+ public void testOpenedCameraInSplitScreen_showToast() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
spyOn(mTask);
spyOn(mDisplayRotationCompatPolicy);
doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+ final PackageManager mockPackageManager = mock(PackageManager.class);
+ final ApplicationInfo mockApplicationInfo = mock(ApplicationInfo.class);
+ when(mContext.getPackageManager()).thenReturn(mockPackageManager);
+ when(mockPackageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenReturn(mockApplicationInfo);
+
+ doReturn(TEST_PACKAGE_1_LABEL).when(mockPackageManager)
+ .getApplicationLabel(mockApplicationInfo);
+
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
verify(mDisplayRotationCompatPolicy).showToast(
- R.string.display_rotation_camera_compat_toast_in_split_screen);
+ R.string.display_rotation_camera_compat_toast_in_multi_window,
+ TEST_PACKAGE_1_LABEL);
}
@Test
@@ -157,7 +171,8 @@
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
verify(mDisplayRotationCompatPolicy, never()).showToast(
- R.string.display_rotation_camera_compat_toast_in_split_screen);
+ R.string.display_rotation_camera_compat_toast_in_multi_window,
+ TEST_PACKAGE_1_LABEL);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 04e1d9c..2a8f0ffc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -59,10 +59,10 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
-import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.DisplayAddress;
@@ -518,7 +518,8 @@
mBuilder.build();
configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
thawRotation();
@@ -544,7 +545,8 @@
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_270, mTarget.getUserRotation());
@@ -553,7 +555,8 @@
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(false);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_90, mTarget.getUserRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index fb29d3a..abf21a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -641,6 +641,31 @@
// Home split secondary and home task should be invisible.
assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitSecondary.getVisibility(null /* starting */));
+
+ // Put another task on top of primary split.
+ final Task topSplitPrimary = new TaskBuilder(mSupervisor).setParentTask(organizer.mPrimary)
+ .setCreateActivity(true).build();
+ doReturn(false).when(topSplitPrimary).isTranslucent(any());
+ // Convert the fullscreen translucent task to opaque.
+ doReturn(false).when(translucentRootTask).isTranslucent(any());
+ translucentRootTask.moveToFront("test");
+ // The tasks of primary split are occluded by the fullscreen opaque task.
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ organizer.mPrimary.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ topSplitPrimary.getVisibility(null /* starting */));
+ // Make primary split root transient-hide.
+ spyOn(splitPrimary.mTransitionController);
+ doReturn(true).when(splitPrimary.mTransitionController).isTransientHide(
+ organizer.mPrimary);
+ // The split root and its top become visible.
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ organizer.mPrimary.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ topSplitPrimary.getVisibility(null /* starting */));
+ // The bottom of primary split becomes invisible because it is occluded by topSplitPrimary.
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ splitPrimary.getVisibility(null /* starting */));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e74ad33..082122e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -406,7 +406,6 @@
clearInvocations(translucentActivity.mLetterboxUiController);
// We destroy the first opaque activity
- mActivity.setState(DESTROYED, "testing");
mActivity.removeImmediately();
// Check that updateInheritedLetterbox() is invoked again
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index bedf099..bfebb42 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -19,6 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
</target_preparer>