Merge "Fix tests broken by @NonNull annotation fix" into main
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index b56dceb..2cbd665 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -723,7 +723,7 @@
bool BootAnimation::preloadAnimation() {
ATRACE_CALL();
findBootAnimationFile();
- if (!mZipFileName.isEmpty()) {
+ if (!mZipFileName.empty()) {
mAnimation = loadAnimation(mZipFileName);
return (mAnimation != nullptr);
}
@@ -842,7 +842,7 @@
// We have no bootanimation file, so we use the stock android logo
// animation.
- if (mZipFileName.isEmpty()) {
+ if (mZipFileName.empty()) {
ALOGD("No animation file");
result = android();
} else {
diff --git a/core/api/current.txt b/core/api/current.txt
index bcb21e5..d26cf2e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10560,6 +10560,7 @@
public final class ContextParams {
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNextAttributionSource();
+ method @NonNull public boolean shouldRegisterAttributionSource();
}
public static final class ContextParams.Builder {
@@ -10568,6 +10569,7 @@
method @NonNull public android.content.ContextParams build();
method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
+ method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
}
public class ContextWrapper extends android.content.Context {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5feafbe..a538247 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3455,20 +3455,20 @@
mOpPackageName = overrideOpPackageName != null ? overrideOpPackageName : opPackageName;
mParams = Objects.requireNonNull(params);
mAttributionSource = createAttributionSource(attributionTag, nextAttributionSource,
- params.getRenouncedPermissions());
+ params.getRenouncedPermissions(), params.shouldRegisterAttributionSource());
mContentResolver = new ApplicationContentResolver(this, mainThread);
}
private @NonNull AttributionSource createAttributionSource(@Nullable String attributionTag,
@Nullable AttributionSource nextAttributionSource,
- @Nullable Set<String> renouncedPermissions) {
+ @Nullable Set<String> renouncedPermissions, boolean shouldRegister) {
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
Process.myPid(), mOpPackageName, attributionTag,
(renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
getDeviceId(), nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
- if (nextAttributionSource != null) {
+ if (nextAttributionSource != null || shouldRegister) {
attributionSource = getSystemService(PermissionManager.class)
.registerAttributionSource(attributionSource);
}
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 729e555..0857c96 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -40,7 +40,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -195,7 +195,8 @@
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
int outerDepth = parser.getDepth();
AttributeSet attrs = Xml.asAttributeSet(parser);
- Set<String> localeNames = new HashSet<String>();
+ // LinkedHashSet to preserve insertion order
+ Set<String> localeNames = new LinkedHashSet<>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
final TypedArray attributes = res.obtainAttributes(
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 5cc3a24..988a9c0 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -50,17 +50,20 @@
private final @Nullable String mAttributionTag;
private final @Nullable AttributionSource mNext;
private final @NonNull Set<String> mRenouncedPermissions;
+ private final boolean mShouldRegisterAttributionSource;
/** {@hide} */
public static final ContextParams EMPTY = new ContextParams.Builder().build();
private ContextParams(@Nullable String attributionTag,
@Nullable AttributionSource next,
- @Nullable Set<String> renouncedPermissions) {
+ @Nullable Set<String> renouncedPermissions,
+ boolean shouldRegister) {
mAttributionTag = attributionTag;
mNext = next;
mRenouncedPermissions = (renouncedPermissions != null)
? renouncedPermissions : Collections.emptySet();
+ mShouldRegisterAttributionSource = shouldRegister;
}
/**
@@ -95,12 +98,22 @@
}
/**
+ * @return Whether the attribution source associated with the Context being created should be
+ * registered.
+ */
+ @NonNull
+ public boolean shouldRegisterAttributionSource() {
+ return mShouldRegisterAttributionSource;
+ }
+
+ /**
* Builder for creating a {@link ContextParams}.
*/
public static final class Builder {
private @Nullable String mAttributionTag;
private @NonNull Set<String> mRenouncedPermissions = Collections.emptySet();
private @Nullable AttributionSource mNext;
+ private boolean mShouldRegisterAttributionSource;
/**
* Create a new builder.
@@ -159,6 +172,19 @@
}
/**
+ * Sets whether the attribution source associated with the context created from these params
+ * should be registered.
+ *
+ * @param shouldRegister Whether the attribution source associated with the Context being
+ * created should be registered.
+ */
+ @NonNull
+ public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
+ mShouldRegisterAttributionSource = shouldRegister;
+ return this;
+ }
+
+ /**
* Sets permissions which have been voluntarily "renounced" by the
* calling app.
* <p>
@@ -205,7 +231,7 @@
@NonNull
public ContextParams build() {
return new ContextParams(mAttributionTag, mNext,
- mRenouncedPermissions);
+ mRenouncedPermissions, mShouldRegisterAttributionSource);
}
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 9387ae1..41ba1dc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,8 +1456,8 @@
private static AssetManager newConfiguredAssetManager() {
AssetManager assetManager = new AssetManager();
- assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
return assetManager;
}
@@ -9011,8 +9011,8 @@
}
AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
mCachedAssetManager = assets;
@@ -9086,8 +9086,8 @@
private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
final AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
return assets;
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index b225de4..23b9d0b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1480,9 +1480,13 @@
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
int majorVersion) {
- synchronized (this) {
- ensureValidLocked();
- nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
+ if (locale != null) {
+ setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
+ density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
+ smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
+ colorMode, grammaticalGender, majorVersion);
+ } else {
+ setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
colorMode, grammaticalGender, majorVersion);
@@ -1490,6 +1494,25 @@
}
/**
+ * Change the configuration used when retrieving resources. Not for use by
+ * applications.
+ * @hide
+ */
+ public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
+ int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
+ int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
+ int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
+ int grammaticalGender, int majorVersion) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
+ touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
+ screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
+ screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
+ }
+ }
+
+ /**
* @hide
*/
@UnsupportedAppUsage
@@ -1572,10 +1595,11 @@
private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
boolean invalidateCaches);
private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
- @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
- int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
- int uiMode, int colorMode, int grammaticalGender, int majorVersion);
+ @Nullable String defaultLocale, @NonNull String[] locales, int orientation,
+ int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
+ int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
+ int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
+ int majorVersion);
private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
long ptr, boolean includeOverlays, boolean includeLoaders);
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 395fef2..76b29e6 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -425,14 +425,12 @@
mConfiguration.setLocales(locales);
}
+ String[] selectedLocales = null;
+ String defaultLocale = null;
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
String[] availableLocales;
-
- LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
- if (!localeList.isEmpty()) {
- availableLocales = localeList.toLanguageTags().split(",");
- } else {
+ if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
// The LocaleList has changed. We must query the AssetManager's
// available Locales and figure out the best matching Locale in the new
// LocaleList.
@@ -444,13 +442,35 @@
availableLocales = null;
}
}
- }
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null && bestLocale != locales.get(0)) {
- mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(bestLocale.toLanguageTag())};
+ if (!bestLocale.equals(locales.get(0))) {
+ mConfiguration.setLocales(
+ new LocaleList(bestLocale, locales));
+ }
+ }
}
+ } else {
+ selectedLocales = locales.getIntersection(
+ ResourcesManager.getInstance().getLocaleList());
+ defaultLocale = ResourcesManager.getInstance()
+ .getLocaleList().get(0).toLanguageTag();
+ }
+ }
+ }
+ if (selectedLocales == null) {
+ if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(locales.get(0).toLanguageTag())};
+ } else {
+ selectedLocales = new String[locales.size()];
+ for (int i = 0; i < locales.size(); i++) {
+ selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
}
}
}
@@ -488,7 +508,8 @@
}
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
- adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
+ defaultLocale,
+ selectedLocales,
mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5940819..703f165 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1468,6 +1468,13 @@
* <p>Only constrains auto-exposure (AE) algorithm, not
* manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.</p>
+ * <p>To start a CaptureSession with a target FPS range different from the
+ * capture request template's default value, the application
+ * is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the target fps range before creating the capture session. The aeTargetFpsRange is
+ * typically a session parameter. Specifying it at session creation time helps avoid
+ * session reconfiguration delays in cases like 60fps or high speed recording.</p>
* <p><b>Units</b>: Frames per second (FPS)</p>
* <p><b>Range of valid values:</b><br>
* Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}</p>
@@ -2140,6 +2147,12 @@
* {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
+ * <p>The application is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the desired video stabilization mode before creating the capture session.
+ * Video stabilization mode is a session parameter on many devices. Specifying
+ * it at session creation time helps avoid reconfiguration delay caused by difference
+ * between the default value and the first CaptureRequest.</p>
* <p>If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 905f98d..746648b 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -887,6 +887,13 @@
* <p>Only constrains auto-exposure (AE) algorithm, not
* manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.</p>
+ * <p>To start a CaptureSession with a target FPS range different from the
+ * capture request template's default value, the application
+ * is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the target fps range before creating the capture session. The aeTargetFpsRange is
+ * typically a session parameter. Specifying it at session creation time helps avoid
+ * session reconfiguration delays in cases like 60fps or high speed recording.</p>
* <p><b>Units</b>: Frames per second (FPS)</p>
* <p><b>Range of valid values:</b><br>
* Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}</p>
@@ -2365,6 +2372,12 @@
* {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
+ * <p>The application is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the desired video stabilization mode before creating the capture session.
+ * Video stabilization mode is a session parameter on many devices. Specifying
+ * it at session creation time helps avoid reconfiguration delay caused by difference
+ * between the default value and the first CaptureRequest.</p>
* <p>If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8f653b3..c0a44b1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -650,8 +650,6 @@
private InlineSuggestionSessionController mInlineSuggestionSessionController;
- private boolean mHideNavBarForKeyboard;
- private boolean mIsAutomotive;
private @NonNull OptionalInt mHandwritingRequestId = OptionalInt.empty();
private InputEventReceiver mHandwritingEventReceiver;
private Handler mHandler;
@@ -1622,7 +1620,7 @@
// shown the first time (cold start).
mSettingsObserver.shouldShowImeWithHardKeyboard();
- mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
+ final boolean hideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
com.android.internal.R.bool.config_hideNavBarForKeyboard);
initConfigurationTracker();
@@ -1668,7 +1666,7 @@
// screen real estate. When this happens, the IME window should animate from the
// bottom of the screen to reduce the jank that happens from the lack of synchronization
// between the bottom system window and the IME window.
- if (mHideNavBarForKeyboard) {
+ if (hideNavBarForKeyboard) {
window.setDecorFitsSystemWindows(false);
}
}
@@ -2366,9 +2364,7 @@
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
- mExtractFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ mExtractFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mExtractView = view;
if (view != null) {
mExtractEditText = view.findViewById(
@@ -2387,7 +2383,7 @@
mExtractAction = null;
}
}
-
+
/**
* Replaces the current candidates view with a new one. You only need to
* call this when dynamically changing the view; normally, you should
@@ -2396,11 +2392,9 @@
*/
public void setCandidatesView(View view) {
mCandidatesFrame.removeAllViews();
- mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
}
-
+
/**
* Replaces the current input view with a new one. You only need to
* call this when dynamically changing the view; normally, you should
@@ -2409,12 +2403,10 @@
*/
public void setInputView(View view) {
mInputFrame.removeAllViews();
- mInputFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ mInputFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
mInputView = view;
}
-
+
/**
* Called by the framework to create the layout for showing extracted text.
* Only called when in fullscreen mode. The returned view hierarchy must
@@ -3448,9 +3440,12 @@
return false;
}
+ /**
+ * Not implemented in this class.
+ */
public void onAppPrivateCommand(String action, Bundle data) {
}
-
+
/**
* Handle a request by the system to toggle the soft input area.
*/
@@ -4092,11 +4087,6 @@
| (isInputViewShown() ? IME_VISIBLE : 0);
}
- private boolean isAutomotive() {
- return getApplicationContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_AUTOMOTIVE);
- }
-
/**
* Performs a dump of the InputMethodService's internal state. Override
* to add your own information to the dump.
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index b74bb33..82cdd28 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -30,6 +30,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
/**
@@ -151,6 +152,25 @@
}
/**
+ * Find the intersection between this LocaleList and another
+ * @return a String array of the Locales in both LocaleLists
+ * {@hide}
+ */
+ @NonNull
+ public String[] getIntersection(@NonNull LocaleList other) {
+ List<String> intersection = new ArrayList<>();
+ for (Locale l1 : mList) {
+ for (Locale l2 : other.mList) {
+ if (matchesLanguageAndScript(l2, l1)) {
+ intersection.add(l1.toLanguageTag());
+ break;
+ }
+ }
+ }
+ return intersection.toArray(new String[0]);
+ }
+
+ /**
* Creates a new {@link LocaleList}.
*
* If two or more same locales are passed, the repeated locales will be dropped.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 45b4935..522caac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11656,6 +11656,21 @@
"accessibility_floating_menu_migration_tooltip_prompt";
/**
+ * For the force dark theme feature which inverts any apps that don't already support dark
+ * theme.
+ *
+ * If true, it will automatically invert any app that is mainly light.
+ *
+ * This is related to the force dark override setting, however it will always force the apps
+ * colors and will ignore any developer hints or opt-out APIs.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED =
+ "accessibility_force_invert_color_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 1c59742..eb49f41 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -874,7 +874,7 @@
if (camera == 0) return 0;
String8 params8 = camera->getParameters();
- if (params8.isEmpty()) {
+ if (params8.empty()) {
jniThrowRuntimeException(env, "getParameters failed (empty parameters)");
return 0;
}
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 5293c58..3e4c7c7 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -526,7 +526,7 @@
"Failed to read from fd (errno = %#x, message = '%s')",
errno, strerror(errno));
//return;
- } else if (!logLine.isEmpty()) {
+ } else if (!logLine.empty()) {
ALOGD("%s", logLine.string());
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index f04b901..3ee15ab 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -347,14 +347,23 @@
}
static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
- jstring locale, jint orientation, jint touchscreen, jint density,
- jint keyboard, jint keyboard_hidden, jint navigation,
- jint screen_width, jint screen_height,
- jint smallest_screen_width_dp, jint screen_width_dp,
- jint screen_height_dp, jint screen_layout, jint ui_mode,
- jint color_mode, jint grammatical_gender, jint major_version) {
+ jstring default_locale, jobjectArray locales, jint orientation,
+ jint touchscreen, jint density, jint keyboard,
+ jint keyboard_hidden, jint navigation, jint screen_width,
+ jint screen_height, jint smallest_screen_width_dp,
+ jint screen_width_dp, jint screen_height_dp, jint screen_layout,
+ jint ui_mode, jint color_mode, jint grammatical_gender,
+ jint major_version) {
ATRACE_NAME("AssetManager::SetConfiguration");
+ const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
+
+ // Constants duplicated from Java class android.content.res.Configuration.
+ static const jint kScreenLayoutRoundMask = 0x300;
+ static const jint kScreenLayoutRoundShift = 8;
+
+ std::vector<ResTable_config> configs;
+
ResTable_config configuration;
memset(&configuration, 0, sizeof(configuration));
configuration.mcc = static_cast<uint16_t>(mcc);
@@ -375,25 +384,37 @@
configuration.colorMode = static_cast<uint8_t>(color_mode);
configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
configuration.sdkVersion = static_cast<uint16_t>(major_version);
-
- if (locale != nullptr) {
- ScopedUtfChars locale_utf8(env, locale);
- CHECK(locale_utf8.c_str() != nullptr);
- configuration.setBcp47Locale(locale_utf8.c_str());
- }
-
- // Constants duplicated from Java class android.content.res.Configuration.
- static const jint kScreenLayoutRoundMask = 0x300;
- static const jint kScreenLayoutRoundShift = 8;
-
// In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
// in C++. We must extract the round qualifier out of the Java screenLayout and put it
// into screenLayout2.
configuration.screenLayout2 =
- static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+ static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
+ if (locale_count > 0) {
+ configs.resize(locale_count, configuration);
+ for (int i = 0; i < locale_count; i++) {
+ jstring locale = (jstring)(env->GetObjectArrayElement(locales, i));
+ ScopedUtfChars locale_utf8(env, locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ configs[i].setBcp47Locale(locale_utf8.c_str());
+ }
+ } else {
+ configs.push_back(configuration);
+ }
+
+ uint32_t default_locale_int = 0;
+ if (default_locale != nullptr) {
+ ResTable_config config;
+ static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>);
+ ScopedUtfChars locale_utf8(env, default_locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ config.setBcp47Locale(locale_utf8.c_str());
+ default_locale_int = config.locale;
+ }
auto assetmanager = LockAndStartAssetManager(ptr);
- assetmanager->SetConfiguration(configuration);
+ assetmanager->SetConfigurations(configs);
+ assetmanager->SetDefaultLocale(default_locale_int);
}
static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
@@ -1498,94 +1519,97 @@
// JNI registration.
static const JNINativeMethod gAssetManagerMethods[] = {
- // AssetManager setup methods.
- {"nativeCreate", "()J", (void*)NativeCreate},
- {"nativeDestroy", "(J)V", (void*)NativeDestroy},
- {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
- {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
- (void*)NativeSetConfiguration},
- {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
- (void*)NativeGetAssignedPackageIdentifiers},
+ // AssetManager setup methods.
+ {"nativeCreate", "()J", (void*)NativeCreate},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+ {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
+ (void*)NativeSetConfiguration},
+ {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
+ (void*)NativeGetAssignedPackageIdentifiers},
- // AssetManager file methods.
- {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
- {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
- {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
- {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*)NativeOpenAssetFd},
- {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
- {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*)NativeOpenNonAssetFd},
- {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
- {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
+ // AssetManager file methods.
+ {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
+ {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+ {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+ {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenAssetFd},
+ {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+ {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenNonAssetFd},
+ {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+ {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
- // AssetManager resource methods.
- {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
- {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
- (void*)NativeGetResourceBagValue},
- {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
- {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
- (void*)NativeGetResourceStringArray},
- {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
- {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
- {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
- {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
- {"nativeGetParentThemeIdentifier", "(JI)I",
- (void*)NativeGetParentThemeIdentifier},
+ // AssetManager resource methods.
+ {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I",
+ (void*)NativeGetResourceValue},
+ {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+ (void*)NativeGetResourceBagValue},
+ {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+ {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+ (void*)NativeGetResourceStringArray},
+ {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+ {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+ {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+ {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+ {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier},
- // AssetManager resource name/ID methods.
- {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
- (void*)NativeGetResourceIdentifier},
- {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
- {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
- {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
- {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
- {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
- (void*) NativeSetResourceResolutionLoggingEnabled},
- {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
- (void*) NativeGetLastResourceResolution},
- {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
- {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
- (void*)NativeGetSizeConfigurations},
- {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
- (void*)NativeGetSizeAndUiModeConfigurations},
+ // AssetManager resource name/ID methods.
+ {"nativeGetResourceIdentifier",
+ "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)NativeGetResourceIdentifier},
+ {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+ {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;",
+ (void*)NativeGetResourcePackageName},
+ {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+ {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+ {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+ (void*)NativeSetResourceResolutionLoggingEnabled},
+ {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+ (void*)NativeGetLastResourceResolution},
+ {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+ {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeConfigurations},
+ {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeAndUiModeConfigurations},
- // Style attribute related methods.
- {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
- {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
- {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
- {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+ // Style attribute related methods.
+ {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
+ {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+ {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+ {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
- // Theme related methods.
- {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
- {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
- {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
- {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+ // Theme related methods.
+ {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+ {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
+ {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+ {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
- {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
- {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
- (void*)NativeThemeGetAttributeValue},
- {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
- {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
+ {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
+ {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+ (void*)NativeThemeGetAttributeValue},
+ {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+ {"nativeThemeGetChangingConfigurations", "(J)I",
+ (void*)NativeThemeGetChangingConfigurations},
- // AssetInputStream methods.
- {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
- {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
- {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
- {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
- {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
- {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+ // AssetInputStream methods.
+ {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+ {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+ {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+ {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+ {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+ {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
- // System/idmap related methods.
- {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
- (void*)NativeGetOverlayableMap},
- {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
- (void*)NativeGetOverlayablesToString},
+ // System/idmap related methods.
+ {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
+ (void*)NativeGetOverlayableMap},
+ {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+ (void*)NativeGetOverlayablesToString},
- // Global management/debug methods.
- {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
- {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
- {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+ // Global management/debug methods.
+ {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+ {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+ {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
};
int register_android_content_AssetManager(JNIEnv* env) {
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ed0081c..ad88092 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -97,6 +97,7 @@
optional SettingProto accessibility_magnification_joystick_enabled = 50 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Settings for font scaling
optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f45499a..e54347f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3537,11 +3537,11 @@
<attr name="preferKeepClear" format="boolean" />
<!-- <p>Whether or not the auto handwriting initiation is enabled in this View.
- <p>For a view with active {@link android.view.inputmethod.InputConnection},
- if auto handwriting initiation is enabled stylus movement within its view boundary
+ <p>For a view with an active {@link android.view.inputmethod.InputConnection},
+ if auto handwriting initiation is enabled, stylus movement within its view boundary
will automatically trigger the handwriting mode.
- <p>This is true by default.
- See {@link android.view.View#setAutoHandwritingEnabled}. -->
+ See {@link android.view.View#setAutoHandwritingEnabled}.
+ <p>The default value of this flag is configurable by the device manufacturer. -->
<attr name="autoHandwritingEnabled" format="boolean" />
<!-- <p>The amount of offset that is applied to the left edge of the view's stylus
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index 54a3817..87e4a42 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -39,6 +39,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,6 +55,7 @@
* Due to how the classes are structured, we have to test it in a somewhat roundabout way. We're
* mocking out the contentProvider and are handcrafting very specific Bundles to answer the queries.
*/
+@Ignore("b/297724333")
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -233,6 +235,7 @@
mConfigsCacheGenerationStore.close();
}
+ @Ignore("b/297724333")
@Test
public void testCaching_singleNamespace() throws Exception {
HashMap<String, String> keyValues = new HashMap<>();
@@ -270,6 +273,7 @@
assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues);
}
+ @Ignore("b/297724333")
@Test
public void testCaching_multipleNamespaces() throws Exception {
HashMap<String, String> keyValues = new HashMap<>();
@@ -309,6 +313,7 @@
assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues2);
}
+ @Ignore("b/297724333")
@Test
public void testCaching_emptyNamespace() throws Exception {
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
@@ -325,6 +330,7 @@
assertThat(cachedKeyValues).isEmpty();
}
+ @Ignore("b/297724333")
@Test
public void testCaching_singleSetting() throws Exception {
Settings.Secure.putString(mMockContentResolver, SETTING, "a");
@@ -355,6 +361,7 @@
assertThat(cachedValue2).isEqualTo("b");
}
+ @Ignore("b/297724333")
@Test
public void testCaching_multipleSettings() throws Exception {
Settings.Secure.putString(mMockContentResolver, SETTING, "a");
@@ -385,6 +392,7 @@
assertThat(cachedValue2).isEqualTo("b");
}
+ @Ignore("b/297724333")
@Test
public void testCaching_unsetSetting() throws Exception {
String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
index fd000ee..a8743fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -21,7 +21,6 @@
import android.graphics.PointF
import android.util.Size
import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipDisplayLayoutState
class LegacySizeSpecSource(
private val context: Context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
index 2d34035..133242d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhonePipKeepClearAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.content.Context;
import android.content.res.Resources;
@@ -24,9 +24,6 @@
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
index c563068..18c7bdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -21,7 +21,6 @@
import android.os.SystemProperties
import android.util.Size
import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipDisplayLayoutState
import java.io.PrintWriter
class PhoneSizeSpecSource(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index 4fef672..a9f687f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,8 +28,6 @@
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import java.io.PrintWriter;
@@ -202,7 +200,8 @@
*
* @return {@code false} if the given source is too small to use for the entering animation.
*/
- static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+ public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint,
+ Rect destinationBounds) {
return sourceRectHint != null
&& sourceRectHint.width() > destinationBounds.width()
&& sourceRectHint.height() > destinationBounds.height();
@@ -224,7 +223,7 @@
}
/**
- * @return whether the given {@param aspectRatio} is valid.
+ * @return whether the given aspectRatio is valid.
*/
public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return Float.compare(mMinAspectRatio, aspectRatio) <= 0
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 279ffc5..3b32b6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -36,7 +36,6 @@
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -314,8 +313,11 @@
return mPipDisplayLayoutState.getDisplayLayout();
}
+ /**
+ * Clears the PiP re-entry state.
+ */
@VisibleForTesting
- void clearReentryState() {
+ public void clearReentryState() {
mPipReentryState = null;
}
@@ -400,11 +402,18 @@
mNamedUnrestrictedKeepClearAreas.remove(name);
}
+
+ /**
+ * @return restricted keep clear areas.
+ */
@NonNull
public Set<Rect> getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
}
+ /**
+ * @return unrestricted keep clear areas.
+ */
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
@@ -561,7 +570,11 @@
}
}
- static final class PipReentryState {
+ /**
+ * Represents the state of pip to potentially restore upon reentry.
+ */
+ @VisibleForTesting
+ public static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
private final @Nullable Size mSize;
@@ -573,11 +586,11 @@
}
@Nullable
- Size getSize() {
+ public Size getSize() {
return mSize;
}
- float getSnapFraction() {
+ public float getSnapFraction() {
return mSnapFraction;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
index 4aa260b..ed42117 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import static com.android.wm.shell.common.pip.PipUtils.dpToPx;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
index 5045cf9..954233c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipKeepClearAlgorithmInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipKeepClearAlgorithmInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
import android.graphics.Rect;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
index 23153be72..02b3a88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPinchResizingAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.graphics.Point;
import android.graphics.PointF;
@@ -35,7 +35,7 @@
private final PointF mTmpLastCentroid = new PointF();
/**
- * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in
+ * Updates resizeBoundsOut with the new bounds of the PIP, and returns the angle in
* degrees that the PIP should be rotated.
*/
public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
similarity index 84%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
index dd30137..007052e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipSnapAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.common.pip;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import android.graphics.Rect;
@@ -39,14 +39,14 @@
}
/**
- * @return returns a fraction that describes where along the {@param movementBounds} the
- * {@param stackBounds} are. If the {@param stackBounds} are not currently on the
- * {@param movementBounds} exactly, then they will be snapped to the movement bounds.
+ * @return returns a fraction that describes where along the movementBounds the
+ * stackBounds are. If the stackBounds are not currently on the
+ * movementBounds exactly, then they will be snapped to the movement bounds.
* stashType dictates whether the PiP is stashed (off-screen) or not. If
* that's the case, we will have to do some math to calculate the snap fraction
* correctly.
*
- * The fraction is defined in a clockwise fashion against the {@param movementBounds}:
+ * The fraction is defined in a clockwise fashion against the movementBounds:
*
* 0 1
* 4 +---+ 1
@@ -58,10 +58,10 @@
@PipBoundsState.StashType int stashType) {
final Rect tmpBounds = new Rect();
snapRectToClosestEdge(stackBounds, movementBounds, tmpBounds, stashType);
- final float widthFraction = (float) (tmpBounds.left - movementBounds.left) /
- movementBounds.width();
- final float heightFraction = (float) (tmpBounds.top - movementBounds.top) /
- movementBounds.height();
+ final float widthFraction = (float) (tmpBounds.left - movementBounds.left)
+ / movementBounds.width();
+ final float heightFraction = (float) (tmpBounds.top - movementBounds.top)
+ / movementBounds.height();
if (tmpBounds.top == movementBounds.top) {
return widthFraction;
} else if (tmpBounds.left == movementBounds.right) {
@@ -74,10 +74,10 @@
}
/**
- * Moves the {@param stackBounds} along the {@param movementBounds} to the given snap fraction.
+ * Moves the stackBounds along the movementBounds to the given snap fraction.
* See {@link #getSnapFraction(Rect, Rect)}.
*
- * The fraction is define in a clockwise fashion against the {@param movementBounds}:
+ * The fraction is define in a clockwise fashion against the movementBounds:
*
* 0 1
* 4 +---+ 1
@@ -122,11 +122,11 @@
}
/**
- * Snaps the {@param stackBounds} to the closest edge of the {@param movementBounds} and writes
- * the new bounds out to {@param boundsOut}.
+ * Snaps the stackBounds to the closest edge of the movementBounds and writes
+ * the new bounds out to boundsOut.
*/
@VisibleForTesting
- void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
+ public void snapRectToClosestEdge(Rect stackBounds, Rect movementBounds, Rect boundsOut,
@PipBoundsState.StashType int stashType) {
int leftEdge = stackBounds.left;
if (stashType == STASH_TYPE_LEFT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index e28b8d7..1c2cee5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -59,8 +59,15 @@
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
@@ -354,6 +361,42 @@
return new PipMediaController(context, mainHandler);
}
+ @WMSingleton
+ @Provides
+ static SizeSpecSource provideSizeSpecSource(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsState providePipBoundsState(Context context,
+ SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
+ }
+
+
+ @WMSingleton
+ @Provides
+ static PipSnapAlgorithm providePipSnapAlgorithm() {
+ return new PipSnapAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
+ static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+ return new PhonePipKeepClearAlgorithm(context);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+ PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+ return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+ pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
+ }
//
// Bubbles (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 4e92ca1..ba882c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -30,9 +30,13 @@
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
@@ -41,17 +45,12 @@
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
@@ -119,35 +118,6 @@
}
}
- @WMSingleton
- @Provides
- static PipBoundsState providePipBoundsState(Context context,
- SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
- return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
- static PipSnapAlgorithm providePipSnapAlgorithm() {
- return new PipSnapAlgorithm();
- }
-
- @WMSingleton
- @Provides
- static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
- return new PhonePipKeepClearAlgorithm(context);
- }
-
- @WMSingleton
- @Provides
- static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
- PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
- PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
- return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
- pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
- }
-
// Handler is used by Icon.loadDrawableAsync
@WMSingleton
@Provides
@@ -213,13 +183,6 @@
@WMSingleton
@Provides
- static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
- pipSurfaceTransactionHelper) {
- return new PipAnimationController(pipSurfaceTransactionHelper);
- }
-
- @WMSingleton
- @Provides
static PipTransition providePipTransition(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -235,13 +198,6 @@
@WMSingleton
@Provides
- static SizeSpecSource provideSizeSpecSource(Context context,
- PipDisplayLayoutState pipDisplayLayoutState) {
- return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
- }
-
- @WMSingleton
- @Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index c4ca501..b42372b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -19,6 +19,7 @@
import android.content.Context;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import dagger.Module;
@@ -35,4 +36,11 @@
static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
return new PipSurfaceTransactionHelper(context);
}
+
+ @WMSingleton
+ @Provides
+ static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+ pipSurfaceTransactionHelper) {
+ return new PipAnimationController(pipSurfaceTransactionHelper);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8dec4ea..af97cf6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -16,11 +16,16 @@
package com.android.wm.shell.dagger.pip;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.PipTransition;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
@@ -33,8 +38,12 @@
public abstract class Pip2Module {
@WMSingleton
@Provides
- @Nullable
- static PipTransition providePipTransition() {
- return null;
+ static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ @NonNull ShellTaskOrganizer shellTaskOrganizer,
+ @NonNull Transitions transitions,
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
+ return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
+ pipBoundsAlgorithm);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 9c9364e..570f0a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.dagger.pip;
-import android.annotation.Nullable;
-
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.PipTransitionController;
@@ -38,8 +36,8 @@
@Provides
static PipTransitionController providePipTransitionController(
com.android.wm.shell.pip.PipTransition legacyPipTransition,
- @Nullable com.android.wm.shell.pip2.PipTransition newPipTransition) {
- if (PipUtils.isPip2ExperimentEnabled() && newPipTransition != null) {
+ com.android.wm.shell.pip2.PipTransition newPipTransition) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
return newPipTransition;
} else {
return legacyPipTransition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index a6ff9ec..a9675f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -30,16 +30,15 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -131,15 +130,9 @@
@WMSingleton
@Provides
- static PipSnapAlgorithm providePipSnapAlgorithm() {
- return new PipSnapAlgorithm();
- }
-
- @WMSingleton
- @Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+ PipDisplayLayoutState pipDisplayLayoutState, LegacySizeSpecSource sizeSpecSource) {
return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
pipDisplayLayoutState, sizeSpecSource);
}
@@ -147,13 +140,13 @@
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+ LegacySizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static SizeSpecSource provideSizeSpecSource(Context context,
+ static LegacySizeSpecSource provideSizeSpecSource(Context context,
PipDisplayLayoutState pipDisplayLayoutState) {
return new LegacySizeSpecSource(context, pipDisplayLayoutState);
}
@@ -200,13 +193,6 @@
@WMSingleton
@Provides
- static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
- pipSurfaceTransactionHelper) {
- return new PipAnimationController(pipSurfaceTransactionHelper);
- }
-
- @WMSingleton
- @Provides
static PipTransitionState providePipTransitionState() {
return new PipTransitionState();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ed9ff1c..9e8f9c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -82,6 +82,9 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.phone.PipMotionHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 83e03dc..e3922d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -64,6 +64,9 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -89,6 +92,7 @@
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
+ private final PipAnimationController mPipAnimationController;
private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private SurfaceControl.Transaction mFinishTransaction;
@@ -137,10 +141,11 @@
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController);
+ pipBoundsAlgorithm);
mContext = context;
mPipTransitionState = pipTransitionState;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipAnimationController = pipAnimationController;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -148,6 +153,13 @@
}
@Override
+ protected void onInit() {
+ if (!PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
if (destinationBounds != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 64bba67..20c57fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,7 +38,8 @@
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -52,7 +53,6 @@
*/
public abstract class PipTransitionController implements Transitions.TransitionHandler {
- protected final PipAnimationController mPipAnimationController;
protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
protected final PipBoundsState mPipBoundsState;
protected final ShellTaskOrganizer mShellTaskOrganizer;
@@ -135,22 +135,18 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
+ PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) {
mPipBoundsState = pipBoundsState;
mPipMenuController = pipMenuController;
mShellTaskOrganizer = shellTaskOrganizer;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
- mPipAnimationController = pipAnimationController;
mTransitions = transitions;
- if (!PipUtils.isPip2ExperimentEnabled()) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- shellInit.addInitCallback(this::onInit, this);
- }
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
}
}
- private void onInit() {
+ protected void onInit() {
mTransitions.addHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index cc182ba..7606526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -38,10 +38,10 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipMediaController.ActionListener;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index 8c2879e..118ad9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -15,7 +15,7 @@
*/
package com.android.wm.shell.pip.phone;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.content.Context;
@@ -36,8 +36,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index ddea574..1064867 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -76,7 +76,12 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -85,12 +90,7 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
index d7d335b..1b1ebc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 8f0a8e1..c708b86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -20,10 +20,10 @@
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -41,8 +41,8 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 4e687dd..e5f9fdc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -46,10 +46,11 @@
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index cf54a71..2ce4fb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -18,10 +18,10 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
@@ -50,12 +50,12 @@
import com.android.wm.shell.R;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index cd58ff4..a48e969f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,11 +36,11 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 8d4a384..8a215b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.pip.tv;
-import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index d11f4d5..2b3a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@
import android.view.Gravity;
import android.view.View;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 5f5d8ad..72115fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -48,11 +48,11 @@
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index a94bd6e..93f6826 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -21,12 +21,12 @@
import android.graphics.Rect
import android.util.Size
import android.view.Gravity
-import com.android.wm.shell.pip.PipBoundsState
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_LEFT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -54,11 +54,11 @@
* the unstash timeout if already stashed.
*/
data class Placement(
- val bounds: Rect,
- val anchorBounds: Rect,
- @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
- val unstashDestinationBounds: Rect? = null,
- val triggerStash: Boolean = false
+ val bounds: Rect,
+ val anchorBounds: Rect,
+ @PipBoundsState.StashType val stashType: Int = STASH_TYPE_NONE,
+ val unstashDestinationBounds: Rect? = null,
+ val triggerStash: Boolean = false
) {
/** Bounds to use if the PiP should not be stashed. */
fun getUnstashedBounds() = unstashDestinationBounds ?: bounds
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 6720804..f315afb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -25,12 +25,12 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d3253a5..f24b2b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -21,8 +21,8 @@
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTransition;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
index 8ab85d0..b8e4c04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
@@ -17,27 +17,65 @@
package com.android.wm.shell.pip2;
import android.annotation.NonNull;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
/** Placeholder, for demonstrate purpose only. */
-public abstract class PipTransition extends PipTransitionController {
+public class PipTransition extends PipTransitionController {
public PipTransition(
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PipAnimationController pipAnimationController) {
+ PipBoundsAlgorithm pipBoundsAlgorithm) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
- pipBoundsAlgorithm, pipAnimationController);
+ pipBoundsAlgorithm);
}
+
+ @Override
+ protected void onInit() {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ mTransitions.addHandler(this);
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return false;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index bf1b7f9..46259a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -33,6 +33,11 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 4341c4c..d34e27b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -36,6 +36,8 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
index b9226d2..ac13d7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipSnapAlgorithmTest.java
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 248d665..4e2b7f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,11 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
index cc9e26b..8c7b47e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithmTest.java
@@ -29,8 +29,9 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 024cba3..3d5cd69 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -33,8 +33,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import org.junit.After;
import org.junit.Assert;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 911f5e1..4eb5193 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,15 +55,16 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 8ce3ca4..0f8db85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -29,7 +29,7 @@
import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipBoundsState;
import org.junit.Assert;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 12b4f3e..6777a5b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,13 +37,13 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 314f195d..9aaabd1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,13 +33,13 @@
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
index 7370ed7..94f2b91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -25,7 +25,7 @@
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
import com.android.wm.shell.pip.tv.TvPipBoundsController.POSITION_DEBOUNCE_TIMEOUT_MILLIS
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 256610b..974539f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -27,9 +27,9 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
index aedf65d..998060f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithmTest.kt
@@ -22,10 +22,10 @@
import android.util.Size
import android.view.Gravity
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_BOTTOM
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT
-import com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_TOP
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_BOTTOM
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT
+import com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_TOP
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index b1ef4e5..5ffec34 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,12 +91,17 @@
StringPoolRef entry_string_ref;
};
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
- : configuration_(configuration) {
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
+ configurations_.push_back(configuration);
+
// Don't invalidate caches here as there's nothing cached yet.
SetApkAssets(apk_assets, false);
}
+AssetManager2::AssetManager2() {
+ configurations_.resize(1);
+}
+
bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
BuildDynamicRefTable(apk_assets);
RebuildFilterList();
@@ -421,9 +426,16 @@
return false;
}
-void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
- const int diff = configuration_.diff(configuration);
- configuration_ = configuration;
+void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
+ int diff = 0;
+ if (configurations_.size() != configurations.size()) {
+ diff = -1;
+ } else {
+ for (int i = 0; i < configurations_.size(); i++) {
+ diff |= configurations_[i].diff(configurations[i]);
+ }
+ }
+ configurations_ = std::move(configurations);
if (diff) {
RebuildFilterList();
@@ -620,16 +632,6 @@
auto op = StartOperation();
- // Might use this if density_override != 0.
- ResTable_config density_override_config;
-
- // Select our configuration or generate a density override configuration.
- const ResTable_config* desired_config = &configuration_;
- if (density_override != 0 && density_override != configuration_.density) {
- density_override_config = configuration_;
- density_override_config.density = density_override;
- desired_config = &density_override_config;
- }
// Retrieve the package group from the package id of the resource id.
if (UNLIKELY(!is_valid_resid(resid))) {
@@ -648,119 +650,160 @@
}
const PackageGroup& package_group = package_groups_[package_idx];
- auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
- stop_at_first_match, ignore_configuration);
- if (UNLIKELY(!result.has_value())) {
- return base::unexpected(result.error());
- }
+ std::optional<FindEntryResult> final_result;
+ bool final_has_locale = false;
+ bool final_overlaid = false;
+ for (auto & config : configurations_) {
+ // Might use this if density_override != 0.
+ ResTable_config density_override_config;
- bool overlaid = false;
- if (!stop_at_first_match && !ignore_configuration) {
- const auto& assets = GetApkAssets(result->cookie);
- if (!assets) {
- ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
- return base::unexpected(std::nullopt);
+ // Select our configuration or generate a density override configuration.
+ const ResTable_config* desired_config = &config;
+ if (density_override != 0 && density_override != config.density) {
+ density_override_config = config;
+ density_override_config.density = density_override;
+ desired_config = &density_override_config;
}
- if (!assets->IsLoader()) {
- for (const auto& id_map : package_group.overlays_) {
- auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
- if (!overlay_entry) {
- // No id map entry exists for this target resource.
- continue;
- }
- if (overlay_entry.IsInlineValue()) {
- // The target resource is overlaid by an inline value not represented by a resource.
- ConfigDescription best_frro_config;
- Res_value best_frro_value;
- bool frro_found = false;
- for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
- if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
- && config.match(*desired_config)) {
- frro_found = true;
- best_frro_config = config;
- best_frro_value = value;
- }
- }
- if (!frro_found) {
+
+ auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+ stop_at_first_match, ignore_configuration);
+ if (UNLIKELY(!result.has_value())) {
+ return base::unexpected(result.error());
+ }
+ bool overlaid = false;
+ if (!stop_at_first_match && !ignore_configuration) {
+ const auto& assets = GetApkAssets(result->cookie);
+ if (!assets) {
+ ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+ return base::unexpected(std::nullopt);
+ }
+ if (!assets->IsLoader()) {
+ for (const auto& id_map : package_group.overlays_) {
+ auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+ if (!overlay_entry) {
+ // No id map entry exists for this target resource.
continue;
}
- result->entry = best_frro_value;
+ if (overlay_entry.IsInlineValue()) {
+ // The target resource is overlaid by an inline value not represented by a resource.
+ ConfigDescription best_frro_config;
+ Res_value best_frro_value;
+ bool frro_found = false;
+ for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+ if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+ && config.match(*desired_config)) {
+ frro_found = true;
+ best_frro_config = config;
+ best_frro_value = value;
+ }
+ }
+ if (!frro_found) {
+ continue;
+ }
+ result->entry = best_frro_value;
+ result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+ result->cookie = id_map.cookie;
+
+ if (UNLIKELY(logging_enabled)) {
+ last_resolution_.steps.push_back(Resolution::Step{
+ Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+ if (auto path = assets->GetPath()) {
+ const std::string overlay_path = path->data();
+ if (IsFabricatedOverlay(overlay_path)) {
+ // FRRO don't have package name so we use the creating package here.
+ String8 frro_name = String8("FRRO");
+ // Get the first part of it since the expected one should be like
+ // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+ // under /data/resource-cache/.
+ const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+ const size_t end = name.find('-');
+ if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+ frro_name.append(base::StringPrintf(" created by %s",
+ name.substr(0 /* pos */,
+ end).c_str()).c_str());
+ }
+ last_resolution_.best_package_name = frro_name;
+ } else {
+ last_resolution_.best_package_name = result->package_name->c_str();
+ }
+ }
+ overlaid = true;
+ }
+ continue;
+ }
+
+ auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+ false /* stop_at_first_match */,
+ false /* ignore_configuration */);
+ if (UNLIKELY(IsIOError(overlay_result))) {
+ return base::unexpected(overlay_result.error());
+ }
+ if (!overlay_result.has_value()) {
+ continue;
+ }
+
+ if (!overlay_result->config.isBetterThan(result->config, desired_config)
+ && overlay_result->config.compare(result->config) != 0) {
+ // The configuration of the entry for the overlay must be equal to or better than the
+ // target configuration to be chosen as the better value.
+ continue;
+ }
+
+ result->cookie = overlay_result->cookie;
+ result->entry = overlay_result->entry;
+ result->config = overlay_result->config;
result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
- result->cookie = id_map.cookie;
if (UNLIKELY(logging_enabled)) {
last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
- if (auto path = assets->GetPath()) {
- const std::string overlay_path = path->data();
- if (IsFabricatedOverlay(overlay_path)) {
- // FRRO don't have package name so we use the creating package here.
- String8 frro_name = String8("FRRO");
- // Get the first part of it since the expected one should be like
- // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
- // under /data/resource-cache/.
- const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
- const size_t end = name.find('-');
- if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
- frro_name.append(base::StringPrintf(" created by %s",
- name.substr(0 /* pos */,
- end).c_str()).c_str());
- }
- last_resolution_.best_package_name = frro_name;
- } else {
- last_resolution_.best_package_name = result->package_name->c_str();
- }
- }
+ Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+ overlay_result->config.toString()});
+ last_resolution_.best_package_name =
+ overlay_result->package_name->c_str();
overlaid = true;
}
- continue;
- }
-
- auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
- false /* stop_at_first_match */,
- false /* ignore_configuration */);
- if (UNLIKELY(IsIOError(overlay_result))) {
- return base::unexpected(overlay_result.error());
- }
- if (!overlay_result.has_value()) {
- continue;
- }
-
- if (!overlay_result->config.isBetterThan(result->config, desired_config)
- && overlay_result->config.compare(result->config) != 0) {
- // The configuration of the entry for the overlay must be equal to or better than the target
- // configuration to be chosen as the better value.
- continue;
- }
-
- result->cookie = overlay_result->cookie;
- result->entry = overlay_result->entry;
- result->config = overlay_result->config;
- result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-
- if (UNLIKELY(logging_enabled)) {
- last_resolution_.steps.push_back(
- Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
- overlay_result->config.toString()});
- last_resolution_.best_package_name =
- overlay_result->package_name->c_str();
- overlaid = true;
}
}
}
+
+ bool has_locale = false;
+ if (result->config.locale == 0) {
+ if (default_locale_ != 0) {
+ ResTable_config conf;
+ conf.locale = default_locale_;
+ // Since we know conf has a locale and only a locale, match will tell us if that locale
+ // matches
+ has_locale = conf.match(config);
+ }
+ } else {
+ has_locale = true;
+ }
+
+ // if we don't have a result yet
+ if (!final_result ||
+ // or this config is better before the locale than the existing result
+ result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
+ // or the existing config isn't better before locale and this one specifies a locale
+ // whereas the existing one doesn't
+ (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
+ && has_locale && !final_has_locale)) {
+ final_result = result.value();
+ final_overlaid = overlaid;
+ final_has_locale = has_locale;
+ }
}
if (UNLIKELY(logging_enabled)) {
- last_resolution_.cookie = result->cookie;
- last_resolution_.type_string_ref = result->type_string_ref;
- last_resolution_.entry_string_ref = result->entry_string_ref;
- last_resolution_.best_config_name = result->config.toString();
- if (!overlaid) {
- last_resolution_.best_package_name = result->package_name->c_str();
+ last_resolution_.cookie = final_result->cookie;
+ last_resolution_.type_string_ref = final_result->type_string_ref;
+ last_resolution_.entry_string_ref = final_result->entry_string_ref;
+ last_resolution_.best_config_name = final_result->config.toString();
+ if (!final_overlaid) {
+ last_resolution_.best_package_name = final_result->package_name->c_str();
}
}
- return result;
+ return *final_result;
}
base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -778,8 +821,10 @@
// If `desired_config` is not the same as the set configuration or the caller will accept a value
// from any configuration, then we cannot use our filtered list of types since it only it contains
// types matched to the set configuration.
- const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
-
+ const bool use_filtered = !ignore_configuration && std::find_if(
+ configurations_.begin(), configurations_.end(),
+ [&desired_config](auto& value) { return &desired_config == &value; })
+ != configurations_.end();
const size_t package_count = package_group.packages_.size();
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -934,10 +979,22 @@
}
std::stringstream log_stream;
- log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
- "\tFor config - %s", resid, resource_name_string.c_str(),
- configuration_.toString().c_str());
-
+ if (configurations_.size() == 1) {
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+ "\tFor config - %s", resid, resource_name_string.c_str(),
+ configurations_[0].toString().c_str());
+ } else {
+ ResTable_config conf = configurations_[0];
+ conf.clearLocale();
+ log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
+ resid, resource_name_string.c_str(), conf.toString().c_str());
+ char str[40];
+ str[0] = '\0';
+ for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
+ iter->getBcp47Locale(str);
+ log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
+ }
+ }
for (const Resolution::Step& step : last_resolution_.steps) {
constexpr static std::array kStepStrings = {
"Found initial",
@@ -1427,11 +1484,14 @@
package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
- if (type_entry.config.match(configuration_)) {
- if (!group) {
- group = &package.filtered_configs_.editItemAt(type_id - 1);
+ for (auto & config : configurations_) {
+ if (type_entry.config.match(config)) {
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
+ }
+ group->type_entries.push_back(&type_entry);
+ break;
}
- group->type_entries.push_back(&type_entry);
}
}
});
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 112058f..ec14316 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2568,6 +2568,22 @@
return false;
}
+bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+ }
+ return false;
+}
+
bool ResTable_config::isBetterThan(const ResTable_config& o,
const ResTable_config* requested) const {
if (requested) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index f611d0d..d9ff35b 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
using ApkAssetsWPtr = wp<const ApkAssets>;
using ApkAssetsList = std::span<const ApkAssetsPtr>;
- AssetManager2() = default;
+ AssetManager2();
explicit AssetManager2(AssetManager2&& other) = default;
AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
@@ -156,10 +156,14 @@
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
- void SetConfiguration(const ResTable_config& configuration);
+ void SetConfigurations(std::vector<ResTable_config> configurations);
- inline const ResTable_config& GetConfiguration() const {
- return configuration_;
+ inline const std::vector<ResTable_config>& GetConfigurations() const {
+ return configurations_;
+ }
+
+ inline void SetDefaultLocale(uint32_t default_locale) {
+ default_locale_ = default_locale;
}
// Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -465,9 +469,11 @@
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
- // The current configuration set for this AssetManager. When this changes, cached resources
+ uint32_t default_locale_;
+
+ // The current configurations set for this AssetManager. When this changes, cached resources
// may need to be purged.
- ResTable_config configuration_ = {};
+ std::vector<ResTable_config> configurations_;
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 6de1d1e..fdb3551 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,6 +1375,8 @@
// match the requested configuration at all.
bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+ bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
+
String8 toString() const;
};
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 6fae72a..2caa98c 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,10 +228,12 @@
ResTable_config config;
memset(&config, 0, sizeof(config));
+ std::vector<ResTable_config> configs;
+ configs.push_back(config);
while (state.KeepRunning()) {
- config.sdkVersion = ~config.sdkVersion;
- assets.SetConfiguration(config);
+ configs[0].sdkVersion = ~configs[0].sdkVersion;
+ assets.SetConfigurations(configs);
}
}
BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index df3fa02..c62f095 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
desired_config.language[1] = 'e';
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
TEST_F(AssetManager2Test, DensityOverride) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
- assetmanager.SetConfiguration({
+ assetmanager.SetConfigurations({{
.density = ResTable_config::DENSITY_XHIGH,
.sdkVersion = 21,
- });
+ }});
auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
assetmanager.SetResourceResolutionLoggingEnabled(false);
@@ -736,7 +736,7 @@
ResTable_config desired_config;
AssetManager2 assetmanager;
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({basic_assets_});
auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
AssetManager2 assetmanager;
assetmanager.SetResourceResolutionLoggingEnabled(true);
- assetmanager.SetConfiguration(desired_config);
+ assetmanager.SetConfigurations({desired_config});
assetmanager.SetApkAssets({overlayable_assets_});
const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index b97dd96..8b883f4 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
AssetManager2 assetmanager;
assetmanager.SetApkAssets(apk_assets);
if (config != nullptr) {
- assetmanager.SetConfiguration(*config);
+ assetmanager.SetConfigurations({*config});
}
while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index e08a6a7..181d141 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
ResTable_config night{};
night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
night.version = 8u;
- am_night.SetConfiguration(night);
+ am_night.SetConfigurations({night});
auto theme = am.NewTheme();
{
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index a17f2f7..7aef7a5 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -203,10 +203,9 @@
if (advances) {
advancesArray.reset(new jfloat[count]);
}
- minikin::MinikinRect bounds;
const float advance = MinikinUtils::measureText(
paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
- contextCount, advancesArray.get(), &bounds);
+ contextCount, advancesArray.get(), nullptr);
if (advances) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
}
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index b50514d..283445f 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -36,7 +36,7 @@
void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
- ResTable_config config = locked_mgr->GetConfiguration();
+ ResTable_config config = locked_mgr->GetConfigurations()[0];
// AConfiguration is not a virtual subclass, so we can memcpy.
memcpy(out, &config, sizeof(config));
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2d62e2a..8787c25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -217,6 +217,7 @@
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 4494765..dfc3cef 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -312,6 +312,7 @@
VALIDATORS.put(
Secure.ACCESSIBILITY_BUTTON_TARGETS,
ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d2b444b..7186aba 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1842,6 +1842,10 @@
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ SecureSettingsProto.Accessibility
+ .ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index c865070..ffb20d8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -22,7 +22,9 @@
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.motionEventSpy
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
@@ -56,6 +58,7 @@
* must have entries in this map.
* @param modifier A modifier.
*/
+@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SceneContainer(
viewModel: SceneContainerViewModel,
@@ -79,7 +82,7 @@
onChangeScene = viewModel::onSceneChanged,
transitions = SceneContainerTransitions,
state = state,
- modifier = modifier.fillMaxSize(),
+ modifier = modifier.fillMaxSize().motionEventSpy { viewModel.onUserInput() },
) {
sceneByKey.forEach { (sceneKey, composableScene) ->
scene(
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 909048e..b00908f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -47,7 +47,7 @@
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:layout_marginStart="16dp"
- app:layout_constraintVertical_bias="0.0"
+ app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -65,7 +65,6 @@
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
- app:layout_constraintVertical_bias="1.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_text"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b23e085..a368703 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -586,16 +586,18 @@
if (shouldTryToDismissKeyguard()) {
tryDismissingKeyguard();
}
- onFingerDown(requestId,
- data.getPointerId(),
- data.getX(),
- data.getY(),
- data.getMinor(),
- data.getMajor(),
- data.getOrientation(),
- data.getTime(),
- data.getGestureStart(),
- mStatusBarStateController.isDozing());
+ if (!mOnFingerDown) {
+ onFingerDown(requestId,
+ data.getPointerId(),
+ data.getX(),
+ data.getY(),
+ data.getMinor(),
+ data.getMajor(),
+ data.getOrientation(),
+ data.getTime(),
+ data.getGestureStart(),
+ mStatusBarStateController.isDozing());
+ }
// Pilfer if valid overlap, don't allow following events to reach keyguard
shouldPilfer = true;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e9a539f..6b578ba 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -370,6 +370,9 @@
// 600- status bar
+ // TODO(b/291315866): Tracking Bug
+ @JvmField val SIGNAL_CALLBACK_DEPRECATION = unreleasedFlag("signal_callback_deprecation")
+
// TODO(b/265892345): Tracking Bug
val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
@@ -654,6 +657,10 @@
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag("warn_on_blocking_binder_transactions")
+ @JvmField
+ val COROUTINE_TRACING =
+ unreleasedFlag("coroutine_tracing")
+
// TODO(b/283071711): Tracking bug
@JvmField
val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index 69cb611..b2a8719 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -39,6 +39,9 @@
/** Wakes up the device. */
fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
+
+ /** Notifies the power repository that a user touch happened. */
+ fun userTouch()
}
@SysUISingleton
@@ -83,6 +86,14 @@
)
}
+ override fun userTouch() {
+ manager.userActivity(
+ systemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+ /* flags= */ 0,
+ )
+ }
+
companion object {
private const val TAG = "PowerRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 54376fe..e9a2428 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -18,6 +18,8 @@
import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION;
+
import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Intent;
@@ -42,6 +44,7 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -54,6 +57,8 @@
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -61,6 +66,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -79,6 +85,9 @@
private final NetworkController mNetworkController;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final Callback mCallback = new Callback();
+ private final WifiInteractor mWifiInteractor;
+ private final TileJavaAdapter mJavaAdapter;
+ private final FeatureFlags mFeatureFlags;
private boolean mWifiConnected;
private boolean mHotspotConnected;
@@ -97,7 +106,10 @@
KeyguardStateController keyguardStateController,
NetworkController networkController,
HotspotController hotspotController,
- DialogLaunchAnimator dialogLaunchAnimator
+ DialogLaunchAnimator dialogLaunchAnimator,
+ WifiInteractor wifiInteractor,
+ TileJavaAdapter javaAdapter,
+ FeatureFlags featureFlags
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -105,9 +117,16 @@
mKeyguard = keyguardStateController;
mNetworkController = networkController;
mDialogLaunchAnimator = dialogLaunchAnimator;
+ mWifiInteractor = wifiInteractor;
+ mJavaAdapter = javaAdapter;
+ mFeatureFlags = featureFlags;
mController.observe(this, mCallback);
mKeyguard.observe(this, mCallback);
- mNetworkController.observe(this, mSignalCallback);
+ if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
+ mNetworkController.observe(this, mSignalCallback);
+ } else {
+ mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
+ }
hotspotController.observe(this, mHotspotCallback);
}
@@ -293,19 +312,37 @@
return mWifiConnected || mHotspotConnected;
}
+ private void setWifiConnected(boolean connected) {
+ if (connected != mWifiConnected) {
+ mWifiConnected = connected;
+ // Hotspot is not connected, so changes here should update
+ if (!mHotspotConnected) {
+ refreshState();
+ }
+ }
+ }
+
+ private void setHotspotConnected(boolean connected) {
+ if (connected != mHotspotConnected) {
+ mHotspotConnected = connected;
+ // Wifi is not connected, so changes here should update
+ if (!mWifiConnected) {
+ refreshState();
+ }
+ }
+ }
+
+ private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
+ setWifiConnected(model instanceof WifiNetworkModel.Active);
+ };
+
private final SignalCallback mSignalCallback = new SignalCallback() {
@Override
public void setWifiIndicators(@NonNull WifiIndicators indicators) {
// statusIcon.visible has the connected status information
boolean enabledAndConnected = indicators.enabled
- && (indicators.qsIcon == null ? false : indicators.qsIcon.visible);
- if (enabledAndConnected != mWifiConnected) {
- mWifiConnected = enabledAndConnected;
- // Hotspot is not connected, so changes here should update
- if (!mHotspotConnected) {
- refreshState();
- }
- }
+ && (indicators.qsIcon != null && indicators.qsIcon.visible);
+ setWifiConnected(enabledAndConnected);
}
};
@@ -314,13 +351,7 @@
@Override
public void onHotspotChanged(boolean enabled, int numDevices) {
boolean enabledAndConnected = enabled && numDevices > 0;
- if (enabledAndConnected != mHotspotConnected) {
- mHotspotConnected = enabledAndConnected;
- // Wifi is not connected, so changes here should update
- if (!mWifiConnected) {
- refreshState();
- }
- }
+ setHotspotConnected(enabledAndConnected);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
new file mode 100644
index 0000000..3b2f8b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.tiles
+
+import android.content.Context
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.view.View
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.AlphaControlledSignalTileView
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel
+import javax.inject.Inject
+
+class InternetTileNewImpl
+@Inject
+constructor(
+ host: QSHost,
+ uiEventLogger: QsEventLogger,
+ @Background backgroundLooper: Looper,
+ @Main private val mainHandler: Handler,
+ falsingManager: FalsingManager,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ viewModel: InternetTileViewModel,
+ private val internetDialogFactory: InternetDialogFactory,
+ private val accessPointController: AccessPointController,
+) :
+ QSTileImpl<QSTile.SignalState>(
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+ ) {
+ private var model: InternetTileModel = viewModel.tileModel.value
+
+ init {
+ InternetTileBinder.bind(lifecycle, viewModel.tileModel) { newModel ->
+ model = newModel
+ refreshState()
+ }
+ }
+
+ override fun createTileView(context: Context): QSIconView =
+ AlphaControlledSignalTileView(context)
+
+ override fun getTileLabel(): CharSequence =
+ mContext.getString(R.string.quick_settings_internet_label)
+
+ override fun newTileState(): QSTile.SignalState {
+ return QSTile.SignalState().also { it.forceExpandIcon = true }
+ }
+
+ override fun handleClick(view: View?) {
+ mainHandler.post {
+ internetDialogFactory.create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ view,
+ )
+ }
+ }
+
+ override fun handleUpdateState(state: QSTile.SignalState, arg: Any?) {
+ state.label = mContext.resources.getString(R.string.quick_settings_internet_label)
+
+ model.applyTo(state, mContext)
+ }
+
+ override fun getLongClickIntent(): Intent = WIFI_SETTINGS
+
+ companion object {
+ private val WIFI_SETTINGS = Intent(Settings.ACTION_WIFI_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt
new file mode 100644
index 0000000..a2430ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/TileJavaAdapter.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.tiles
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * Utility for binding tiles to kotlin flows. Similar to [JavaAdapter] and usable for QS tiles. We
+ * use [Lifecycle.State.RESUMED] here to match the implementation of [CallbackController.observe]
+ */
+@SysUISingleton
+class TileJavaAdapter @Inject constructor() {
+ fun <T> bind(
+ lifecycleOwner: LifecycleOwner,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+ ) {
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ flow.collect { consumer.accept(it) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index a7434c6..342c218 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.power.data.repository.PowerRepository
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -44,6 +45,7 @@
constructor(
@Application applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
+ private val powerRepository: PowerRepository,
private val logger: SceneLogger,
) {
@@ -153,6 +155,11 @@
repository.setTransitionState(transitionState)
}
+ /** Handles a user input event. */
+ fun onUserInput() {
+ powerRepository.userTouch()
+ }
+
/**
* Notifies that the UI has transitioned sufficiently to the given scene.
*
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 5c16fb5..c5b605a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -63,6 +63,11 @@
interactor.setTransitionState(transitionState)
}
+ /** Handles an event representing user input. */
+ fun onUserInput() {
+ interactor.onUserInput()
+ }
+
companion object {
private const val SCENE_TRANSITION_LOGGING_REASON = "user input"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 92aa986..b46b525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.connectivity
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.AirplaneModeTile
import com.android.systemui.qs.tiles.BluetoothTile
@@ -23,21 +25,17 @@
import com.android.systemui.qs.tiles.DataSaverTile
import com.android.systemui.qs.tiles.HotspotTile
import com.android.systemui.qs.tiles.InternetTile
+import com.android.systemui.qs.tiles.InternetTileNewImpl
import com.android.systemui.qs.tiles.NfcTile
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@Module
interface ConnectivityModule {
- /** Inject InternetTile into tileMap in QSModule */
- @Binds
- @IntoMap
- @StringKey(InternetTile.TILE_SPEC)
- fun bindInternetTile(internetTile: InternetTile): QSTileImpl<*>
-
/** Inject BluetoothTile into tileMap in QSModule */
@Binds
@IntoMap
@@ -70,4 +68,21 @@
/** Inject NfcTile into tileMap in QSModule */
@Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*>
+
+ companion object {
+ /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(InternetTile.TILE_SPEC)
+ fun bindInternetTile(
+ internetTile: InternetTile,
+ newInternetTile: InternetTileNewImpl,
+ featureFlags: FeatureFlags,
+ ): QSTileImpl<*> =
+ if (featureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
+ newInternetTile
+ } else {
+ internetTile
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
new file mode 100644
index 0000000..72a5cfe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Intent
+import androidx.lifecycle.LifecycleRegistry
+import com.android.keyguard.AuthKeyguardMessageArea
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.navigationbar.NavigationBarView
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.qs.QSPanelController
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import java.io.PrintWriter
+
+/**
+ * Empty implementation of [CentralSurfaces] for variants only need to override portions of the
+ * interface.
+ *
+ * **Important**: Prefer binding an Optional<CentralSurfaces> to an empty optional instead of
+ * including this class.
+ */
+abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
+ override val lifecycle = LifecycleRegistry(this)
+ override fun updateIsKeyguard() = false
+ override fun updateIsKeyguard(forceStateChange: Boolean) = false
+ override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
+ override fun isLaunchingActivityOverLockscreen() = false
+ override fun onKeyguardViewManagerStatesUpdated() {}
+ override fun isPulsing() = false
+ override fun isOccluded() = false
+ override fun isDeviceInVrMode() = false
+ override fun getPresenter(): NotificationPresenter? = null
+ override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {}
+ override fun getCommandQueuePanelsEnabled() = false
+ override fun getBiometricUnlockController(): BiometricUnlockController? = null
+ override fun showWirelessChargingAnimation(batteryLevel: Int) {}
+ override fun checkBarModes() {}
+ override fun updateBubblesVisibility() {}
+ override fun setInteracting(barWindow: Int, interacting: Boolean) {}
+ override fun dump(pwOriginal: PrintWriter, args: Array<String>) {}
+ override fun getDisplayWidth() = 0f
+ override fun getDisplayHeight() = 0f
+ override fun readyForKeyguardDone() {}
+ override fun showKeyguard() {}
+ override fun hideKeyguard() = false
+ override fun showKeyguardImpl() {}
+ override fun fadeKeyguardAfterLaunchTransition(
+ beforeFading: Runnable?,
+ endRunnable: Runnable?,
+ cancelRunnable: Runnable?,
+ ) {}
+ override fun startLaunchTransitionTimeout() {}
+ override fun hideKeyguardImpl(forceStateChange: Boolean) = false
+ override fun keyguardGoingAway() {}
+ override fun setKeyguardFadingAway(startTime: Long, delay: Long, fadeoutDuration: Long) {}
+ override fun finishKeyguardFadingAway() {}
+ override fun userActivity() {}
+ override fun endAffordanceLaunch() {}
+ override fun shouldKeyguardHideImmediately() = false
+ override fun showBouncerWithDimissAndCancelIfKeyguard(
+ performAction: OnDismissAction?,
+ cancelAction: Runnable?,
+ ) {}
+ override fun getNavigationBarView(): NavigationBarView? = null
+ override fun isOverviewEnabled() = false
+ override fun showPinningEnterExitToast(entering: Boolean) {}
+ override fun showPinningEscapeToast() {}
+ override fun setBouncerShowing(bouncerShowing: Boolean) {}
+ override fun getWakefulnessState() = 0
+ override fun isScreenFullyOff() = false
+ override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {}
+ override fun getEmergencyActionIntent(): Intent? = null
+ override fun isCameraAllowedByAdmin() = false
+ override fun isGoingToSleep() = false
+ override fun notifyBiometricAuthModeChanged() {}
+ override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
+ override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
+ override fun updateScrimController() {}
+ override fun isKeyguardShowing() = false
+ override fun shouldIgnoreTouch() = false
+ override fun isDeviceInteractive() = false
+ override fun awakenDreams() {}
+ override fun clearNotificationEffects() {}
+ override fun isBouncerShowing() = false
+ override fun isBouncerShowingScrimmed() = false
+ override fun isBouncerShowingOverDream() = false
+ override fun isKeyguardSecure() = false
+ override fun updateNotificationPanelTouchState() {}
+ override fun getRotation() = 0
+ override fun setBarStateForTest(state: Int) {}
+ override fun showTransientUnchecked() {}
+ override fun clearTransient() {}
+ override fun acquireGestureWakeLock(time: Long) {}
+ override fun setAppearance(appearance: Int) = false
+ override fun getBarMode() = 0
+ override fun resendMessage(msg: Int) {}
+ override fun resendMessage(msg: Any?) {}
+ override fun setLastCameraLaunchSource(source: Int) {}
+ override fun setLaunchCameraOnFinishedGoingToSleep(launch: Boolean) {}
+ override fun setLaunchCameraOnFinishedWaking(launch: Boolean) {}
+ override fun setLaunchEmergencyActionOnFinishedGoingToSleep(launch: Boolean) {}
+ override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {}
+ override fun getQSPanelController(): QSPanelController? = null
+ override fun getDisplayDensity() = 0f
+ override fun extendDozePulse() {}
+ override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {}
+ override fun getAnimatorControllerFromNotification(
+ associatedView: ExpandableNotificationRow?,
+ ): ActivityLaunchAnimator.Controller? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt
new file mode 100644
index 0000000..3709e4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.statusbar.pipeline.ethernet.domain
+
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Currently we don't do much to interact with ethernet. We simply need a place to map between the
+ * connectivity state of a default ethernet connection, and an icon representing that connection.
+ */
+@SysUISingleton
+class EthernetInteractor
+@Inject
+constructor(
+ connectivityRepository: ConnectivityRepository,
+) {
+ /** Icon representing the current connectivity status of the ethernet connection */
+ val icon: Flow<Icon.Resource?> =
+ connectivityRepository.defaultConnections.map {
+ if (it.ethernet.isDefault) {
+ if (it.isValidated) {
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet_fully,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[1]
+ )
+ )
+ } else {
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0]
+ )
+ )
+ }
+ } else {
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 051e88f..02473f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -16,9 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data
+import android.content.Intent
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.dagger.SysUISingleton
@@ -162,6 +164,28 @@
fun logOnSubscriptionsChanged() {
buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" })
}
+
+ fun logServiceProvidersUpdatedBroadcast(intent: Intent) {
+ val showSpn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_SPN, false)
+ val spn = intent.getStringExtra(TelephonyManager.EXTRA_DATA_SPN)
+ val showPlmn = intent.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)
+ val plmn = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
+
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ bool1 = showSpn
+ str1 = spn
+ bool2 = showPlmn
+ str2 = plmn
+ },
+ {
+ "Intent: ACTION_SERVICE_PROVIDERS_UPDATED." +
+ " showSpn=$bool1 spn=$str1 showPlmn=$bool2 plmn=$str2"
+ }
+ )
+ }
}
private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 1f1ac92..cd68621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
@@ -34,6 +38,7 @@
import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
@@ -81,6 +86,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
override val subId: Int,
+ private val context: Context,
subscriptionModel: StateFlow<SubscriptionModel?>,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
@@ -323,16 +329,35 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId)
+ /** BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here */
+ @SuppressLint("RegisterReceiverViaContext")
override val networkName: StateFlow<NetworkNameModel> =
- broadcastDispatcher
- .broadcastFlow(
- filter = IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
- map = { intent, _ -> intent },
- )
- .filter { intent ->
- intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+ conflatedCallbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (
+ intent.getIntExtra(
+ EXTRA_SUBSCRIPTION_INDEX,
+ INVALID_SUBSCRIPTION_ID
+ ) == subId
+ ) {
+ logger.logServiceProvidersUpdatedBroadcast(intent)
+ trySend(
+ intent.toNetworkNameModel(networkNameSeparator)
+ ?: defaultNetworkName
+ )
+ }
+ }
+ }
+
+ context.registerReceiver(
+ receiver,
+ IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
}
- .map { intent -> intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName }
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
override val dataEnabled = run {
@@ -349,6 +374,7 @@
class Factory
@Inject
constructor(
+ private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val telephonyManager: TelephonyManager,
private val logger: MobileInputLogger,
@@ -366,6 +392,7 @@
): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
subId,
+ context,
subscriptionModel,
defaultNetworkName,
networkNameSeparator,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4cfde5b..4bf297c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.content.Context
-import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
import com.android.systemui.dagger.qualifiers.Application
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,24 +61,17 @@
*/
val isDataConnected: StateFlow<Boolean>
- /** Only true if mobile is the default transport but is not validated, otherwise false */
- val isDefaultConnectionFailed: StateFlow<Boolean>
-
/** True if we consider this connection to be in service, i.e. can make calls */
val isInService: StateFlow<Boolean>
- // TODO(b/256839546): clarify naming of default vs active
- /** True if we want to consider the data connection enabled */
- val isDefaultDataEnabled: StateFlow<Boolean>
-
/** Observable for the data enabled state of this connection */
val isDataEnabled: StateFlow<Boolean>
/** True if the RAT icon should always be displayed and false otherwise. */
val alwaysShowDataRatIcon: StateFlow<Boolean>
- /** True if the CDMA level should be preferred over the primary level. */
- val alwaysUseCdmaLevel: StateFlow<Boolean>
+ /** Canonical representation of the current mobile signal strength as a triangle. */
+ val signalLevelIcon: StateFlow<SignalIconModel>
/** Observable for RAT type (network type) indicator */
val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
@@ -108,9 +102,6 @@
/** True if there is only one active subscription. */
val isSingleCarrier: StateFlow<Boolean>
- /** True if this line of service is emergency-only */
- val isEmergencyOnly: StateFlow<Boolean>
-
/**
* True if this connection is considered roaming. The roaming bit can come from [ServiceState],
* or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
@@ -118,12 +109,6 @@
*/
val isRoaming: StateFlow<Boolean>
- /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
- val level: StateFlow<Int>
-
- /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
- val numberOfLevels: StateFlow<Int>
-
/** See [MobileIconsInteractor.isForceHidden]. */
val isForceHidden: Flow<Boolean>
@@ -141,12 +126,12 @@
@Application scope: CoroutineScope,
defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
override val alwaysShowDataRatIcon: StateFlow<Boolean>,
- override val alwaysUseCdmaLevel: StateFlow<Boolean>,
+ alwaysUseCdmaLevel: StateFlow<Boolean>,
override val isSingleCarrier: StateFlow<Boolean>,
override val mobileIsDefault: StateFlow<Boolean>,
defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
- override val isDefaultConnectionFailed: StateFlow<Boolean>,
+ isDefaultConnectionFailed: StateFlow<Boolean>,
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
private val context: Context,
@@ -170,8 +155,6 @@
.distinctUntilChanged()
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
-
override val networkName =
combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
operatorAlphaShort,
@@ -255,8 +238,6 @@
DefaultIcon(defaultMobileIconGroup.value),
)
- override val isEmergencyOnly = connectionRepository.isEmergencyOnly
-
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -274,7 +255,7 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val level: StateFlow<Int> =
+ private val level: StateFlow<Int> =
combine(
connectionRepository.isGsm,
connectionRepository.primaryLevel,
@@ -290,7 +271,7 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- override val numberOfLevels: StateFlow<Int> =
+ private val numberOfLevels: StateFlow<Int> =
connectionRepository.numberOfLevels.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -305,4 +286,54 @@
override val isInService = connectionRepository.isInService
override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
+
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: StateFlow<Boolean> =
+ combine(
+ defaultSubscriptionHasDataEnabled,
+ isDefaultConnectionFailed,
+ isInService,
+ ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+ private val shownLevel: StateFlow<Int> =
+ combine(
+ level,
+ isInService,
+ ) { level, isInService ->
+ if (isInService) level else 0
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
+ override val signalLevelIcon: StateFlow<SignalIconModel> = run {
+ val initial =
+ SignalIconModel(
+ level = shownLevel.value,
+ numberOfLevels = numberOfLevels.value,
+ showExclamationMark = showExclamationMark.value,
+ carrierNetworkChange = carrierNetworkChangeActive.value,
+ )
+ combine(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChangeActive,
+ ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChange,
+ )
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "icon",
+ initialValue = initial,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index d08808b..62150e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker
+import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -70,6 +71,12 @@
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+ /**
+ * Flow providing a reference to the Interactor for the active data subId. This represents the
+ * [MobileConnectionInteractor] responsible for the active data connection, if any.
+ */
+ val activeDataIconInteractor: StateFlow<MobileIconInteractor?>
+
/** True if the RAT icon should always be displayed and false otherwise. */
val alwaysShowDataRatIcon: StateFlow<Boolean>
@@ -96,9 +103,9 @@
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
- * subId. Will throw if the ID is invalid
+ * subId.
*/
- fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+ fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -116,6 +123,9 @@
private val context: Context,
) : MobileIconsInteractor {
+ // Weak reference lookup for created interactors
+ private val reuseCache = mutableMapOf<Int, WeakReference<MobileIconInteractor>>()
+
override val mobileIsDefault =
combine(
mobileConnectionsRepo.mobileIsDefault,
@@ -138,6 +148,17 @@
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> =
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
+ .mapLatest {
+ if (it != null) {
+ getMobileConnectionInteractorForSubId(it)
+ } else {
+ null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
mobileConnectionsRepo.subscriptions
@@ -306,21 +327,25 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
/** Vends out new [MobileIconInteractor] for a particular subId */
- override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId)
+
+ private fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
- scope,
- activeDataConnectionHasDataEnabled,
- alwaysShowDataRatIcon,
- alwaysUseCdmaLevel,
- isSingleCarrier,
- mobileIsDefault,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- isDefaultConnectionFailed,
- isForceHidden,
- mobileConnectionsRepo.getRepoForSubId(subId),
- context,
- )
+ scope,
+ activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel,
+ isSingleCarrier,
+ mobileIsDefault,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ isDefaultConnectionFailed,
+ isForceHidden,
+ mobileConnectionsRepo.getRepoForSubId(subId),
+ context,
+ )
+ .also { reuseCache[subId] = WeakReference(it) }
companion object {
private const val LOGGING_PREFIX = "Intr"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
index 6de3f85..e58f081 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.ui.model
+package com.android.systemui.statusbar.pipeline.mobile.domain.model
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.log.table.Diffable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index cffc833..a1a5370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -22,8 +22,8 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 275cfc5..dfabeea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -17,14 +17,13 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
-import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
@@ -76,26 +75,6 @@
constants: ConnectivityConstants,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
- /** Whether or not to show the error state of [SignalDrawable] */
- private val showExclamationMark: StateFlow<Boolean> =
- combine(
- iconInteractor.isDefaultDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
- iconInteractor.isInService,
- ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
- !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), true)
-
- private val shownLevel: StateFlow<Int> =
- combine(
- iconInteractor.level,
- iconInteractor.isInService,
- ) { level, isInService ->
- if (isInService) level else 0
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
-
override val isVisible: StateFlow<Boolean> =
if (!constants.hasDataCapabilities) {
flowOf(false)
@@ -123,40 +102,12 @@
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val icon: Flow<SignalIconModel> = run {
- val initial =
- SignalIconModel(
- level = shownLevel.value,
- numberOfLevels = iconInteractor.numberOfLevels.value,
- showExclamationMark = showExclamationMark.value,
- carrierNetworkChange = iconInteractor.carrierNetworkChangeActive.value,
- )
- combine(
- shownLevel,
- iconInteractor.numberOfLevels,
- showExclamationMark,
- iconInteractor.carrierNetworkChangeActive,
- ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
- SignalIconModel(
- shownLevel,
- numberOfLevels,
- showExclamationMark,
- carrierNetworkChange,
- )
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- iconInteractor.tableLogBuffer,
- columnPrefix = "icon",
- initialValue = initial,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon
override val contentDescription: Flow<ContentDescription> = run {
val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[0])
- shownLevel
- .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it]) }
+ iconInteractor.signalLevelIcon
+ .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it.level]) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 216afb9..a4ec3a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -101,7 +101,7 @@
val common = commonViewModelForSub(subId)
return LocationBasedMobileViewModel.viewModelForLocation(
common,
- mobileIconInteractorForSub(subId),
+ interactor.getMobileConnectionInteractorForSubId(subId),
verboseLogger,
location,
scope,
@@ -112,7 +112,7 @@
return mobileIconSubIdCache[subId]
?: MobileIconViewModel(
subId,
- mobileIconInteractorForSub(subId),
+ interactor.getMobileConnectionInteractorForSubId(subId),
airplaneModeInteractor,
constants,
scope,
@@ -120,14 +120,6 @@
.also { mobileIconSubIdCache[subId] = it }
}
- @VisibleForTesting
- fun mobileIconInteractorForSub(subId: Int): MobileIconInteractor {
- return mobileIconInteractorSubIdCache[subId]
- ?: interactor.createMobileConnectionInteractorForSubId(subId).also {
- mobileIconInteractorSubIdCache[subId] = it
- }
- }
-
private fun invalidateCaches(subIds: List<Int>) {
val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt
new file mode 100644
index 0000000..189dc40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/InternetTileBinder.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.coroutineScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Binds an [InternetTileModel] flow to a consumer for the internet tile to apply to its qs state
+ */
+object InternetTileBinder {
+ fun bind(
+ lifecycle: Lifecycle,
+ tileModelFlow: StateFlow<InternetTileModel>,
+ consumer: Consumer<InternetTileModel>
+ ) {
+ lifecycle.coroutineScope.launch {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ tileModelFlow.collect { consumer.accept(it) }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
new file mode 100644
index 0000000..327dd8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileModel.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.model
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.service.quicksettings.Tile
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.tileimpl.QSTileImpl
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+ val secondaryTitle: String?
+ val secondaryLabel: Text?
+ val iconId: Int?
+ val icon: QSTile.Icon?
+
+ fun applyTo(state: QSTile.SignalState, context: Context) {
+ if (secondaryLabel != null) {
+ state.secondaryLabel = secondaryLabel.loadText(context)
+ } else {
+ state.secondaryLabel = secondaryTitle
+ }
+
+ // inout indicators are unused
+ state.activityIn = false
+ state.activityOut = false
+
+ // To support both SignalDrawable and other icons, give priority to icons over IDs
+ if (icon != null) {
+ state.icon = icon
+ } else if (iconId != null) {
+ state.icon = QSTileImpl.ResourceIcon.get(iconId!!)
+ }
+
+ state.state =
+ if (this is Active) {
+ Tile.STATE_ACTIVE
+ } else {
+ Tile.STATE_INACTIVE
+ }
+ }
+
+ data class Active(
+ override val secondaryTitle: String? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: QSTile.Icon? = null,
+ ) : InternetTileModel
+
+ data class Inactive(
+ override val secondaryTitle: String? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: QSTile.Icon? = null,
+ ) : InternetTileModel
+}
+
+/**
+ * [QSTile.Icon]-compatible container class for us to marshal the compacted [SignalDrawable] state
+ * across to the internet tile.
+ */
+data class SignalIcon(val state: Int) : QSTile.Icon() {
+
+ override fun getDrawable(context: Context): Drawable {
+ val d = SignalDrawable(context)
+ d.setLevel(state)
+ return d
+ }
+
+ override fun toString(): String {
+ return String.format("SignalIcon[mState=0x%08x]", state)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
new file mode 100644
index 0000000..120ba4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * View model for the quick settings [InternetTile]. This model exposes mainly a single flow of
+ * InternetTileModel objects, so that updating the tile is as simple as collecting on this state
+ * flow and then calling [QSTileImpl.refreshState]
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class InternetTileViewModel
+@Inject
+constructor(
+ airplaneModeRepository: AirplaneModeRepository,
+ connectivityRepository: ConnectivityRepository,
+ ethernetInteractor: EthernetInteractor,
+ mobileIconsInteractor: MobileIconsInteractor,
+ wifiInteractor: WifiInteractor,
+ private val context: Context,
+ @Application scope: CoroutineScope,
+) {
+ // Three symmetrical Flows that can be switched upon based on the value of
+ // [DefaultConnectionModel]
+ private val wifiIconFlow: Flow<InternetTileModel> =
+ wifiInteractor.wifiNetwork.flatMapLatest {
+ val wifiIcon = WifiIcon.fromModel(it, context)
+ if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = removeDoubleQuotes(it.ssid),
+ icon = ResourceIcon.get(wifiIcon.icon.res)
+ )
+ )
+ } else {
+ notConnectedFlow
+ }
+ }
+
+ private val mobileDataContentName: Flow<CharSequence?> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ flowOf(null)
+ } else {
+ combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+ val cd = loadString(networkTypeIconGroup.contentDescription)
+ if (isRoaming) {
+ val roaming = context.getString(R.string.data_connection_roaming)
+ if (cd != null) {
+ context.getString(R.string.mobile_data_text_format, roaming, cd)
+ } else {
+ roaming
+ }
+ } else {
+ cd
+ }
+ }
+ }
+ }
+
+ private val mobileIconFlow: Flow<InternetTileModel> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ combine(
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ InternetTileModel.Active(
+ secondaryTitle =
+ mobileDataContentConcat(networkNameModel.name, dataContentDescription),
+ icon = SignalIcon(signalIcon.toSignalDrawableState()),
+ )
+ }
+ }
+ }
+
+ private fun mobileDataContentConcat(
+ networkName: String?,
+ dataContentDescription: CharSequence?
+ ): String {
+ if (dataContentDescription == null) {
+ return networkName ?: ""
+ }
+ if (networkName == null) {
+ return dataContentDescription.toString()
+ }
+
+ return context.getString(
+ R.string.mobile_carrier_text_format,
+ networkName,
+ dataContentDescription
+ )
+ }
+
+ private fun loadString(resId: Int): String? =
+ if (resId > 0) {
+ context.getString(resId)
+ } else {
+ null
+ }
+
+ private val ethernetIconFlow: Flow<InternetTileModel> =
+ ethernetInteractor.icon.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = it.contentDescription.toString(),
+ iconId = it.res
+ )
+ )
+ }
+ }
+
+ private val notConnectedFlow: StateFlow<InternetTileModel> =
+ combine(
+ wifiInteractor.areNetworksAvailable,
+ airplaneModeRepository.isAirplaneMode,
+ ) { networksAvailable, isAirplaneMode ->
+ when {
+ isAirplaneMode -> {
+ InternetTileModel.Inactive(
+ secondaryTitle = context.getString(R.string.status_bar_airplane),
+ icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable)
+ )
+ }
+ networksAvailable -> {
+ InternetTileModel.Inactive(
+ secondaryTitle =
+ context.getString(R.string.quick_settings_networks_available),
+ iconId = R.drawable.ic_qs_no_internet_available,
+ )
+ }
+ else -> {
+ NOT_CONNECTED_NETWORKS_UNAVAILABLE
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+ /**
+ * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+ * the interim providers (wifi, mobile, ethernet, or not-connected)
+ */
+ private val activeModelProvider: Flow<InternetTileModel> =
+ connectivityRepository.defaultConnections.flatMapLatest {
+ when {
+ it.ethernet.isDefault -> ethernetIconFlow
+ it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+ it.wifi.isDefault -> wifiIconFlow
+ else -> notConnectedFlow
+ }
+ }
+
+ /** Consumable flow describing the correct state for the InternetTile */
+ val tileModel: StateFlow<InternetTileModel> =
+ activeModelProvider.stateIn(scope, SharingStarted.WhileSubscribed(), notConnectedFlow.value)
+
+ companion object {
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ )
+
+ private fun removeDoubleQuotes(string: String?): String? {
+ if (string == null) return null
+ val length = string.length
+ return if (length > 1 && string[0] == '"' && string[length - 1] == '"') {
+ string.substring(1, length - 1)
+ } else string
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b5b99a7..b22e09e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.StateFlow
/** Provides data related to the wifi state. */
@@ -45,6 +46,12 @@
val wifiActivity: StateFlow<DataActivityModel>
/**
+ * The list of known wifi networks, per [WifiManager.scanResults]. This list is passively
+ * updated and does not trigger a scan.
+ */
+ val wifiScanResults: StateFlow<List<WifiScanEntry>>
+
+ /**
* Returns true if the device is currently connected to a wifi network with a valid SSID and
* false otherwise.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index 80091ac..ca042e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -124,4 +125,9 @@
activeRepo
.flatMapLatest { it.wifiActivity }
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)
+
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ activeRepo
+ .flatMapLatest { it.wifiScanResults }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiScanResults.value)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index 4b19c3a..152d181 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -55,6 +56,10 @@
MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+ private val _wifiScanResults: MutableStateFlow<List<WifiScanEntry>> =
+ MutableStateFlow(emptyList())
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> = _wifiScanResults
+
fun startProcessingCommands() {
demoCommandJob =
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 36c46a9..cfdbe4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -49,6 +50,9 @@
override val wifiActivity: StateFlow<DataActivityModel> =
MutableStateFlow(ACTIVITY).asStateFlow()
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ MutableStateFlow<List<WifiScanEntry>>(emptyList()).asStateFlow()
+
companion object {
private val NETWORK = WifiNetworkModel.Unavailable
private val ACTIVITY = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
index f1b98b3..67dd32f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
@@ -16,15 +16,21 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+import android.annotation.SuppressLint
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -64,6 +70,34 @@
)
}
+ /**
+ * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
+ */
+ fun createNetworkScanFlow(
+ wifiManager: WifiManager,
+ scope: CoroutineScope,
+ @Background dispatcher: CoroutineDispatcher,
+ inputLogger: () -> Unit,
+ ): StateFlow<List<WifiScanEntry>> {
+ return conflatedCallbackFlow {
+ val callback =
+ object : WifiManager.ScanResultsCallback() {
+ @SuppressLint("MissingPermission")
+ override fun onScanResultsAvailable() {
+ inputLogger.invoke()
+ trySend(wifiManager.scanResults.toModel())
+ }
+ }
+
+ wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
+
+ awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+ }
+
+ private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
+
// TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
private fun prettyPrintActivity(activity: Int): String {
return when (activity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 7c7b58d..59ef884 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -231,6 +232,14 @@
logger::logActivity,
)
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ WifiRepositoryHelper.createNetworkScanFlow(
+ wifiManager,
+ scope,
+ bgDispatcher,
+ logger::logScanResults
+ )
+
companion object {
// Start out with no known wifi network.
// Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index d4f40dd..9b404f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -23,6 +23,7 @@
import androidx.lifecycle.LifecycleRegistry
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -42,6 +43,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -51,6 +53,7 @@
import com.android.wifitrackerlib.WifiPickerTracker
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
@@ -73,6 +76,7 @@
featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
private val wifiManager: WifiManager,
@WifiTrackerLibInputLog private val inputLogger: LogBuffer,
@@ -300,6 +304,14 @@
this::logActivity,
)
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ WifiRepositoryHelper.createNetworkScanFlow(
+ wifiManager,
+ scope,
+ bgDispatcher,
+ this::logScanResults,
+ )
+
private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
inputLogger.log(
TAG,
@@ -322,6 +334,9 @@
inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
}
+ private fun logScanResults() =
+ inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
+
/**
* Data class storing all the information fetched from [WifiPickerTracker].
*
@@ -345,6 +360,7 @@
private val featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
@WifiTrackerLibInputLog private val inputLogger: LogBuffer,
@WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
@@ -354,6 +370,7 @@
featureFlags,
scope,
mainExecutor,
+ bgDispatcher,
wifiPickerTrackerFactory,
wifiManager,
inputLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 1a41abf..110e339 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -17,15 +17,21 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* The business logic layer for the wifi icon.
@@ -54,6 +60,9 @@
/** True if we're configured to force-hide the wifi icon and false otherwise. */
val isForceHidden: Flow<Boolean>
+
+ /** True if there are networks available other than the currently-connected one */
+ val areNetworksAvailable: StateFlow<Boolean>
}
@SysUISingleton
@@ -62,6 +71,7 @@
constructor(
connectivityRepository: ConnectivityRepository,
wifiRepository: WifiRepository,
+ @Application scope: CoroutineScope,
) : WifiInteractor {
override val ssid: Flow<String?> =
@@ -91,4 +101,26 @@
override val isForceHidden: Flow<Boolean> =
connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.WIFI) }
+
+ override val areNetworksAvailable: StateFlow<Boolean> =
+ combine(
+ wifiNetwork,
+ wifiRepository.wifiScanResults,
+ ) { currentNetwork, scanResults ->
+ // We consider networks to be available if the scan results list contains networks
+ // other than the one that is currently connected
+ if (scanResults.isEmpty()) {
+ false
+ } else if (currentNetwork !is WifiNetworkModel.Active) {
+ true
+ } else {
+ anyNonMatchingNetworkExists(currentNetwork, scanResults)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private fun anyNonMatchingNetworkExists(
+ currentNetwork: WifiNetworkModel.Active,
+ availableNetworks: List<WifiScanEntry>
+ ): Boolean = availableNetworks.firstOrNull { it.ssid != currentNetwork.ssid } != null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index f244376..b76bb51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -59,6 +59,8 @@
fun logActivity(activity: String) {
buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" })
}
+
+ fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
}
private const val TAG = "WifiInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt
new file mode 100644
index 0000000..d4a5a0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiScanEntry.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.statusbar.pipeline.wifi.shared.model
+
+/**
+ * Represents a single entry in the scan results callback. Use the [ssid] field to check against
+ * other networks
+ */
+data class WifiScanEntry(val ssid: String)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index 094bcf9..8156500 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -17,10 +17,18 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.model
import android.annotation.DrawableRes
+import android.content.Context
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
+import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
/** Represents the various states of the wifi icon. */
sealed interface WifiIcon : Diffable<WifiIcon> {
@@ -34,7 +42,7 @@
* description.
*/
class Visible(
- @DrawableRes res: Int,
+ @DrawableRes val res: Int,
val contentDescription: ContentDescription.Loaded,
) : WifiIcon {
val icon = Icon.Resource(res, contentDescription)
@@ -51,6 +59,46 @@
override fun logFull(row: TableRowLogger) {
row.logChange(COL_ICON, toString())
}
+
+ companion object {
+ @StringRes
+ @VisibleForTesting
+ internal val NO_INTERNET = R.string.data_connection_no_internet
+
+ /** Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon] */
+ fun fromModel(model: WifiNetworkModel, context: Context): WifiIcon =
+ when (model) {
+ is WifiNetworkModel.Unavailable -> Hidden
+ is WifiNetworkModel.Invalid -> Hidden
+ is WifiNetworkModel.CarrierMerged -> Hidden
+ is WifiNetworkModel.Inactive ->
+ Visible(
+ res = WifiIcons.WIFI_NO_NETWORK,
+ ContentDescription.Loaded(
+ "${context.getString(WIFI_NO_CONNECTION)},${context.getString(
+ NO_INTERNET
+ )}"
+ )
+ )
+ is WifiNetworkModel.Active -> {
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
+ when {
+ model.isValidated ->
+ Visible(
+ WifiIcons.WIFI_FULL_ICONS[model.level],
+ ContentDescription.Loaded(levelDesc),
+ )
+ else ->
+ Visible(
+ WifiIcons.WIFI_NO_INTERNET_ICONS[model.level],
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ ),
+ )
+ }
+ }
+ }
+ }
}
private const val COL_ICON = "icon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index d9c2144..27ac7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,19 +17,10 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
-import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
-import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
@@ -77,35 +68,7 @@
) : WifiViewModelCommon {
/** Returns the icon to use based on the given network. */
private fun WifiNetworkModel.icon(): WifiIcon {
- return when (this) {
- is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
- is WifiNetworkModel.Invalid -> WifiIcon.Hidden
- is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
- is WifiNetworkModel.Inactive ->
- WifiIcon.Visible(
- res = WIFI_NO_NETWORK,
- ContentDescription.Loaded(
- "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
- )
- )
- is WifiNetworkModel.Active -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
- when {
- this.isValidated ->
- WifiIcon.Visible(
- WIFI_FULL_ICONS[this.level],
- ContentDescription.Loaded(levelDesc),
- )
- else ->
- WifiIcon.Visible(
- WIFI_NO_INTERNET_ICONS[this.level],
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- ),
- )
- }
- }
- }
+ return WifiIcon.fromModel(this, context)
}
override val wifiIcon: StateFlow<WifiIcon> =
@@ -186,10 +149,4 @@
airplaneModeViewModel.isAirplaneModeIconVisible
override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get()
-
- companion object {
- @StringRes
- @VisibleForTesting
- internal val NO_INTERNET = R.string.data_connection_no_internet
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
index 1212585..feef029 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
@@ -16,9 +16,15 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
import android.view.View;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.util.Compile;
/**
* A class of utility static methods for heads up notifications.
@@ -26,12 +32,18 @@
public final class HeadsUpUtil {
private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag;
+ private static final String LOG_TAG = "HeadsUpUtil";
+ private static final boolean LOG_DEBUG = Compile.IS_DEBUG && Log.isLoggable(LOG_TAG, Log.DEBUG);
+
/**
* Set the given view as clicked or not-clicked.
* @param view The view to be set the flag to.
* @param clicked True to set as clicked. False to not-clicked.
*/
public static void setNeedsHeadsUpDisappearAnimationAfterClick(View view, boolean clicked) {
+ if (LOG_DEBUG) {
+ logTagClickedNotificationChanged(view, clicked);
+ }
view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null);
}
@@ -44,4 +56,36 @@
Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION);
return clicked != null && clicked;
}
+
+ private static void logTagClickedNotificationChanged(@Nullable View view, boolean isClicked) {
+ if (view == null) {
+ return;
+ }
+
+ final boolean wasClicked = isClickedHeadsUpNotification(view);
+ if (isClicked == wasClicked) {
+ return;
+ }
+
+ Log.d(LOG_TAG, getViewKey(view) + ": TAG_CLICKED_NOTIFICATION set to " + isClicked);
+ }
+
+ private static @NonNull String getViewKey(@NonNull View view) {
+ if (!(view instanceof ExpandableNotificationRow)) {
+ return "(not a row)";
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ final NotificationEntry entry = row.getEntry();
+ if (entry == null) {
+ return "(null entry)";
+ }
+
+ final String key = entry.getKey();
+ if (key == null) {
+ return "(null key)";
+ }
+
+ return key;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 7ab8e8b..e56b5c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1616,4 +1616,43 @@
// THEN vibrate is used
verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
}
+
+ @Test
+ public void aodInterrupt_withNewTouchDetection() throws RemoteException {
+ mUdfpsController.cancelAodSendFingerUpAction();
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultDown =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+ 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // GIVEN that the overlay is showing and screen is on and fp is running
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+
+ // WHEN fingerprint is requested because of AOD interrupt
+ mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
+
+ // Check case where touch driver sends touch to UdfpsView as well
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDown);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+
+ mBiometricExecutor.runAllReady();
+
+ // THEN only one onPointerDown is sent
+ verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index a01394f..f566efe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -193,6 +193,23 @@
assertThat(reasonCaptor.value).contains(context.applicationContext.packageName)
}
+ @Test
+ fun userActivity_notifiesPowerManager() {
+ systemClock.setUptimeMillis(345000)
+
+ underTest.userTouch()
+
+ val flagsCaptor = argumentCaptor<Int>()
+ verify(manager)
+ .userActivity(
+ eq(345000L),
+ eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+ capture(flagsCaptor)
+ )
+ assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)
+ assertThat(flagsCaptor.value).isNotEqualTo(PowerManager.USER_ACTIVITY_FLAG_INDIRECT)
+ }
+
private fun verifyRegistered() {
// We must verify with all arguments, even those that are optional because they have default
// values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 93ed994..e537131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs.tiles;
+import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION;
+
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
@@ -37,9 +39,11 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
@@ -49,6 +53,12 @@
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -65,6 +75,7 @@
import java.util.ArrayList;
import java.util.List;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -98,6 +109,12 @@
@Mock
private QsEventLogger mUiEventLogger;
+ private WifiInteractor mWifiInteractor;
+ private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
+ private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+ private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+
private TestableLooper mTestableLooper;
private CastTile mCastTile;
@@ -108,42 +125,11 @@
when(mHost.getContext()).thenReturn(mContext);
- mCastTile = new CastTile(
- mHost,
- mUiEventLogger,
- mTestableLooper.getLooper(),
- new Handler(mTestableLooper.getLooper()),
- new FalsingManagerFake(),
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQSLogger,
- mController,
- mKeyguard,
- mNetworkController,
- mHotspotController,
- mDialogLaunchAnimator
+ mWifiInteractor = new WifiInteractorImpl(
+ new FakeConnectivityRepository(),
+ mWifiRepository,
+ mTestScope
);
- mCastTile.initialize();
-
- // We are not setting the mocks to listening, so we trigger a first refresh state to
- // set the initial state
- mCastTile.refreshState();
-
- mTestableLooper.processAllMessages();
-
- mCastTile.handleSetListening(true);
- ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
- ArgumentCaptor.forClass(SignalCallback.class);
- verify(mNetworkController).observe(any(LifecycleOwner.class),
- signalCallbackArgumentCaptor.capture());
- mSignalCallback = signalCallbackArgumentCaptor.getValue();
-
- ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
- ArgumentCaptor.forClass(HotspotController.Callback.class);
- verify(mHotspotController).observe(any(LifecycleOwner.class),
- hotspotCallbackArgumentCaptor.capture());
- mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
}
@After
@@ -156,10 +142,11 @@
// All these tests for enabled/disabled wifi have hotspot not enabled
@Test
public void testStateUnavailable_wifiDisabled() {
+ createAndStartTileOldImpl();
IconState qsIcon = new IconState(false, 0, "");
WifiIndicators indicators = new WifiIndicators(
false, mock(IconState.class),
- qsIcon, false,false, "",
+ qsIcon, false, false, "",
false, "");
mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
@@ -169,10 +156,11 @@
@Test
public void testStateUnavailable_wifiNotConnected() {
+ createAndStartTileOldImpl();
IconState qsIcon = new IconState(false, 0, "");
WifiIndicators indicators = new WifiIndicators(
true, mock(IconState.class),
- qsIcon, false,false, "",
+ qsIcon, false, false, "",
false, "");
mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
@@ -184,7 +172,7 @@
IconState qsIcon = new IconState(true, 0, "");
WifiIndicators indicators = new WifiIndicators(
true, mock(IconState.class),
- qsIcon, false,false, "",
+ qsIcon, false, false, "",
false, "");
mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
@@ -192,6 +180,7 @@
@Test
public void testStateActive_wifiEnabledAndCasting() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastController.CastDevice.STATE_CONNECTED;
List<CastDevice> devices = new ArrayList<>();
@@ -204,15 +193,87 @@
@Test
public void testStateInactive_wifiEnabledNotCasting() {
+ createAndStartTileOldImpl();
enableWifiAndProcessMessages();
assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
}
// -------------------------------------------------
// -------------------------------------------------
+ // All these tests for enabled/disabled wifi have hotspot not enabled, and have the
+ // SIGNAL_CALLBACK_DEPRECATION flag set to true
+
+ @Test
+ public void stateUnavailable_wifiDisabled_newPipeline() {
+ createAndStartTileNewImpl();
+ mWifiRepository.setIsWifiEnabled(false);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
+ createAndStartTileNewImpl();
+ mWifiRepository.setIsWifiEnabled(true);
+ mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateActive_wifiConnectedAndCasting_newPipeline() {
+ createAndStartTileNewImpl();
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ mWifiRepository.setWifiNetwork(
+ new WifiNetworkModel.Active(
+ 1 /* networkId */,
+ true /* isValidated */,
+ 3 /* level */,
+ "test" /* ssid */,
+ WifiNetworkModel.HotspotDeviceType.NONE,
+ false /* isPasspointAccessPoint */,
+ false /* isOnlineSignUpforPasspointAccessPoint */,
+ null /* passpointProviderFriendlyName */
+ ));
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void stateInactive_wifiConnectedNotCasting_newPipeline() {
+ createAndStartTileNewImpl();
+
+ mWifiRepository.setWifiNetwork(
+ new WifiNetworkModel.Active(
+ 1 /* networkId */,
+ true /* isValidated */,
+ 3 /* level */,
+ "test" /* ssid */,
+ WifiNetworkModel.HotspotDeviceType.NONE,
+ false /* isPasspointAccessPoint */,
+ false /* isOnlineSignUpforPasspointAccessPoint */,
+ null /* passpointProviderFriendlyName */
+ ));
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+
+ // -------------------------------------------------
+
+ // -------------------------------------------------
// All these tests for enabled/disabled hotspot have wifi not enabled
@Test
public void testStateUnavailable_hotspotDisabled() {
+ createAndStartTileOldImpl();
mHotspotCallback.onHotspotChanged(false, 0);
mTestableLooper.processAllMessages();
@@ -221,6 +282,7 @@
@Test
public void testStateUnavailable_hotspotEnabledNotConnected() {
+ createAndStartTileOldImpl();
mHotspotCallback.onHotspotChanged(true, 0);
mTestableLooper.processAllMessages();
@@ -229,6 +291,7 @@
@Test
public void testStateActive_hotspotEnabledAndConnectedAndCasting() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastController.CastDevice.STATE_CONNECTED;
List<CastDevice> devices = new ArrayList<>();
@@ -242,6 +305,7 @@
@Test
public void testStateInactive_hotspotEnabledAndConnectedAndNotCasting() {
+ createAndStartTileOldImpl();
mHotspotCallback.onHotspotChanged(true, 1);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
@@ -250,6 +314,7 @@
@Test
public void testHandleClick_castDevicePresent() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaRouter.RouteInfo.class);
@@ -267,6 +332,7 @@
@Test
public void testHandleClick_projectionOnly() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaProjectionInfo.class);
@@ -283,6 +349,7 @@
@Test
public void testUpdateState_projectionOnly() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaProjectionInfo.class);
@@ -298,6 +365,7 @@
@Test
public void testUpdateState_castingAndProjection() {
+ createAndStartTileOldImpl();
CastController.CastDevice casting = new CastController.CastDevice();
casting.state = CastDevice.STATE_CONNECTED;
casting.tag = mock(RouteInfo.class);
@@ -322,6 +390,7 @@
@Test
public void testUpdateState_connectedAndConnecting() {
+ createAndStartTileOldImpl();
CastController.CastDevice connecting = new CastController.CastDevice();
connecting.state = CastDevice.STATE_CONNECTING;
connecting.tag = mock(RouteInfo.class);
@@ -346,6 +415,7 @@
@Test
public void testExpandView_wifiNotConnected() {
+ createAndStartTileOldImpl();
mCastTile.refreshState();
mTestableLooper.processAllMessages();
@@ -354,6 +424,7 @@
@Test
public void testExpandView_wifiEnabledNotCasting() {
+ createAndStartTileOldImpl();
enableWifiAndProcessMessages();
assertTrue(mCastTile.getState().forceExpandIcon);
@@ -361,6 +432,7 @@
@Test
public void testExpandView_casting_projection() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastController.CastDevice.STATE_CONNECTED;
List<CastDevice> devices = new ArrayList<>();
@@ -374,6 +446,7 @@
@Test
public void testExpandView_connecting_projection() {
+ createAndStartTileOldImpl();
CastController.CastDevice connecting = new CastController.CastDevice();
connecting.state = CastDevice.STATE_CONNECTING;
connecting.name = "Test Casting Device";
@@ -389,6 +462,7 @@
@Test
public void testExpandView_casting_mediaRoute() {
+ createAndStartTileOldImpl();
CastController.CastDevice device = new CastController.CastDevice();
device.state = CastDevice.STATE_CONNECTED;
device.tag = mock(MediaRouter.RouteInfo.class);
@@ -403,6 +477,7 @@
@Test
public void testExpandView_connecting_mediaRoute() {
+ createAndStartTileOldImpl();
CastController.CastDevice connecting = new CastController.CastDevice();
connecting.state = CastDevice.STATE_CONNECTING;
connecting.tag = mock(RouteInfo.class);
@@ -416,4 +491,86 @@
assertTrue(mCastTile.getState().forceExpandIcon);
}
+
+ /**
+ * For simplicity, let this method still set the field even though that's kind of gross
+ */
+ private void createAndStartTileOldImpl() {
+ mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, false);
+ mCastTile = new CastTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mKeyguard,
+ mNetworkController,
+ mHotspotController,
+ mDialogLaunchAnimator,
+ mWifiInteractor,
+ mJavaAdapter,
+ mFeatureFlags
+ );
+ mCastTile.initialize();
+
+ // We are not setting the mocks to listening, so we trigger a first refresh state to
+ // set the initial state
+ mCastTile.refreshState();
+
+ mTestableLooper.processAllMessages();
+
+ mCastTile.handleSetListening(true);
+ ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(SignalCallback.class);
+ verify(mNetworkController).observe(any(LifecycleOwner.class),
+ signalCallbackArgumentCaptor.capture());
+ mSignalCallback = signalCallbackArgumentCaptor.getValue();
+
+ ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(HotspotController.Callback.class);
+ verify(mHotspotController).observe(any(LifecycleOwner.class),
+ hotspotCallbackArgumentCaptor.capture());
+ mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
+ }
+
+ private void createAndStartTileNewImpl() {
+ mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, true);
+ mCastTile = new CastTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mKeyguard,
+ mNetworkController,
+ mHotspotController,
+ mDialogLaunchAnimator,
+ mWifiInteractor,
+ mJavaAdapter,
+ mFeatureFlags
+ );
+ mCastTile.initialize();
+
+ // Since we do not capture the callbacks like in the old impl, set the state to RESUMED
+ // So that TileJavaAdapter is collecting on flows
+ mCastTile.setListening(new Object(), true);
+
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(HotspotController.Callback.class);
+ verify(mHotspotController).observe(any(LifecycleOwner.class),
+ hotspotCallbackArgumentCaptor.capture());
+ mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
new file mode 100644
index 0000000..b6cf459
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.tiles
+
+import android.os.Handler
+import android.service.quicksettings.Tile
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+class InternetTileNewImplTest : SysuiTestCase() {
+ lateinit var underTest: InternetTileNewImpl
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private var airplaneModeRepository = FakeAirplaneModeRepository()
+ private var connectivityRepository = FakeConnectivityRepository()
+ private var ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private var mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private var wifiRepository = FakeWifiRepository()
+ private var wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+ private lateinit var viewModel: InternetTileViewModel
+
+ private lateinit var looper: TestableLooper
+
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var eventLogger: QsEventLogger
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var sbStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var logger: QSLogger
+ @Mock private lateinit var dialogFactory: InternetDialogFactory
+ @Mock private lateinit var accessPointController: AccessPointController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ looper = TestableLooper.get(this)
+
+ // Allow the tile to load resources
+ whenever(host.context).thenReturn(context)
+ whenever(host.userContext).thenReturn(context)
+
+ viewModel =
+ InternetTileViewModel(
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ context,
+ testScope.backgroundScope,
+ )
+
+ underTest =
+ InternetTileNewImpl(
+ host,
+ eventLogger,
+ looper.looper,
+ Handler(looper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ sbStateController,
+ activityStarter,
+ logger,
+ viewModel,
+ dialogFactory,
+ accessPointController
+ )
+
+ underTest.initialize()
+ underTest.setListening(Object(), true)
+
+ looper.processAllMessages()
+ }
+
+ @Test
+ fun noDefaultConnection_noNetworkAvailable() =
+ testScope.runTest {
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.wifiScanResults.value = listOf()
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.secondaryLabel.toString())
+ .isEqualTo(context.getString(R.string.quick_settings_networks_unavailable))
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+ }
+
+ @Test
+ fun noDefaultConnection_networksAvailable() =
+ testScope.runTest {
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ )
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.secondaryLabel.toString())
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(underTest.state.state).isEqualTo(1)
+ }
+
+ @Test
+ fun airplaneMode_enabled_wifiDisabled() =
+ testScope.runTest {
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.setIsWifiEnabled(false)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(underTest.state.secondaryLabel)
+ .isEqualTo(context.getString(R.string.status_bar_airplane))
+ }
+
+ @Test
+ fun airplaneMode_enabled_wifiEnabledButNotConnected() =
+ testScope.runTest {
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ wifiRepository.setIsWifiEnabled(true)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(underTest.state.secondaryLabel)
+ .isEqualTo(context.getString(R.string.status_bar_airplane))
+ }
+
+ @Test
+ fun airplaneMode_enabled_wifiEnabledAndConnected() =
+ testScope.runTest {
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.defaultConnections.value =
+ DefaultConnectionModel(
+ wifi = Wifi(true),
+ isValidated = true,
+ )
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setWifiNetwork(ACTIVE_WIFI)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID)
+ }
+
+ @Test
+ fun wifiConnected() =
+ testScope.runTest {
+ connectivityRepository.defaultConnections.value =
+ DefaultConnectionModel(
+ wifi = Wifi(true),
+ isValidated = true,
+ )
+
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setWifiNetwork(ACTIVE_WIFI)
+
+ runCurrent()
+ looper.processAllMessages()
+
+ assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(underTest.state.secondaryLabel).isEqualTo(WIFI_SSID)
+ }
+
+ companion object {
+ const val WIFI_SSID = "test ssid"
+ val ACTIVE_WIFI =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ isValidated = true,
+ level = 4,
+ ssid = WIFI_SSID,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 46cbfac..0ecbcde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -127,7 +127,7 @@
bouncerInteractor = bouncerInteractor,
)
- private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardRepository = utils.keyguardRepository
private val keyguardInteractor =
utils.keyguardInteractor(
repository = keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 713c602..8620f61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -147,4 +147,11 @@
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
+
+ @Test
+ fun userInput() =
+ testScope.runTest {
+ underTest.onUserInput()
+ assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 951cadd..d60d994 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -61,7 +61,7 @@
utils.authenticationInteractor(
repository = authenticationRepository,
)
- private val keyguardRepository = utils.keyguardRepository()
+ private val keyguardRepository = utils.keyguardRepository
private val keyguardInteractor =
utils.keyguardInteractor(
repository = keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
new file mode 100644
index 0000000..5784be3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ethernet/domain/EthernetInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.statusbar.pipeline.ethernet.domain
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class EthernetInteractorTest : SysuiTestCase() {
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val underTest = EthernetInteractor(connectivityRepository)
+
+ private val testScope = TestScope()
+
+ @Test
+ fun icon_default_validated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ val expected =
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet_fully,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[1]
+ )
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun icon_default_notValidated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ val expected =
+ Icon.Resource(
+ R.drawable.stat_sys_ethernet,
+ ContentDescription.Resource(
+ AccessibilityContentDescriptions.ETHERNET_CONNECTION_VALUES[0]
+ )
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun icon_notDefault_validated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = false, validated = true)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_notDefault_notValidated() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ connectivityRepository.setEthernetConnected(default = false, validated = false)
+
+ assertThat(latest).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index ff28753..812e91b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -64,6 +64,28 @@
_dataEnabled.value = enabled
}
+ /**
+ * Set [primaryLevel] and [cdmaLevel]. Convenient when you don't care about the connection type
+ */
+ fun setAllLevels(level: Int) {
+ cdmaLevel.value = level
+ primaryLevel.value = level
+ }
+
+ /**
+ * Set both [isRoaming] and [cdmaRoaming] properties, in the event that you don't care about the
+ * connection type
+ */
+ fun setAllRoaming(roaming: Boolean) {
+ isRoaming.value = roaming
+ cdmaRoaming.value = roaming
+ }
+
+ /** Set the correct [resolvedNetworkType] for the given group via its lookup key */
+ fun setNetworkTypeKey(key: String) {
+ resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(key)
+ }
+
companion object {
const val DEFAULT_NETWORK_NAME = "default name"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 9c0cb17..ede02d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -674,6 +674,7 @@
val realRepo =
MobileConnectionRepositoryImpl(
SUB_ID,
+ context,
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index e50e5e3..3af960b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -16,18 +16,22 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED
import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
@@ -75,6 +79,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -88,6 +93,7 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -100,6 +106,7 @@
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: MobileInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
+ @Mock private lateinit var context: Context
private val mobileMappings = FakeMobileMappingsProxy()
private val systemUiCarrierConfig =
@@ -129,6 +136,7 @@
underTest =
MobileConnectionRepositoryImpl(
SUB_1_ID,
+ context,
subscriptionModel,
DEFAULT_NAME_MODEL,
SEP,
@@ -706,7 +714,9 @@
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
val intent = spnIntent()
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.value!!.onReceive(context, intent)
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
@@ -720,13 +730,16 @@
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
val intent = spnIntent()
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.value!!.onReceive(context, intent)
+
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
// WHEN an intent with a different subId is sent
val wrongSubIntent = spnIntent(subId = 101)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, wrongSubIntent)
+ captor.value!!.onReceive(context, wrongSubIntent)
// THEN the previous intent's name is still used
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
@@ -741,7 +754,10 @@
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
val intent = spnIntent()
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.value!!.onReceive(context, intent)
+
assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
val intentWithoutInfo =
@@ -750,7 +766,7 @@
showPlmn = false,
)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intentWithoutInfo)
+ captor.value!!.onReceive(context, intentWithoutInfo)
assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
@@ -893,7 +909,7 @@
plmn: String = PLMN,
): Intent =
Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
- putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+ putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
putExtra(EXTRA_SHOW_SPN, showSpn)
putExtra(EXTRA_SPN, spn)
putExtra(EXTRA_SHOW_PLMN, showPlmn)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index ea60aa7..852ed20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -125,6 +125,7 @@
underTest =
MobileConnectionRepositoryImpl(
SUB_1_ID,
+ context,
subscriptionModel,
DEFAULT_NAME,
SEP,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index fd05cc4..6f9764a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -175,6 +175,7 @@
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
+ context,
fakeBroadcastDispatcher,
telephonyManager = telephonyManager,
bgDispatcher = dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index a3df785..de2b6a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -16,12 +16,11 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
-import android.telephony.CellSignalStrength
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,8 +29,6 @@
) : MobileIconInteractor {
override val alwaysShowDataRatIcon = MutableStateFlow(false)
- override val alwaysUseCdmaLevel = MutableStateFlow(false)
-
override val activity =
MutableStateFlow(
DataActivityModel(
@@ -55,14 +52,8 @@
override val carrierName = MutableStateFlow("demo mode")
- private val _isEmergencyOnly = MutableStateFlow(false)
- override val isEmergencyOnly = _isEmergencyOnly
-
override val isRoaming = MutableStateFlow(false)
- private val _isFailedConnection = MutableStateFlow(false)
- override val isDefaultConnectionFailed = _isFailedConnection
-
override val isDataConnected = MutableStateFlow(true)
override val isInService = MutableStateFlow(true)
@@ -70,40 +61,21 @@
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
- private val _isDefaultDataEnabled = MutableStateFlow(true)
- override val isDefaultDataEnabled = _isDefaultDataEnabled
-
- private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
- override val level = _level
-
- private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
- override val numberOfLevels = _numberOfLevels
-
override val isForceHidden = MutableStateFlow(false)
override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
- fun setIsEmergencyOnly(emergency: Boolean) {
- _isEmergencyOnly.value = emergency
- }
+ override val signalLevelIcon: MutableStateFlow<SignalIconModel> =
+ MutableStateFlow(
+ SignalIconModel(
+ level = 0,
+ numberOfLevels = 4,
+ showExclamationMark = false,
+ carrierNetworkChange = false,
+ )
+ )
fun setIsDataEnabled(enabled: Boolean) {
_isDataEnabled.value = enabled
}
-
- fun setIsDefaultDataEnabled(disabled: Boolean) {
- _isDefaultDataEnabled.value = disabled
- }
-
- fun setIsFailedConnection(failed: Boolean) {
- _isFailedConnection.value = failed
- }
-
- fun setLevel(level: Int) {
- _level.value = level
- }
-
- fun setNumberOfLevels(num: Int) {
- _numberOfLevels.value = num
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 82b7ec4..75d1869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -58,6 +58,8 @@
private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+ override val activeDataIconInteractor = MutableStateFlow(null)
+
override val alwaysShowDataRatIcon = MutableStateFlow(false)
override val alwaysUseCdmaLevel = MutableStateFlow(false)
@@ -78,7 +80,7 @@
override val isForceHidden = MutableStateFlow(false)
/** Always returns a new fake interactor */
- override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
+ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index e3c59ad..e2f9119 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -75,22 +76,12 @@
@Before
fun setUp() {
underTest = createInteractor()
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ connectionRepository.isInService.value = true
}
@Test
- fun gsm_level_default_unknown() =
- testScope.runTest {
- connectionRepository.isGsm.value = true
-
- var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
-
- job.cancel()
- }
-
- @Test
fun gsm_usesGsmLevel() =
testScope.runTest {
connectionRepository.isGsm.value = true
@@ -98,7 +89,7 @@
connectionRepository.cdmaLevel.value = CDMA_LEVEL
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(GSM_LEVEL)
@@ -114,7 +105,7 @@
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(GSM_LEVEL)
@@ -127,7 +118,7 @@
connectionRepository.isGsm.value = false
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
job.cancel()
@@ -142,7 +133,7 @@
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(CDMA_LEVEL)
@@ -158,7 +149,7 @@
mobileIconsInteractor.alwaysUseCdmaLevel.value = false
var latest: Int? = null
- val job = underTest.level.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
assertThat(latest).isEqualTo(GSM_LEVEL)
@@ -169,7 +160,7 @@
fun numberOfLevels_comesFromRepo() =
testScope.runTest {
var latest: Int? = null
- val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+ val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this)
connectionRepository.numberOfLevels.value = 5
assertThat(latest).isEqualTo(5)
@@ -295,50 +286,6 @@
}
@Test
- fun alwaysUseCdmaLevel_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
-
- mobileIconsInteractor.alwaysUseCdmaLevel.value = true
- assertThat(latest).isTrue()
-
- mobileIconsInteractor.alwaysUseCdmaLevel.value = false
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun test_isDefaultDataEnabled_matchesParent() =
- testScope.runTest {
- var latest: Boolean? = null
- val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
-
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
- assertThat(latest).isTrue()
-
- mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun test_isDefaultConnectionFailed_matchedParent() =
- testScope.runTest {
- val job = underTest.isDefaultConnectionFailed.launchIn(this)
-
- mobileIconsInteractor.isDefaultConnectionFailed.value = false
- assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
-
- mobileIconsInteractor.isDefaultConnectionFailed.value = true
- assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
-
- job.cancel()
- }
-
- @Test
fun dataState_connected() =
testScope.runTest {
var latest: Boolean? = null
@@ -541,6 +488,142 @@
assertThat(latest).isFalse()
}
+ @Test
+ fun iconId_correctLevel_notCutout() =
+ testScope.runTest {
+ connectionRepository.isInService.value = true
+ connectionRepository.primaryLevel.value = 1
+ connectionRepository.setDataEnabled(false)
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest?.level).isEqualTo(1)
+ assertThat(latest?.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesLevelFromInteractor() =
+ testScope.runTest {
+ connectionRepository.isInService.value = true
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.primaryLevel.value = 3
+ assertThat(latest!!.level).isEqualTo(3)
+
+ connectionRepository.primaryLevel.value = 1
+ assertThat(latest!!.level).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesNumberOfLevelsFromInteractor() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.numberOfLevels.value = 5
+ assertThat(latest!!.numberOfLevels).isEqualTo(5)
+
+ connectionRepository.numberOfLevels.value = 2
+ assertThat(latest!!.numberOfLevels).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_defaultDataDisabled_showExclamationTrue() =
+ testScope.runTest {
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_defaultConnectionFailed_showExclamationTrue() =
+ testScope.runTest {
+ mobileIconsInteractor.isDefaultConnectionFailed.value = true
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_enabledAndNotFailed_showExclamationFalse() =
+ testScope.runTest {
+ connectionRepository.isInService.value = true
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ mobileIconsInteractor.isDefaultConnectionFailed.value = false
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesEmptyState_whenNotInService() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.isInService.value = false
+
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
+
+ // Changing the level doesn't overwrite the disabled state
+ connectionRepository.primaryLevel.value = 2
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
+
+ // Once back in service, the regular icon appears
+ connectionRepository.isInService.value = true
+ assertThat(latest?.level).isEqualTo(2)
+ assertThat(latest?.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.isInService.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
+ connectionRepository.primaryLevel.value = 1
+ connectionRepository.cdmaLevel.value = 1
+
+ assertThat(latest!!.level).isEqualTo(1)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ // SignalIconModel respects the current level
+ connectionRepository.primaryLevel.value = 2
+
+ assertThat(latest!!.level).isEqualTo(2)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ job.cancel()
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 3e6f909..b4c7578 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -781,6 +781,16 @@
job.cancel()
}
+ @Test
+ fun iconInteractor_cachedPerSubId() =
+ testScope.runTest {
+ val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+ val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+
+ assertThat(interactor1).isNotNull()
+ assertThat(interactor1).isSameInstanceAs(interactor2)
+ }
+
/**
* Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
* flow.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
index 01c388a..90a8946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -19,6 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index e59d90f..1878329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -17,18 +17,26 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
-import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,11 +58,18 @@
private lateinit var homeIcon: HomeMobileIconViewModel
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
- private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var iconsInteractor: MobileIconsInteractor
+ private lateinit var interactor: MobileIconInteractor
+ private lateinit var connectionsRepository: FakeMobileConnectionsRepository
+ private lateinit var repository: FakeMobileConnectionRepository
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+
+ private val connectivityRepository = FakeConnectivityRepository()
+
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -67,16 +82,51 @@
FakeAirplaneModeRepository(),
FakeConnectivityRepository(),
)
- interactor = FakeMobileIconInteractor(tableLogBuffer)
- interactor.apply {
- setLevel(1)
- setIsDefaultDataEnabled(true)
- setIsFailedConnection(false)
- setIsEmergencyOnly(false)
- setNumberOfLevels(4)
- networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
- isDataConnected.value = true
- }
+ connectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ repository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
+ isInService.value = true
+ cdmaLevel.value = 1
+ primaryLevel.value = 1
+ isEmergencyOnly.value = false
+ numberOfLevels.value = 4
+ resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G")
+ dataConnectionState.value = DataConnectionState.Connected
+ }
+
+ connectionsRepository.activeMobileDataRepository.value = repository
+
+ connectivityRepository.apply { setMobileConnected() }
+
+ iconsInteractor =
+ MobileIconsInteractorImpl(
+ connectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ )
+
+ interactor =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ iconsInteractor.activeDataConnectionHasDataEnabled,
+ iconsInteractor.alwaysShowDataRatIcon,
+ iconsInteractor.alwaysUseCdmaLevel,
+ iconsInteractor.isSingleCarrier,
+ iconsInteractor.mobileIsDefault,
+ iconsInteractor.defaultMobileIconMapping,
+ iconsInteractor.defaultMobileIconGroup,
+ iconsInteractor.isDefaultConnectionFailed,
+ iconsInteractor.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake()
+ )
+
commonImpl =
MobileIconViewModel(
SUB_1_ID,
@@ -109,7 +159,7 @@
assertThat(latestQs).isEqualTo(expected)
assertThat(latestKeyguard).isEqualTo(expected)
- interactor.setLevel(2)
+ repository.setAllLevels(2)
expected = defaultSignal(level = 2)
assertThat(latestHome).isEqualTo(expected)
@@ -123,5 +173,16 @@
companion object {
private const val SUB_1_ID = 1
+ private const val NUM_LEVELS = 4
+
+ /** Convenience constructor for these tests */
+ fun defaultSignal(level: Int = 1): SignalIconModel {
+ return SignalIconModel(
+ level,
+ NUM_LEVELS,
+ showExclamationMark = false,
+ carrierNetworkChange = false,
+ )
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 72feec7..796d5ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -19,20 +19,30 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons.G
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
-import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
-import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,12 +61,18 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileIconViewModelTest : SysuiTestCase() {
+ private var connectivityRepository = FakeConnectivityRepository()
+
private lateinit var underTest: MobileIconViewModel
- private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var interactor: MobileIconInteractorImpl
+ private lateinit var iconsInteractor: MobileIconsInteractorImpl
+ private lateinit var repository: FakeMobileConnectionRepository
+ private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -66,23 +82,53 @@
MockitoAnnotations.initMocks(this)
whenever(constants.hasDataCapabilities).thenReturn(true)
+ connectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+
+ repository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
+ setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+ connectionsRepository.activeMobileDataRepository.value = repository
+ connectionsRepository.mobileIsDefault.value = true
+
airplaneModeRepository = FakeAirplaneModeRepository()
airplaneModeInteractor =
AirplaneModeInteractor(
airplaneModeRepository,
- FakeConnectivityRepository(),
+ connectivityRepository,
)
- interactor = FakeMobileIconInteractor(tableLogBuffer)
- interactor.apply {
- setLevel(1)
- setIsDefaultDataEnabled(true)
- setIsFailedConnection(false)
- setIsEmergencyOnly(false)
- setNumberOfLevels(4)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- isDataConnected.value = true
- }
+ iconsInteractor =
+ MobileIconsInteractorImpl(
+ connectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ )
+
+ interactor =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ iconsInteractor.activeDataConnectionHasDataEnabled,
+ iconsInteractor.alwaysShowDataRatIcon,
+ iconsInteractor.alwaysUseCdmaLevel,
+ iconsInteractor.isSingleCarrier,
+ iconsInteractor.mobileIsDefault,
+ iconsInteractor.defaultMobileIconMapping,
+ iconsInteractor.defaultMobileIconGroup,
+ iconsInteractor.isDefaultConnectionFailed,
+ iconsInteractor.isForceHidden,
+ repository,
+ context,
+ MobileIconCarrierIdOverridesFake()
+ )
createAndSetViewModel()
}
@@ -108,7 +154,6 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
- interactor.isForceHidden.value = false
assertThat(latest).isTrue()
@@ -122,8 +167,8 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
- interactor.isAllowedDuringAirplaneMode.value = false
- interactor.isForceHidden.value = false
+ repository.isAllowedDuringAirplaneMode.value = false
+ connectivityRepository.setForceHiddenIcons(setOf())
assertThat(latest).isFalse()
@@ -138,8 +183,8 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(true)
- interactor.isAllowedDuringAirplaneMode.value = true
- interactor.isForceHidden.value = false
+ repository.isAllowedDuringAirplaneMode.value = true
+ connectivityRepository.setForceHiddenIcons(setOf())
assertThat(latest).isTrue()
@@ -153,7 +198,7 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
- interactor.isForceHidden.value = true
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isFalse()
@@ -167,156 +212,29 @@
val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
airplaneModeRepository.setIsAirplaneMode(false)
- interactor.isForceHidden.value = false
+ connectivityRepository.setForceHiddenIcons(setOf())
assertThat(latest).isTrue()
airplaneModeRepository.setIsAirplaneMode(true)
assertThat(latest).isFalse()
- interactor.isAllowedDuringAirplaneMode.value = true
+ repository.isAllowedDuringAirplaneMode.value = true
assertThat(latest).isTrue()
- interactor.isForceHidden.value = true
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun iconId_correctLevel_notCutout() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
- val expected = defaultSignal()
-
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
-
- @Test
- fun icon_usesLevelFromInteractor() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.level.value = 3
- assertThat(latest!!.level).isEqualTo(3)
-
- interactor.level.value = 1
- assertThat(latest!!.level).isEqualTo(1)
-
- job.cancel()
- }
-
- @Test
- fun icon_usesNumberOfLevelsFromInteractor() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.numberOfLevels.value = 5
- assertThat(latest!!.numberOfLevels).isEqualTo(5)
-
- interactor.numberOfLevels.value = 2
- assertThat(latest!!.numberOfLevels).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun icon_defaultDataDisabled_showExclamationTrue() =
- testScope.runTest {
- interactor.setIsDefaultDataEnabled(false)
-
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest!!.showExclamationMark).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun icon_defaultConnectionFailed_showExclamationTrue() =
- testScope.runTest {
- interactor.isDefaultConnectionFailed.value = true
-
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest!!.showExclamationMark).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun icon_enabledAndNotFailed_showExclamationFalse() =
- testScope.runTest {
- interactor.setIsDefaultDataEnabled(true)
- interactor.isDefaultConnectionFailed.value = false
-
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- assertThat(latest!!.showExclamationMark).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun icon_usesEmptyState_whenNotInService() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.isInService.value = false
-
- var expected = emptySignal()
-
- assertThat(latest).isEqualTo(expected)
-
- // Changing the level doesn't overwrite the disabled state
- interactor.level.value = 2
- assertThat(latest).isEqualTo(expected)
-
- // Once back in service, the regular icon appears
- interactor.isInService.value = true
- expected = defaultSignal(level = 2)
- assertThat(latest).isEqualTo(expected)
-
- job.cancel()
- }
-
- @Test
- fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
- testScope.runTest {
- var latest: SignalIconModel? = null
- val job = underTest.icon.onEach { latest = it }.launchIn(this)
-
- interactor.carrierNetworkChangeActive.value = true
- interactor.level.value = 1
-
- assertThat(latest!!.level).isEqualTo(1)
- assertThat(latest!!.carrierNetworkChange).isTrue()
-
- // SignalIconModel respects the current level
- interactor.level.value = 2
-
- assertThat(latest!!.level).isEqualTo(2)
- assertThat(latest!!.carrierNetworkChange).isTrue()
-
- job.cancel()
- }
-
- @Test
fun contentDescription_notInService_usesNoPhone() =
testScope.runTest {
var latest: ContentDescription? = null
val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
- interactor.isInService.value = false
+ repository.isInService.value = false
assertThat((latest as ContentDescription.Resource).res)
.isEqualTo(PHONE_SIGNAL_STRENGTH_NONE)
@@ -330,13 +248,11 @@
var latest: ContentDescription? = null
val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
- interactor.isInService.value = true
-
- interactor.level.value = 2
+ repository.setAllLevels(2)
assertThat((latest as ContentDescription.Resource).res)
.isEqualTo(PHONE_SIGNAL_STRENGTH[2])
- interactor.level.value = 0
+ repository.setAllLevels(0)
assertThat((latest as ContentDescription.Resource).res)
.isEqualTo(PHONE_SIGNAL_STRENGTH[0])
@@ -351,7 +267,8 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+ connectionsRepository.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -364,9 +281,9 @@
@Test
fun networkType_null_whenDisabled() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(false)
- interactor.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.setDataEnabled(false)
+ connectionsRepository.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -378,9 +295,9 @@
@Test
fun networkType_null_whenCarrierNetworkChangeActive() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.carrierNetworkChangeActive.value = true
- interactor.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.carrierNetworkChangeActive.value = true
+ connectionsRepository.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -397,10 +314,10 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(true)
- interactor.isDataConnected.value = true
- interactor.mobileIsDefault.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.setDataEnabled(true)
+ repository.dataConnectionState.value = DataConnectionState.Connected
+ connectionsRepository.mobileIsDefault.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -418,15 +335,13 @@
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
assertThat(latest).isEqualTo(initial)
- interactor.isDataConnected.value = false
- yield()
+ repository.dataConnectionState.value = DataConnectionState.Disconnected
assertThat(latest).isNull()
@@ -441,15 +356,13 @@
THREE_G.dataType,
ContentDescription.Resource(THREE_G.dataContentDescription)
)
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(true)
+ repository.dataEnabled.value = true
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(expected)
- interactor.setIsDataEnabled(false)
- yield()
+ repository.dataEnabled.value = false
assertThat(latest).isNull()
@@ -459,9 +372,10 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisabled() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsDataEnabled(false)
- interactor.alwaysShowDataRatIcon.value = true
+ repository.dataEnabled.value = false
+
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -479,9 +393,11 @@
@Test
fun networkType_alwaysShow_shownEvenWhenDisconnected() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.isDataConnected.value = false
- interactor.alwaysShowDataRatIcon.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.dataConnectionState.value = DataConnectionState.Disconnected
+
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -499,9 +415,10 @@
@Test
fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.setIsFailedConnection(true)
- interactor.alwaysShowDataRatIcon.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -517,16 +434,24 @@
}
@Test
- fun networkType_alwaysShow_notShownWhenInvalidDataTypeIcon() =
+ fun networkType_alwaysShow_usesDefaultIconWhenInvalid() =
testScope.runTest {
- // The UNKNOWN icon group doesn't have a valid data type icon ID
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(UNKNOWN)
- interactor.alwaysShowDataRatIcon.value = true
+ // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the
+ // old pipeline was to use the default icon group if the map doesn't exist
+ repository.setNetworkTypeKey(UNKNOWN.name)
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
- assertThat(latest).isNull()
+ val expected =
+ Icon.Resource(
+ connectionsRepository.defaultMobileIconGroup.value.dataType,
+ ContentDescription.Resource(G.dataContentDescription)
+ )
+
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -534,9 +459,10 @@
@Test
fun networkType_alwaysShow_shownWhenNotDefault() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.mobileIsDefault.value = false
- interactor.alwaysShowDataRatIcon.value = true
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.defaultDataSubRatConfig.value =
+ MobileMappings.Config().also { it.alwaysShowDataRatIcon = true }
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -554,9 +480,9 @@
@Test
fun networkType_notShownWhenNotDefault() =
testScope.runTest {
- interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
- interactor.isDataConnected.value = true
- interactor.mobileIsDefault.value = false
+ repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
+ repository.dataConnectionState.value = DataConnectionState.Connected
+ connectionsRepository.mobileIsDefault.value = false
var latest: Icon? = null
val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
@@ -569,13 +495,14 @@
@Test
fun roaming() =
testScope.runTest {
- interactor.isRoaming.value = true
+ repository.setAllRoaming(true)
+
var latest: Boolean? = null
val job = underTest.roaming.onEach { latest = it }.launchIn(this)
assertThat(latest).isTrue()
- interactor.isRoaming.value = false
+ repository.setAllRoaming(false)
assertThat(latest).isFalse()
@@ -599,7 +526,7 @@
val containerJob =
underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = true,
hasActivityOut = true,
@@ -631,7 +558,7 @@
val containerJob =
underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = true,
hasActivityOut = false,
@@ -643,7 +570,7 @@
assertThat(outVisible).isFalse()
assertThat(containerVisible).isTrue()
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = false,
hasActivityOut = true,
@@ -653,7 +580,7 @@
assertThat(outVisible).isTrue()
assertThat(containerVisible).isTrue()
- interactor.activity.value =
+ repository.dataActivityDirection.value =
DataActivityModel(
hasActivityIn = false,
hasActivityOut = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 065dfba..e42515e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -154,33 +154,6 @@
}
@Test
- fun caching_mobileIconInteractorIsReusedForSameSubId() =
- testScope.runTest {
- val interactor1 = underTest.mobileIconInteractorForSub(1)
- val interactor2 = underTest.mobileIconInteractorForSub(1)
-
- assertThat(interactor1).isSameInstanceAs(interactor2)
- }
-
- @Test
- fun caching_invalidInteractorssAreRemovedFromCacheWhenSubDisappears() =
- testScope.runTest {
- // Retrieve interactors to trigger caching
- val interactor1 = underTest.mobileIconInteractorForSub(1)
- val interactor2 = underTest.mobileIconInteractorForSub(2)
-
- // Both impls are cached
- assertThat(underTest.mobileIconInteractorSubIdCache)
- .containsExactly(1, interactor1, 2, interactor2)
-
- // SUB_1 is removed from the list...
- interactor.filteredSubscriptions.value = listOf(SUB_2)
-
- // ... and dropped from the cache
- assertThat(underTest.mobileIconInteractorSubIdCache).containsExactly(2, interactor2)
- }
-
- @Test
fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() =
testScope.runTest {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 8f28cc0..e44ff8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -27,7 +27,7 @@
MutableStateFlow(emptySet())
override val forceHiddenSlots: StateFlow<Set<ConnectivitySlot>> = _forceHiddenIcons
- override val defaultConnections: StateFlow<DefaultConnectionModel> =
+ override val defaultConnections: MutableStateFlow<DefaultConnectionModel> =
MutableStateFlow(DefaultConnectionModel())
override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
@@ -35,4 +35,43 @@
fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
_forceHiddenIcons.value = hiddenIcons
}
+
+ /**
+ * Convenience for setting mobile data connected, disconnected, or validated. Defaults to
+ * setting mobile connected && validated, since the default state is disconnected && not
+ * validated
+ */
+ fun setMobileConnected(
+ default: Boolean = true,
+ validated: Boolean = true,
+ ) {
+ defaultConnections.value =
+ DefaultConnectionModel(
+ mobile = DefaultConnectionModel.Mobile(default),
+ isValidated = validated,
+ )
+ }
+
+ /** Similar convenience method for ethernet */
+ fun setEthernetConnected(
+ default: Boolean = true,
+ validated: Boolean = true,
+ ) {
+ defaultConnections.value =
+ DefaultConnectionModel(
+ ethernet = DefaultConnectionModel.Ethernet(default),
+ isValidated = validated,
+ )
+ }
+
+ fun setWifiConnected(
+ default: Boolean = true,
+ validated: Boolean = true,
+ ) {
+ defaultConnections.value =
+ DefaultConnectionModel(
+ wifi = DefaultConnectionModel.Wifi(default),
+ isValidated = validated,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
new file mode 100644
index 0000000..8150a31
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -0,0 +1,243 @@
+/*
+ * 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.statusbar.pipeline.shared.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.InternetTileViewModel.Companion.NOT_CONNECTED_NETWORKS_UNAVAILABLE
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class InternetTileViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: InternetTileViewModel
+ private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+ private val airplaneModeRepository = FakeAirplaneModeRepository()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private val wifiRepository = FakeWifiRepository()
+ private val userSetupRepo = FakeUserSetupRepository()
+ private val testScope = TestScope()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+ private val tableLogBuffer: TableLogBuffer = mock()
+ private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+ private val mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ private val mobileConnectionRepository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+ @Before
+ fun setUp() {
+ mobileConnectionRepository.apply {
+ setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+
+ mobileConnectionsRepository.apply {
+ activeMobileDataRepository.value = mobileConnectionRepository
+ activeMobileDataSubscriptionId.value = SUB_1_ID
+ setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+ }
+
+ mobileIconsInteractor =
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ userSetupRepo,
+ testScope.backgroundScope,
+ context,
+ )
+
+ underTest =
+ InternetTileViewModel(
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ context,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun noDefault_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+ assertThat(latest?.secondaryLabel)
+ .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ }
+
+ @Test
+ fun noDefault_networksAvailable() =
+ testScope.runTest {
+ // TODO: support [WifiInteractor.areNetworksAvailable]
+ }
+
+ @Test
+ fun wifiDefaultAndActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ // Type is [Visible] since that is the only model that stores a resId
+ val expectedIcon: WifiIcon.Visible =
+ WifiIcon.fromModel(networkModel, context) as WifiIcon.Visible
+
+ assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isEqualTo(ResourceIcon.get(expectedIcon.icon.res))
+ assertThat(latest?.iconId).isNull()
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = emptyList()
+
+ assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_networksAvailable() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+ }
+
+ @Test
+ fun mobileDefault_usesNetworkNameAndIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ assertThat(latest?.secondaryTitle).contains("test network")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isInstanceOf(SignalIcon::class.java)
+ assertThat(latest?.iconId).isNull()
+ }
+
+ @Test
+ fun ethernetDefault_validated_matchesInteractor() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(ethernetIcon!!.contentDescription.toString())
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isNull()
+ }
+
+ @Test
+ fun ethernetDefault_notValidated_matchesInteractor() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(ethernetIcon!!.contentDescription.toString())
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isNull()
+ }
+
+ companion object {
+ const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 4f7bb72..106b548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -39,6 +40,9 @@
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+ override val wifiScanResults: MutableStateFlow<List<WifiScanEntry>> =
+ MutableStateFlow(emptyList())
+
fun setIsWifiEnabled(enabled: Boolean) {
_isWifiEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index bea1154..c2e75aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -27,6 +27,7 @@
import android.net.TransportInfo
import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
+import android.net.wifi.ScanResult
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
@@ -45,6 +46,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -1205,6 +1207,58 @@
.isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
}
+ @Test
+ fun wifiScanResults_containsSsidList() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ val scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ val expected =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ WifiScanEntry(ssid = "ssid 4"),
+ WifiScanEntry(ssid = "ssid 5"),
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun wifiScanResults_updates() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ var scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ // New scan representing no results
+ scanResults = emptyList()
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ assertThat(latest).isEmpty()
+ }
+
private fun createRepo(): WifiRepositoryImpl {
return WifiRepositoryImpl(
fakeBroadcastDispatcher,
@@ -1240,6 +1294,13 @@
return callbackCaptor.value!!
}
+ private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
+ verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun createWifiNetworkCapabilities(
transportInfo: TransportInfo,
isValidated: Boolean = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index 662e36a..afab623 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
@@ -32,6 +33,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -77,6 +79,7 @@
featureFlags,
testScope.backgroundScope,
executor,
+ dispatcher,
wifiPickerTrackerFactory,
wifiManager,
logger,
@@ -1137,6 +1140,58 @@
.isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
}
+ @Test
+ fun wifiScanResults_containsSsidList() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ val scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ val expected =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ WifiScanEntry(ssid = "ssid 4"),
+ WifiScanEntry(ssid = "ssid 5"),
+ )
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun wifiScanResults_updates() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiScanResults)
+
+ var scanResults =
+ listOf(
+ ScanResult().also { it.SSID = "ssid 1" },
+ ScanResult().also { it.SSID = "ssid 2" },
+ ScanResult().also { it.SSID = "ssid 3" },
+ ScanResult().also { it.SSID = "ssid 4" },
+ ScanResult().also { it.SSID = "ssid 5" },
+ )
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ // New scan representing no results
+ scanResults = listOf()
+ whenever(wifiManager.scanResults).thenReturn(scanResults)
+ getScanResultsCallback().onScanResultsAvailable()
+
+ assertThat(latest).isEmpty()
+ }
+
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
testScope.runCurrent()
return callbackCaptor.value
@@ -1156,6 +1211,13 @@
}
}
+ private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
+ verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private companion object {
const val TITLE = "AB"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 6fe88c1..1db8065 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -21,11 +21,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -56,7 +58,8 @@
fun setUp() {
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
- underTest = WifiInteractorImpl(connectivityRepository, wifiRepository)
+ underTest =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
}
@Test
@@ -300,4 +303,76 @@
job.cancel()
}
+
+ @Test
+ fun areNetworksAvailable_noneActive_noResults() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value = emptyList()
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun areNetworksAvailable_noneActive_nonEmptyResults() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ )
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun areNetworksAvailable_activeNetwork_resultsIncludeOtherNetworks() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 1"),
+ WifiScanEntry(ssid = "ssid 2"),
+ WifiScanEntry(ssid = "ssid 3"),
+ )
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ ssid = "ssid 2",
+ networkId = 1,
+ level = 2,
+ )
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun areNetworksAvailable_activeNetwork_onlyResultIsTheActiveNetwork() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areNetworksAvailable)
+
+ wifiRepository.wifiScanResults.value =
+ listOf(
+ WifiScanEntry(ssid = "ssid 2"),
+ )
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ ssid = "ssid 2",
+ networkId = 1,
+ level = 2,
+ )
+ )
+
+ assertThat(latest).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3f49935..a0d4d13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -82,8 +82,8 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index e6724d8..1d1b84c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -40,7 +40,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -83,8 +83,8 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index bdeba2a..5aacc66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -74,7 +74,8 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
+ interactor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
airplaneModeViewModel =
AirplaneModeViewModelImpl(
AirplaneModeInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c12df98..5256245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -71,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -129,6 +131,9 @@
private FakeFeatureFlags mFeatureFlags;
private int mLongestHideShowAnimationDuration = 250;
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -242,7 +247,6 @@
| AccessibilityManager.FLAG_CONTENT_TEXT);
}
-
@Test
public void testComputeTimeout_withHovering() {
Mockito.reset(mAccessibilityMgr);
@@ -669,11 +673,12 @@
@After
public void teardown() {
- cleanUp(mDialog);
setOrientation(mOriginalOrientation);
+ mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration);
mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration);
mTestableLooper.processAllMessages();
reset(mPostureController);
+ cleanUp(mDialog);
}
private void cleanUp(VolumeDialogImpl dialog) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 3334f3e..b92d946 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -32,6 +32,8 @@
var lastWakeWhy: String? = null
var lastWakeReason: Int? = null
+ var userTouchRegistered = false
+
fun setInteractive(value: Boolean) {
_isInteractive.value = value
}
@@ -40,4 +42,8 @@
lastWakeWhy = why
lastWakeReason = wakeReason
}
+
+ override fun userTouch() {
+ userTouchRegistered = true
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 6b40f8e..4f33a97 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -37,6 +37,7 @@
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -93,6 +94,8 @@
}
}
+ val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
+
private val context = test.context
fun fakeSceneContainerRepository(
@@ -126,6 +129,7 @@
return SceneInteractor(
applicationScope = applicationScope(),
repository = repository,
+ powerRepository = powerRepository,
logger = mock(),
)
}
@@ -149,10 +153,6 @@
)
}
- fun keyguardRepository(): FakeKeyguardRepository {
- return keyguardRepository
- }
-
fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
return KeyguardInteractor(
repository = repository,
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 3fd6fe8..9bab261 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -78,11 +78,14 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import sun.misc.Unsafe;
+
/**
* <p>PinnerService pins important files for key processes in memory.</p>
* <p>Files to pin are specified in the config_defaultPinnerServiceFiles
@@ -150,6 +153,11 @@
@GuardedBy("this")
private ArraySet<Integer> mPinKeys;
+ private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB
+ private long mPinAnonSize;
+ private long mPinAnonAddress;
+ private long mCurrentlyPinnedAnonSize;
+
// Resource-configured pinner flags;
private final boolean mConfiguredToPinCamera;
private final boolean mConfiguredToPinHome;
@@ -550,6 +558,11 @@
pinKeys.add(KEY_ASSISTANT);
}
+ mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+ "pin_anon_size",
+ SystemProperties.getLong("pinner.pin_anon_size", 0));
+ mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE));
+
return pinKeys;
}
@@ -589,6 +602,7 @@
int key = currentPinKeys.valueAt(i);
pinApp(key, userHandle, true /* force */);
}
+ pinAnonRegion();
}
/**
@@ -673,6 +687,64 @@
}
/**
+ * Pin an empty anonymous region. This should only be used for ablation experiments.
+ */
+ private void pinAnonRegion() {
+ if (mPinAnonSize == 0) {
+ return;
+ }
+ long alignedPinSize = mPinAnonSize;
+ if (alignedPinSize % PAGE_SIZE != 0) {
+ alignedPinSize -= alignedPinSize % PAGE_SIZE;
+ Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize);
+ }
+ if (mPinAnonAddress != 0
+ && mCurrentlyPinnedAnonSize != alignedPinSize) {
+ unpinAnonRegion();
+ }
+ long address = 0;
+ try {
+ address = Os.mmap(0, alignedPinSize,
+ OsConstants.PROT_READ | OsConstants.PROT_WRITE,
+ OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS,
+ new FileDescriptor(), /*offset=*/0);
+
+ Unsafe tempUnsafe = null;
+ Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
+ for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
+ f.setAccessible(true);
+ Object obj = f.get(null);
+ if (clazz.isInstance(obj)) {
+ tempUnsafe = clazz.cast(obj);
+ }
+ }
+ if (tempUnsafe == null) {
+ throw new Exception("Couldn't get Unsafe");
+ }
+ Method setMemory = clazz.getMethod("setMemory", long.class, long.class, byte.class);
+ setMemory.invoke(tempUnsafe, address, alignedPinSize, (byte) 1);
+ Os.mlock(address, alignedPinSize);
+ mCurrentlyPinnedAnonSize = alignedPinSize;
+ mPinAnonAddress = address;
+ address = -1;
+ Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
+ } catch (Exception ex) {
+ Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex);
+ return;
+ } finally {
+ if (address >= 0) {
+ safeMunmap(address, alignedPinSize);
+ }
+ }
+ }
+
+ private void unpinAnonRegion() {
+ if (mPinAnonAddress != 0) {
+ safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
+ }
+ }
+
+ /**
* @return The maximum amount of bytes to be pinned for an app of type {@code key}.
*/
private int getSizeLimitForKey(@AppKey int key) {
@@ -1083,6 +1155,9 @@
totalSize += pf.bytesPinned;
}
}
+ if (mPinAnonAddress != 0) {
+ pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize);
+ }
pw.format("Total size: %s\n", totalSize);
pw.println();
if (!mPendingRepin.isEmpty()) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 6baae4b..e17424b 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -937,7 +937,7 @@
mActivity.addErrorToDropBox(
dropboxTag, null, "system_server", null, null, null,
null, report.toString(), stack, null, null, null,
- errorId);
+ errorId, null);
}
}
};
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c4816fb..5773e20 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2115,6 +2115,34 @@
mVoiceInteractionManagerProvider = provider;
}
+ /**
+ * Represents volatile states associated with a Dropbox entry.
+ * <p>
+ * These states, such as the process frozen state, can change quickly over time and thus
+ * should be captured as soon as possible to ensure accurate state. If a state is undefined,
+ * it means that the state was not read early and a fallback value can be used.
+ * </p>
+ */
+ static class VolatileDropboxEntryStates {
+ private final Boolean mIsProcessFrozen;
+
+ private VolatileDropboxEntryStates(Boolean frozenState) {
+ this.mIsProcessFrozen = frozenState;
+ }
+
+ public static VolatileDropboxEntryStates withProcessFrozenState(boolean frozenState) {
+ return new VolatileDropboxEntryStates(frozenState);
+ }
+
+ public static VolatileDropboxEntryStates emptyVolatileDropboxEnytyStates() {
+ return new VolatileDropboxEntryStates(null);
+ }
+
+ public Boolean isProcessFrozen() {
+ return mIsProcessFrozen;
+ }
+ }
+
static class MemBinder extends Binder {
ActivityManagerService mActivityManagerService;
private final PriorityDump.PriorityDumper mPriorityDumper =
@@ -8941,7 +8969,7 @@
addErrorToDropBox(
eventType, r, processName, null, null, null, null, null, null, crashInfo,
- new Float(loadingProgress), incrementalMetrics, null);
+ new Float(loadingProgress), incrementalMetrics, null, null);
// For GWP-ASan recoverable crashes, don't make the app crash (the whole point of
// 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
@@ -9052,7 +9080,7 @@
final StringBuilder sb = new StringBuilder(1024);
synchronized (sb) {
- appendDropBoxProcessHeaders(process, processName, sb);
+ appendDropBoxProcessHeaders(process, processName, null, sb);
sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
sb.append("System-App: ").append(isSystemApp).append("\n");
sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n");
@@ -9155,7 +9183,7 @@
callingPid, (r != null) ? r.getProcessClassEnum() : 0);
addErrorToDropBox("wtf", r, processName, null, null, null, tag, null, null, crashInfo,
- null, null, null);
+ null, null, null, null);
return r;
}
@@ -9180,7 +9208,7 @@
for (Pair<String, ApplicationErrorReport.CrashInfo> p = list.poll();
p != null; p = list.poll()) {
addErrorToDropBox("wtf", proc, "system_server", null, null, null, p.first, null, null,
- p.second, null, null, null);
+ p.second, null, null, null, null);
}
}
@@ -9203,7 +9231,7 @@
* to append various headers to the dropbox log text.
*/
void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
- final StringBuilder sb) {
+ final VolatileDropboxEntryStates volatileStates, final StringBuilder sb) {
// Watchdog thread ends up invoking this function (with
// a null ProcessRecord) to add the stack file to dropbox.
// Do not acquire a lock on this (am) in such cases, as it
@@ -9222,7 +9250,12 @@
sb.append("PID: ").append(process.getPid()).append("\n");
sb.append("UID: ").append(process.uid).append("\n");
if (process.mOptRecord != null) {
- sb.append("Frozen: ").append(process.mOptRecord.isFrozen()).append("\n");
+ // Use 'isProcessFrozen' from 'volatileStates' if it'snon-null (present),
+ // otherwise use 'isFrozen' from 'mOptRecord'.
+ sb.append("Frozen: ").append(
+ (volatileStates != null && volatileStates.isProcessFrozen() != null)
+ ? volatileStates.isProcessFrozen() : process.mOptRecord.isFrozen()
+ ).append("\n");
}
int flags = process.info.flags;
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -9335,7 +9368,7 @@
String subject, final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo,
@Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics,
- @Nullable UUID errorId) {
+ @Nullable UUID errorId, @Nullable VolatileDropboxEntryStates volatileStates) {
// NOTE -- this must never acquire the ActivityManagerService lock,
// otherwise the watchdog may be prevented from resetting the system.
@@ -9357,7 +9390,7 @@
if (rateLimitResult.shouldRateLimit()) return;
final StringBuilder sb = new StringBuilder(1024);
- appendDropBoxProcessHeaders(process, processName, sb);
+ appendDropBoxProcessHeaders(process, processName, volatileStates, sb);
if (process != null) {
sb.append("Foreground: ")
.append(process.isInterestingToUserLocked() ? "Yes" : "No")
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index e7f4bf9..46e5523 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1843,7 +1843,8 @@
dropBuilder.append(catSw.toString());
FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED);
mService.addErrorToDropBox("lowmem", null, "system_server", null,
- null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null);
+ null, null, tag.toString(), dropBuilder.toString(), null, null, null, null, null,
+ null);
synchronized (mService) {
long now = SystemClock.uptimeMillis();
if (mLastMemUsageReportTime < now) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 09df277..40b1de6 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -297,6 +297,7 @@
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseBooleanArray lastPids = new SparseBooleanArray(20);
+ ActivityManagerService.VolatileDropboxEntryStates volatileDropboxEntriyStates = null;
mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
latencyTracker.waitingOnAMSLockStarted();
@@ -343,6 +344,9 @@
synchronized (mProcLock) {
latencyTracker.waitingOnProcLockEnded();
setNotResponding(true);
+ volatileDropboxEntriyStates =
+ ActivityManagerService.VolatileDropboxEntryStates
+ .withProcessFrozenState(mApp.mOptRecord.isFrozen());
}
// Log the ANR to the event log.
@@ -620,7 +624,8 @@
? (ProcessRecord) parentProcess.mOwner : null;
mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
parentShortComponentName, parentPr, null, report.toString(), tracesFile,
- null, new Float(loadingProgress), incrementalMetrics, errorId);
+ null, new Float(loadingProgress), incrementalMetrics, errorId,
+ volatileDropboxEntriyStates);
if (mApp.getWindowProcessController().appNotResponding(info.toString(),
() -> {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 97fb0e7..3d11c68 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5643,7 +5643,7 @@
if (logToDropbox) {
final long now = SystemClock.elapsedRealtime();
final StringBuilder sb = new StringBuilder();
- mService.appendDropBoxProcessHeaders(app, app.processName, sb);
+ mService.appendDropBoxProcessHeaders(app, app.processName, null, sb);
sb.append("Reason: " + reason).append("\n");
sb.append("Requester UID: " + requester).append("\n");
dbox.addText(DROPBOX_TAG_IMPERCEPTIBLE_KILL, sb.toString());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6f0a4b4..be6133b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2328,8 +2328,7 @@
mRankingHandler = rankingHandler;
mConditionProviders = conditionProviders;
mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
- new SysUiStatsEvent.BuilderFactory(), flagResolver,
- new ZenModeEventLogger(mPackageManagerClient));
+ flagResolver, new ZenModeEventLogger(mPackageManagerClient));
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -2390,7 +2389,6 @@
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- new SysUiStatsEvent.BuilderFactory(),
mShowReviewPermissionsNotification);
mRankingHelper = new RankingHelper(getContext(),
mRankingHandler,
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0e37f10..b132a23 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -25,7 +25,6 @@
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -78,6 +77,7 @@
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
@@ -169,7 +169,6 @@
* fields.
*/
private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
- private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
/**
* All user-lockable fields for a given application.
@@ -208,7 +207,6 @@
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
boolean showReviewPermissionsNotification) {
mContext = context;
mZenModeHelper = zenHelper;
@@ -219,7 +217,6 @@
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
mUserProfiles = userProfiles;
- mStatsEventBuilderFactory = statsEventBuilderFactory;
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
XML_VERSION = 4;
@@ -2190,11 +2187,7 @@
break;
}
pulledEvents++;
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
final PackagePreferences r = mPackagePreferences.valueAt(i);
- event.writeInt(r.uid);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
// collect whether this package's importance info was user-set for later, if needed
// before the migration is enabled, this will simply default to false in all cases.
@@ -2214,15 +2207,7 @@
pkgsWithPermissionsToHandle.remove(key);
}
- event.writeInt(importance);
- event.writeInt(r.visibility);
- event.writeInt(r.lockedAppFields);
-
- // optional bool user_set_importance = 5;
- event.writeBoolean(importanceIsUserSet);
-
- // optional FsiState fsi_state = 6;
final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
.isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
@@ -2232,20 +2217,23 @@
final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
isStickyHunFlagEnabled);
- event.writeInt(fsiState);
-
- // optional bool is_fsi_permission_user_set = 7;
final int currentPermissionFlags = mPm.getPermissionFlags(
android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
UserHandle.getUserHandleForUid(r.uid));
- final boolean isUserSet =
+ final boolean fsiIsUserSet =
isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
isStickyHunFlagEnabled);
- event.writeBoolean(isUserSet);
-
- events.add(event.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
+ /* optional int32 importance = 2 */ importance,
+ /* optional int32 visibility = 3 */ r.visibility,
+ /* optional int32 user_locked_fields = 4 */ r.lockedAppFields,
+ /* optional bool user_set_importance = 5 */ importanceIsUserSet,
+ /* optional FsiState fsi_state = 6 */ fsiState,
+ /* optional bool is_fsi_permission_user_set = 7 */ fsiIsUserSet));
}
}
@@ -2256,18 +2244,18 @@
break;
}
pulledEvents++;
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
- event.writeInt(p.first);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeInt(pkgPermissions.get(p).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
-
- // fill out the rest of the fields with default values so as not to confuse the
- // builder
- event.writeInt(DEFAULT_VISIBILITY);
- event.writeInt(DEFAULT_LOCKED_APP_FIELDS);
- event.writeBoolean(pkgPermissions.get(p).second); // user_set_importance field
- events.add(event.build());
+ // Because all fields are required in FrameworkStatsLog.buildStatsEvent, we have
+ // to fill in default values for all the unspecified fields.
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ p.first,
+ /* optional int32 importance = 2 */ pkgPermissions.get(p).first
+ ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE,
+ /* optional int32 visibility = 3 */ DEFAULT_VISIBILITY,
+ /* optional int32 user_locked_fields = 4 */ DEFAULT_LOCKED_APP_FIELDS,
+ /* optional bool user_set_importance = 5 */ pkgPermissions.get(p).second,
+ /* optional FsiState fsi_state = 6 */ 0,
+ /* optional bool is_fsi_permission_user_set = 7 */ false));
}
}
}
@@ -2288,20 +2276,21 @@
if (++totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
break;
}
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
- event.writeInt(r.uid);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeString(channel.getId());
- event.writeString(channel.getName().toString());
- event.writeString(channel.getDescription());
- event.writeInt(channel.getImportance());
- event.writeInt(channel.getUserLockedFields());
- event.writeBoolean(channel.isDeleted());
- event.writeBoolean(channel.getConversationId() != null);
- event.writeBoolean(channel.isDemoted());
- event.writeBoolean(channel.isImportantConversation());
- events.add(event.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
+ /* optional string channel_id = 2 */ channel.getId(),
+ /* optional string channel_name = 3 */ channel.getName().toString(),
+ /* optional string description = 4 */ channel.getDescription(),
+ /* optional int32 importance = 5 */ channel.getImportance(),
+ /* optional int32 user_locked_fields = 6 */
+ channel.getUserLockedFields(),
+ /* optional bool is_deleted = 7 */ channel.isDeleted(),
+ /* optional bool is_conversation = 8 */
+ channel.getConversationId() != null,
+ /* optional bool is_demoted_conversation = 9 */ channel.isDemoted(),
+ /* optional bool is_important_conversation = 10 */
+ channel.isImportantConversation()));
}
}
}
@@ -2323,16 +2312,15 @@
if (++totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
break;
}
- SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
- event.writeInt(r.uid);
- event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- event.writeString(groupChannel.getId());
- event.writeString(groupChannel.getName().toString());
- event.writeString(groupChannel.getDescription());
- event.writeBoolean(groupChannel.isBlocked());
- event.writeInt(groupChannel.getUserLockedFields());
- events.add(event.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(
+ PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES,
+ /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
+ /* optional string group_id = 2 */ groupChannel.getId(),
+ /* optional string group_name = 3 */ groupChannel.getName().toString(),
+ /* optional string description = 4 */ groupChannel.getDescription(),
+ /* optional bool is_blocked = 5 */ groupChannel.isBlocked(),
+ /* optional int32 user_locked_fields = 6 */
+ groupChannel.getUserLockedFields()));
}
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1f5bd3e..e490745 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,7 +21,6 @@
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
@@ -81,6 +80,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -116,7 +116,6 @@
private final SettingsObserver mSettingsObserver;
private final AppOpsManager mAppOps;
private final NotificationManager mNotificationManager;
- private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
private ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final ZenModeFiltering mFiltering;
@@ -152,7 +151,6 @@
private String[] mPriorityOnlyDndExemptPackages;
public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders,
- SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
ZenModeEventLogger zenModeEventLogger) {
mContext = context;
@@ -174,7 +172,6 @@
mFiltering = new ZenModeFiltering(mContext);
mConditions = new ZenModeConditions(this, conditionProviders);
mServiceConfig = conditionProviders.getConfig();
- mStatsEventBuilderFactory = statsEventBuilderFactory;
mFlagResolver = flagResolver;
mZenModeEventLogger = zenModeEventLogger;
}
@@ -1314,17 +1311,14 @@
for (int i = 0; i < numConfigs; i++) {
final int user = mConfigs.keyAt(i);
final ZenModeConfig config = mConfigs.valueAt(i);
- SysUiStatsEvent.Builder data = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(DND_MODE_RULE)
- .writeInt(user)
- .writeBoolean(config.manualRule != null) // enabled
- .writeBoolean(config.areChannelsBypassingDnd)
- .writeInt(ROOT_CONFIG)
- .writeString("") // name, empty for root config
- .writeInt(Process.SYSTEM_UID) // system owns root config
- .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
- .writeByteArray(config.toZenPolicy().toProto());
- events.add(data.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE,
+ /* optional int32 user = 1 */ user,
+ /* optional bool enabled = 2 */ config.manualRule != null,
+ /* optional bool channels_bypassing = 3 */ config.areChannelsBypassingDnd,
+ /* optional LoggedZenMode zen_mode = 4 */ ROOT_CONFIG,
+ /* optional string id = 5 */ "", // empty for root config
+ /* optional int32 uid = 6 */ Process.SYSTEM_UID, // system owns root config
+ /* optional DNDPolicyProto policy = 7 */ config.toZenPolicy().toProto()));
if (config.manualRule != null) {
ruleToProtoLocked(user, config.manualRule, true, events);
}
@@ -1355,21 +1349,18 @@
}
SysUiStatsEvent.Builder data;
- data = mStatsEventBuilderFactory.newBuilder()
- .setAtomId(DND_MODE_RULE)
- .writeInt(user)
- .writeBoolean(rule.enabled)
- .writeBoolean(false) // channels_bypassing unused for rules
- .writeInt(rule.zenMode)
- .writeString(id)
- .writeInt(getPackageUid(pkg, user))
- .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
byte[] policyProto = new byte[]{};
if (rule.zenPolicy != null) {
policyProto = rule.zenPolicy.toProto();
}
- data.writeByteArray(policyProto);
- events.add(data.build());
+ events.add(FrameworkStatsLog.buildStatsEvent(DND_MODE_RULE,
+ /* optional int32 user = 1 */ user,
+ /* optional bool enabled = 2 */ rule.enabled,
+ /* optional bool channels_bypassing = 3 */ false, // unused for rules
+ /* optional android.stats.dnd.ZenMode zen_mode = 4 */ rule.zenMode,
+ /* optional string id = 5 */ id,
+ /* optional int32 uid = 6 */ getPackageUid(pkg, user),
+ /* optional DNDPolicyProto policy = 7 */ policyProto));
}
private int getPackageUid(String pkg, int user) {
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index a2177e8..0bb969f 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -17,13 +17,13 @@
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.os.Build;
import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
import libcore.io.IoUtils;
@@ -82,8 +82,8 @@
}
AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
mCachedAssetManager = assets;
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index 1a8c1996..56d92fb 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,8 +80,8 @@
private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
final AssetManager assets = new AssetManager();
- assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- Build.VERSION.RESOURCES_SDK_INT);
+ assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, Build.VERSION.RESOURCES_SDK_INT);
assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
return assets;
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 7ea9870..6a2b824 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3079,7 +3079,7 @@
}
FrameworkStatsLog.write(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT, eventType, inputState,
- inputType, displayName, vendorId, hdmiPort, tifSessionId);
+ inputType, vendorId, hdmiPort, tifSessionId, displayName);
}
private static final class UserState {
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4b65895..2dacda0 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -34,6 +34,7 @@
"platform-test-annotations",
"platformprotosnano",
"statsdprotolite",
+ "StatsdTestUtils",
"hamcrest-library",
"servicestests-utils",
"testables",
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 9b745f5..020afdb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -44,22 +44,11 @@
import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_SYSTEM;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
-import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
-import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IMPORTANCE_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_CONVERSATION_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DELETED_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DEMOTED_CONVERSATION_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_IMPORTANT_CONVERSATION_FIELD_NUMBER;
-import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
@@ -134,6 +123,7 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
@@ -144,11 +134,14 @@
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
import com.google.common.collect.ImmutableList;
+import com.google.protobuf.InvalidProtocolBufferException;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -218,7 +211,6 @@
private PreferencesHelper mHelper;
private AudioAttributes mAudioAttributes;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
- private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
@Before
public void setUp() throws Exception {
@@ -331,11 +323,9 @@
when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
- mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
-
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, false);
+ false);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -683,7 +673,7 @@
public void testReadXml_oldXml_migrates() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -825,7 +815,7 @@
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -883,7 +873,7 @@
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ false);
+ /* showReviewPermissionsNotification= */ false);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -941,7 +931,7 @@
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true);
+ /* showReviewPermissionsNotification= */ true);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1521,7 +1511,7 @@
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mStatsEventBuilderFactory, false);
+ false);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -5392,7 +5382,7 @@
}
@Test
- public void testPullPackageChannelPreferencesStats() {
+ public void testPullPackageChannelPreferencesStats() throws InvalidProtocolBufferException {
String channelId = "parent";
String name = "messages";
NotificationChannel fodderA = new NotificationChannel("a", "a", IMPORTANCE_LOW);
@@ -5406,25 +5396,40 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- int found = 0;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- ++found;
- assertEquals("uid", UID_O, builder.getValue(UID_FIELD_NUMBER));
- assertTrue("uid annotation", builder.getBooleanAnnotation(
- UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID));
- assertEquals("importance", IMPORTANCE_DEFAULT, builder.getValue(
- IMPORTANCE_FIELD_NUMBER));
- assertEquals("name", name, builder.getValue(CHANNEL_NAME_FIELD_NUMBER));
- assertFalse("isconv", builder.getBoolean(IS_CONVERSATION_FIELD_NUMBER));
- assertFalse("deleted", builder.getBoolean(IS_DELETED_FIELD_NUMBER));
+ // number of channels with preferences should be 3 total
+ assertEquals("expected number of events", 3, events.size());
+ for (StatsEvent ev : events) {
+ // all of these events should be of PackageNotificationChannelPreferences type,
+ // and therefore we expect the atom to have this field.
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+
+ // uid is shared across all channels; conversation & deleted are not set in any of
+ // these channels; beyond that check individual channel properties
+ assertEquals("uid", UID_O, p.getUid());
+ assertFalse("is conversation", p.getIsConversation());
+ assertFalse("is deleted", p.getIsDeleted());
+
+ String eventChannelId = p.getChannelId();
+ if (eventChannelId.equals(channelId)) {
+ assertEquals("channel name", name, p.getChannelName());
+ assertEquals("importance", IMPORTANCE_DEFAULT, p.getImportance());
+ assertFalse("is conversation", p.getIsConversation());
+ } else if (eventChannelId.equals("a")) {
+ assertEquals("channel name", "a", p.getChannelName());
+ assertEquals("importance", IMPORTANCE_LOW, p.getImportance());
+ } else { // b
+ assertEquals("channel name", "b", p.getChannelName());
+ assertEquals("importance", IMPORTANCE_HIGH, p.getImportance());
}
}
}
@Test
- public void testPullPackageChannelPreferencesStats_one_to_one() {
+ public void testPullPackageChannelPreferencesStats_one_to_one()
+ throws InvalidProtocolBufferException {
NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_LOW);
mHelper.createNotificationChannel(PKG_O, UID_O, channelA, true, false, UID_O, false);
NotificationChannel channelB = new NotificationChannel("b", "b", IMPORTANCE_LOW);
@@ -5437,19 +5442,22 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- int found = 0;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES) {
- Object id = builder.getValue(CHANNEL_ID_FIELD_NUMBER);
- assertTrue("missing channel in the output", channels.contains(id));
- channels.remove(id);
- }
+ assertEquals("total events", 3, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+ String id = p.getChannelId();
+ assertTrue("missing channel in the output", channels.contains(id));
+ channels.remove(id);
}
assertTrue("unexpected channel in output", channels.isEmpty());
}
@Test
- public void testPullPackageChannelPreferencesStats_conversation() {
+ public void testPullPackageChannelPreferencesStats_conversation()
+ throws InvalidProtocolBufferException {
String conversationId = "friend";
NotificationChannel parent =
@@ -5467,21 +5475,25 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- assertTrue("isConveration should be true", builder.getBoolean(
- IS_CONVERSATION_FIELD_NUMBER));
- assertFalse("not demoted", builder.getBoolean(
- IS_DEMOTED_CONVERSATION_FIELD_NUMBER));
- assertFalse("not important", builder.getBoolean(
- IS_IMPORTANT_CONVERSATION_FIELD_NUMBER));
+ // In this case, we want to check the properties of the conversation channel (not parent)
+ assertEquals("total events", 2, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+
+ if (channelId.equals(p.getChannelId())) {
+ assertTrue("isConversation should be true", p.getIsConversation());
+ assertFalse("not demoted", p.getIsDemotedConversation());
+ assertFalse("not important", p.getIsImportantConversation());
}
}
}
@Test
- public void testPullPackageChannelPreferencesStats_conversation_demoted() {
+ public void testPullPackageChannelPreferencesStats_conversation_demoted()
+ throws InvalidProtocolBufferException {
NotificationChannel parent =
new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false, UID_O, false);
@@ -5496,21 +5508,23 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- assertTrue("isConveration should be true", builder.getBoolean(
- IS_CONVERSATION_FIELD_NUMBER));
- assertTrue("is demoted", builder.getBoolean(
- IS_DEMOTED_CONVERSATION_FIELD_NUMBER));
- assertFalse("not important", builder.getBoolean(
- IS_IMPORTANT_CONVERSATION_FIELD_NUMBER));
+ assertEquals("total events", 2, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+ if (channelId.equals(p.getChannelId())) {
+ assertTrue("isConversation should be true", p.getIsConversation());
+ assertTrue("is demoted", p.getIsDemotedConversation());
+ assertFalse("not important", p.getIsImportantConversation());
}
}
}
@Test
- public void testPullPackageChannelPreferencesStats_conversation_priority() {
+ public void testPullPackageChannelPreferencesStats_conversation_priority()
+ throws InvalidProtocolBufferException {
NotificationChannel parent =
new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false, UID_O, false);
@@ -5525,21 +5539,23 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackageChannelPreferencesStats(events);
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES
- && channelId.equals(builder.getValue(CHANNEL_ID_FIELD_NUMBER))) {
- assertTrue("isConveration should be true", builder.getBoolean(
- IS_CONVERSATION_FIELD_NUMBER));
- assertFalse("not demoted", builder.getBoolean(
- IS_DEMOTED_CONVERSATION_FIELD_NUMBER));
- assertTrue("is important", builder.getBoolean(
- IS_IMPORTANT_CONVERSATION_FIELD_NUMBER));
+ assertEquals("total events", 2, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationChannelPreferences());
+ PackageNotificationChannelPreferences p =
+ atom.getPackageNotificationChannelPreferences();
+ if (channelId.equals(p.getChannelId())) {
+ assertTrue("isConversation should be true", p.getIsConversation());
+ assertFalse("not demoted", p.getIsDemotedConversation());
+ assertTrue("is important", p.getIsImportantConversation());
}
}
}
@Test
- public void testPullPackagePreferencesStats_postPermissionMigration() {
+ public void testPullPackagePreferencesStats_postPermissionMigration()
+ throws InvalidProtocolBufferException {
// make sure there's at least one channel for each package we want to test
NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channelA, true, false,
@@ -5568,23 +5584,18 @@
ArrayList<StatsEvent> events = new ArrayList<>();
mHelper.pullPackagePreferencesStats(events, appPermissions);
- int found = 0;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) {
- ++found;
- int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER);
- boolean userSet = builder.getBoolean(
- PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
+ assertEquals("total number of packages", 3, events.size());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasPackageNotificationPreferences());
+ PackageNotificationPreferences p = atom.getPackageNotificationPreferences();
+ int uid = p.getUid();
- // if it's one of the expected ids, then make sure the importance matches
- assertTrue(expected.containsKey(uid));
- assertThat(expected.get(uid).first).isEqualTo(
- builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
- assertThat(expected.get(uid).second).isEqualTo(userSet);
- }
+ // if it's one of the expected ids, then make sure the importance matches
+ assertTrue(expected.containsKey(uid));
+ assertThat(expected.get(uid).first).isEqualTo(p.getImportance());
+ assertThat(expected.get(uid).second).isEqualTo(p.getUserSetImportance());
}
- // should have at least one entry for each of the packages we expected to see
- assertThat(found).isAtLeast(3);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
deleted file mode 100644
index 89adc72..0000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.util.StatsEvent;
-
-import com.android.server.notification.SysUiStatsEvent.Builder;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Wrapper for SysUiStatsEvent that implements validation.
- */
-public class WrappedSysUiStatsEvent {
-
- static class WrappedBuilder extends Builder {
- private ArrayList<Object> mValues;
- private HashMap<Integer, HashMap<Byte, Object>> mAnnotations;
- private int mAtomId;
- private int mLastIndex;
- private boolean mBuilt;
-
- WrappedBuilder(StatsEvent.Builder builder) {
- super(builder);
- mValues = new ArrayList<>();
- mAnnotations = new HashMap<>();
- mValues.add(0); // proto fields are 1-based
- }
-
- @Override
- public Builder setAtomId(int atomId) {
- mAtomId = atomId;
- super.setAtomId(atomId);
- return this;
- }
-
- @Override
- public Builder writeInt(int value) {
- addValue(Integer.valueOf(value));
- super.writeInt(value);
- return this;
- }
-
- @Override
- public Builder addBooleanAnnotation(byte annotation, boolean value) {
- addAnnotation(annotation, Boolean.valueOf(value));
- super.addBooleanAnnotation(annotation, value);
- return this;
- }
-
- @Override
- public Builder writeString(String value) {
- addValue(value);
- super.writeString(value);
- return this;
- }
-
- @Override
- public Builder writeBoolean(boolean value) {
- addValue(Boolean.valueOf(value));
- super.writeBoolean(value);
- return this;
- }
-
- @Override
- public StatsEvent build() {
- mBuilt = true;
- return super.build();
- }
-
- public Object getValue(int index) {
- return index < mValues.size() ? mValues.get(index) : null;
- }
-
- /** useful to make assertTrue() statements more readable. */
- public boolean getBoolean(int index) {
- return (Boolean) mValues.get(index);
- }
-
- /** useful to make assertTrue() statements more readable. */
- public int getInt(int index) {
- return (Integer) mValues.get(index);
- }
-
- /** useful to make assertTrue() statements more readable. */
- public String getString(int index) {
- return (String) mValues.get(index);
- }
-
- private void addValue(Object value) {
- mLastIndex = mValues.size();
- mValues.add(value);
- }
-
- private void addAnnotation(byte annotation, Object value) {
- Integer key = Integer.valueOf(mLastIndex);
- if (!mAnnotations.containsKey(key)) {
- mAnnotations.put(key, new HashMap<>());
- }
- mAnnotations.get(key).put(Byte.valueOf(annotation), value);
- }
-
- public boolean getBooleanAnnotation(int i, byte a) {
- return ((Boolean) mAnnotations.get(Integer.valueOf(i)).get(Byte.valueOf(a)))
- .booleanValue();
- }
-
- public int getAtomId() {
- return mAtomId;
- }
- }
-
- static class WrappedBuilderFactory extends SysUiStatsEvent.BuilderFactory {
- public List<WrappedBuilder> builders;
-
- WrappedBuilderFactory() {
- builders = new ArrayList<>();
- }
-
- @Override
- Builder newBuilder() {
- WrappedBuilder b = new WrappedBuilder(StatsEvent.newBuilder());
- builders.add(b);
- return b;
- }
- }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 3ee75de..e540068 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -33,18 +33,12 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
-import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
-import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
-import static com.android.os.dnd.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.ENABLED_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.ID_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.UID_FIELD_NUMBER;
-import static com.android.os.dnd.DNDModeProto.ZEN_MODE_FIELD_NUMBER;
import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
@@ -73,6 +67,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.SuppressLint;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -106,6 +101,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
import android.util.Xml;
import com.android.internal.R;
@@ -113,12 +109,15 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.os.AtomsProto;
+import com.android.os.dnd.DNDModeProto;
import com.android.os.dnd.DNDPolicyProto;
import com.android.os.dnd.DNDProtoEnums;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.google.common.collect.ImmutableList;
+import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Before;
import org.junit.Test;
@@ -140,13 +139,13 @@
import java.util.Objects;
@SmallTest
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
- private static final int ZEN_MODE_FOR_TESTING = 99;
private static final String CUSTOM_PKG_NAME = "not.android";
private static final int CUSTOM_PKG_UID = 1;
private static final String CUSTOM_RULE_ID = "custom_rule";
@@ -159,7 +158,6 @@
private ZenModeHelper mZenModeHelper;
private ContentResolver mContentResolver;
@Mock AppOpsManager mAppOps;
- private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
ZenModeEventLoggerFake mZenModeEventLogger;
@@ -178,7 +176,6 @@
Log.d("ZenModeHelperTest", "Couldn't mock default zen mode config xml file err=" +
e.toString());
}
- mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps);
when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
@@ -188,8 +185,7 @@
mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
mZenModeEventLogger = new ZenModeEventLoggerFake(mPackageManager);
mZenModeHelper = new ZenModeHelper(mContext, mTestableLooper.getLooper(),
- mConditionProviders, mStatsEventBuilderFactory, mTestFlagResolver,
- mZenModeEventLogger);
+ mConditionProviders, mTestFlagResolver, mZenModeEventLogger);
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = new ActivityInfo();
@@ -911,37 +907,35 @@
}
@Test
- public void testProto() {
+ public void testProto() throws InvalidProtocolBufferException {
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
// existence of manual rule means it should be in output
mZenModeHelper.mConfig.manualRule = new ZenModeConfig.ZenRule();
mZenModeHelper.mConfig.manualRule.pkg = "android"; // system
+ mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
- int n = mZenModeHelper.mConfig.automaticRules.size();
- List<String> ids = new ArrayList<>(n);
- for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
- ids.add(rule.id);
- }
+ List<String> ids = new ArrayList<>();
ids.add(ZenModeConfig.MANUAL_RULE_ID);
ids.add(""); // for ROOT_CONFIG, logged with empty string as id
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- assertEquals(n + 2, events.size()); // automatic rules + manual rule + root config
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE) {
- if (builder.getInt(ZEN_MODE_FIELD_NUMBER) == ROOT_CONFIG) {
- assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
- assertFalse(builder.getBoolean(CHANNELS_BYPASSING_FIELD_NUMBER));
- }
- assertEquals(Process.SYSTEM_UID, builder.getInt(UID_FIELD_NUMBER));
- assertTrue(builder.getBooleanAnnotation(UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID));
- String name = (String) builder.getValue(ID_FIELD_NUMBER);
- assertTrue("unexpected rule id", ids.contains(name));
- ids.remove(name);
- } else {
- fail("unexpected atom id: " + builder.getAtomId());
+ assertEquals(2, events.size()); // manual rule + root config
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ // Additional check for ID to clearly identify the root config because there's some
+ // odd behavior in the test util around enum value of 0 (the usual default, but not in
+ // this case).
+ if (cfg.getZenMode().getNumber() == ROOT_CONFIG && cfg.getId().equals("")) {
+ assertTrue(cfg.getEnabled());
+ assertFalse(cfg.getChannelsBypassing());
}
+ assertEquals(Process.SYSTEM_UID, cfg.getUid());
+ String name = cfg.getId();
+ assertTrue("unexpected rule id", ids.contains(name));
+ ids.remove(name);
}
assertEquals("extra rule in output", 0, ids.size());
}
@@ -949,28 +943,61 @@
@Test
public void testProtoWithAutoRule() throws Exception {
setupZenConfig();
- // one enabled automatic rule
- mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_FOR_TESTING);
+ // one enabled automatic rule. we use a non-usual zen mode value (though it has to be
+ // a real one in the enum because non-valid enum values are reverted to default).
+ mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_ALARMS);
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
boolean foundCustomEvent = false;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE) {
- if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) {
- foundCustomEvent = true;
- assertEquals(CUSTOM_PKG_UID, builder.getInt(UID_FIELD_NUMBER));
- assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
- }
- } else {
- fail("unexpected atom id: " + builder.getAtomId());
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if (cfg.getZenMode().getNumber() == ZEN_MODE_ALARMS) {
+ foundCustomEvent = true;
+ assertEquals(CUSTOM_PKG_UID, cfg.getUid());
+ assertTrue(cfg.getEnabled());
}
}
assertTrue("couldn't find custom rule", foundCustomEvent);
}
@Test
+ public void testProtoWithDefaultAutoRules() throws Exception {
+ setupZenConfig();
+ // clear the automatic rules so we can reset to only the default rules
+ mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
+
+ // read in XML to restore the default rules
+ ByteArrayOutputStream baos = writeXmlAndPurge(5);
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+ List<StatsEvent> events = new LinkedList<>();
+ mZenModeHelper.pullRules(events);
+
+ // list for tracking which ids we've seen in the pulled atom output
+ List<String> ids = new ArrayList<>();
+ ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS);
+ ids.add(""); // empty string for root config
+
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if (!ids.contains(cfg.getId())) {
+ fail("unexpected ID found: " + cfg.getId());
+ }
+ ids.remove(cfg.getId());
+ }
+ assertEquals("default ID(s) not found", 0, ids.size());
+ }
+
+ @Test
public void ruleUidsCached() throws Exception {
setupZenConfig();
// one enabled automatic rule
@@ -1019,10 +1046,11 @@
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- boolean foundCustomEvent = false;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE
- && "customRule".equals(builder.getString(ID_FIELD_NUMBER))) {
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if ("customRule".equals(cfg.getId())) {
fail("non-default IDs should be redacted");
}
}
@@ -1039,14 +1067,17 @@
mZenModeHelper.pullRules(events);
boolean foundManualRule = false;
- for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
- if (builder.getAtomId() == DND_MODE_RULE
- && ZenModeConfig.MANUAL_RULE_ID.equals(builder.getString(ID_FIELD_NUMBER))) {
- assertEquals(0, builder.getInt(UID_FIELD_NUMBER));
+ for (StatsEvent ev : events) {
+ AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+ assertTrue(atom.hasDndModeRule());
+ DNDModeProto cfg = atom.getDndModeRule();
+ if (ZenModeConfig.MANUAL_RULE_ID.equals(cfg.getId())) {
+ assertEquals(0, cfg.getUid());
foundManualRule = true;
}
}
- assertTrue("couldn't find manual rule", foundManualRule); }
+ assertTrue("couldn't find manual rule", foundManualRule);
+ }
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 4473a31..c84fe08 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,7 +123,7 @@
controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
mWaitingDisplaySession);
- verify(mVirtualDisplayContent).updateRecording();
+ verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
// WHEN updating the session on the same display, so no longer waiting to record.
ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(