diff options
137 files changed, 3469 insertions, 1903 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8c00c6a4bfd8..c0e89d2c4a05 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3452,7 +3452,7 @@ package android.window { public class WindowOrganizer { ctor public WindowOrganizer(); - method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction); } diff --git a/core/java/android/window/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java index 12ad91498626..c8f632707966 100644 --- a/core/java/android/window/TaskFragmentAnimationParams.java +++ b/core/java/android/window/TaskFragmentAnimationParams.java @@ -33,6 +33,13 @@ public final class TaskFragmentAnimationParams implements Parcelable { public static final TaskFragmentAnimationParams DEFAULT = new TaskFragmentAnimationParams.Builder().build(); + /** + * The default value for animation background color, which means to use the theme window + * background color. + */ + @ColorInt + public static final int DEFAULT_ANIMATION_BACKGROUND_COLOR = 0; + @ColorInt private final int mAnimationBackgroundColor; @@ -104,12 +111,13 @@ public final class TaskFragmentAnimationParams implements Parcelable { public static final class Builder { @ColorInt - private int mAnimationBackgroundColor = 0; + private int mAnimationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR; /** * Sets the {@link ColorInt} to use for the background during the animation with this * TaskFragment if the animation requires a background. The default value is - * {@code 0}, which is to use the theme window background. + * {@link #DEFAULT_ANIMATION_BACKGROUND_COLOR}, which is to use the theme window background + * color. * * @param color a packed color int, {@code AARRGGBB}, for the animation background color. * @return this {@link Builder}. diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index 2a80d021abd6..740fbacbbfcc 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -61,9 +61,7 @@ public class WindowOrganizer { * Apply multiple WindowContainer operations at once. * * Note that using this API requires the caller to hold - * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using - * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is - * created by itself. + * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}. * * @param t The transaction to apply. * @param callback This transaction will use the synchronization scheme described in @@ -72,8 +70,7 @@ public class WindowOrganizer { * @return An ID for the sync operation which will later be passed to transactionReady callback. * This lets the caller differentiate overlapping sync operations. */ - @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS, - conditional = true) + @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull WindowContainerTransaction t, @NonNull WindowContainerTransactionCallback callback) { try { diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java index 205c5fd735ea..d2b612a9e6f3 100644 --- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -73,6 +73,23 @@ public class GestureNavigationSettingsObserver extends ContentObserver { mOnPropertiesChangedListener); } + public void registerForCurrentUser() { + ContentResolver r = mContext.getContentResolver(); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT), + false, this); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT), + false, this); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), + false, this); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_SYSTEMUI, + runnable -> mMainHandler.post(runnable), + mOnPropertiesChangedListener); + } + public void unregister() { mContext.getContentResolver().unregisterContentObserver(this); DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b1610d790222..8952f37b1469 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -428,7 +428,7 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj, jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return 0; } else if (err != NO_ERROR) { - jniThrowException(env, OutOfResourcesException, NULL); + jniThrowException(env, OutOfResourcesException, statusToString(err).c_str()); return 0; } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dffd1cc9e217..dafa0ad7989f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6084,6 +6084,12 @@ different from the home screen wallpaper. --> <bool name="config_independentLockscreenLiveWallpaper">false</bool> + <!-- Whether the vendor power press code need to be mapped. --> + <bool name="config_powerPressMapping">false</bool> + + <!-- Power press vendor code. --> + <integer name="config_powerPressCode">-1</integer> + <!-- Whether to show weather on the lock screen by default. --> <bool name="config_lockscreenWeatherEnabledByDefault">false</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9cc8aa81419d..591ba5feeee9 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2655,6 +2655,8 @@ <java-symbol type="integer" name="config_sideFpsToastTimeout"/> <java-symbol type="integer" name="config_sidefpsSkipWaitForPowerAcquireMessage"/> <java-symbol type="integer" name="config_sidefpsSkipWaitForPowerVendorAcquireMessage"/> + <java-symbol type="integer" name="config_powerPressCode"/> + <java-symbol type="bool" name="config_powerPressMapping"/> <!-- Clickable toast used during sidefps enrollment --> <java-symbol type="layout" name="side_fps_toast" /> diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 68523209c9cc..c3b6916121d0 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar 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 111cfd8fc3c1..f11836ea5bee 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 @@ -650,7 +650,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo); - mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER); // If the displayId of the task is different than what PipBoundsHandler has, then update // it. This is possible if we entered PiP on an external display. @@ -659,6 +658,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mOnDisplayIdChangeCallback.accept(info.displayId); } + // UiEvent logging. + final PipUiEventLogger.PipUiEventEnum uiEventEnum; + if (isLaunchIntoPipTask()) { + uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER_CONTENT_PIP; + } else if (mPipTransitionState.getInSwipePipToHomeTransition()) { + uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER; + } else { + uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER; + } + mPipUiEventLoggerLogger.log(uiEventEnum); + if (mPipTransitionState.getInSwipePipToHomeTransition()) { if (!mWaitForFixedRotation) { onEndOfSwipePipToHomeTransition(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java index 513ebba59258..3e5a19b69a59 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java @@ -78,6 +78,12 @@ public class PipUiEventLogger { @UiEvent(doc = "Activity enters picture-in-picture mode") PICTURE_IN_PICTURE_ENTER(603), + @UiEvent(doc = "Activity enters picture-in-picture mode with auto-enter-pip API") + PICTURE_IN_PICTURE_AUTO_ENTER(1313), + + @UiEvent(doc = "Activity enters picture-in-picture mode from content-pip API") + PICTURE_IN_PICTURE_ENTER_CONTENT_PIP(1314), + @UiEvent(doc = "Expands from picture-in-picture to fullscreen") PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604), diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 90c4440c8339..2ab7a58556a2 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -174,14 +174,13 @@ void set(BlobCache* cache, const void* key, size_t keySize, const void* value, s void ShaderCache::saveToDiskLocked() { ATRACE_NAME("ShaderCache::saveToDiskLocked"); - if (mInitialized && mBlobCache && mSavePending) { + if (mInitialized && mBlobCache) { if (mIDHash.size()) { auto key = sIDKey; set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size()); } mBlobCache->writeToFile(); } - mSavePending = false; } void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) { @@ -224,10 +223,10 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / } set(bc, key.data(), keySize, value, valueSize); - if (!mSavePending && mDeferredSaveDelay > 0) { + if (!mSavePending && mDeferredSaveDelayMs > 0) { mSavePending = true; std::thread deferredSaveThread([this]() { - sleep(mDeferredSaveDelay); + usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds std::lock_guard<std::mutex> lock(mMutex); // Store file on disk if there a new shader or Vulkan pipeline cache size changed. if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) { @@ -236,6 +235,7 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / mTryToStorePipelineCache = false; mCacheDirty = false; } + mSavePending = false; }); deferredSaveThread.detach(); } diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 3e0fd5164011..4e3eb816da29 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -153,7 +153,8 @@ private: * pending. Each time a key/value pair is inserted into the cache via * load, a deferred save is initiated if one is not already pending. * This will wait some amount of time and then trigger a save of the cache - * contents to disk. + * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving + * is disabled. */ bool mSavePending = false; @@ -163,9 +164,11 @@ private: size_t mObservedBlobValueSize = 20 * 1024; /** - * The time in seconds to wait before saving newly inserted cache entries. + * The time in milliseconds to wait before saving newly inserted cache entries. + * + * WARNING: setting this to 0 will disable writing the cache to disk. */ - unsigned int mDeferredSaveDelay = 4; + unsigned int mDeferredSaveDelayMs = 4 * 1000; /** * "mMutex" is the mutex used to prevent concurrent access to the member diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 974d85a453db..7bcd45c6b643 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -14,6 +14,10 @@ * limitations under the License. */ +#include <GrDirectContext.h> +#include <Properties.h> +#include <SkData.h> +#include <SkRefCnt.h> #include <cutils/properties.h> #include <dirent.h> #include <errno.h> @@ -22,9 +26,12 @@ #include <stdlib.h> #include <sys/types.h> #include <utils/Log.h> + #include <cstdint> + #include "FileBlobCache.h" #include "pipeline/skia/ShaderCache.h" +#include "tests/common/TestUtils.h" using namespace android::uirenderer::skiapipeline; @@ -35,11 +42,38 @@ namespace skiapipeline { class ShaderCacheTestUtils { public: /** - * "setSaveDelay" sets the time in seconds to wait before saving newly inserted cache entries. - * If set to 0, then deferred save is disabled. + * Hack to reset all member variables of the given cache to their default / initial values. + * + * WARNING: this must be kept up to date manually, since ShaderCache's parent disables just + * reassigning a new instance. */ - static void setSaveDelay(ShaderCache& cache, unsigned int saveDelay) { - cache.mDeferredSaveDelay = saveDelay; + static void reinitializeAllFields(ShaderCache& cache) { + ShaderCache newCache = ShaderCache(); + std::lock_guard<std::mutex> lock(cache.mMutex); + // By order of declaration + cache.mInitialized = newCache.mInitialized; + cache.mBlobCache.reset(nullptr); + cache.mFilename = newCache.mFilename; + cache.mIDHash.clear(); + cache.mSavePending = newCache.mSavePending; + cache.mObservedBlobValueSize = newCache.mObservedBlobValueSize; + cache.mDeferredSaveDelayMs = newCache.mDeferredSaveDelayMs; + cache.mTryToStorePipelineCache = newCache.mTryToStorePipelineCache; + cache.mInStoreVkPipelineInProgress = newCache.mInStoreVkPipelineInProgress; + cache.mNewPipelineCacheSize = newCache.mNewPipelineCacheSize; + cache.mOldPipelineCacheSize = newCache.mOldPipelineCacheSize; + cache.mCacheDirty = newCache.mCacheDirty; + cache.mNumShadersCachedInRam = newCache.mNumShadersCachedInRam; + } + + /** + * "setSaveDelayMs" sets the time in milliseconds to wait before saving newly inserted cache + * entries. If set to 0, then deferred save is disabled, and "saveToDiskLocked" must be called + * manually, as seen in the "terminate" testing helper function. + */ + static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) { + std::lock_guard<std::mutex> lock(cache.mMutex); + cache.mDeferredSaveDelayMs = saveDelayMs; } /** @@ -48,8 +82,9 @@ public: */ static void terminate(ShaderCache& cache, bool saveContent) { std::lock_guard<std::mutex> lock(cache.mMutex); - cache.mSavePending = saveContent; - cache.saveToDiskLocked(); + if (saveContent) { + cache.saveToDiskLocked(); + } cache.mBlobCache = NULL; } @@ -60,6 +95,38 @@ public: static bool validateCache(ShaderCache& cache, std::vector<T> hash) { return cache.validateCache(hash.data(), hash.size() * sizeof(T)); } + + /** + * Waits until cache::mSavePending is false, checking every 0.1 ms *while the mutex is free*. + * + * Fails if there was no save pending, or if the cache was already being written to disk, or if + * timeoutMs is exceeded. + * + * Note: timeoutMs only guards against mSavePending getting stuck like in b/268205519, and + * cannot protect against mutex-based deadlock. Reaching timeoutMs implies something is broken, + * so setting it to a sufficiently large value will not delay execution in the happy state. + */ + static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) { + { + std::lock_guard<std::mutex> lock(cache.mMutex); + ASSERT_TRUE(cache.mSavePending); + } + bool saving = true; + float elapsedMilliseconds = 0; + while (saving) { + if (elapsedMilliseconds >= timeoutMs) { + FAIL() << "Timed out after waiting " << timeoutMs << " ms for a pending save"; + } + // This small (0.1 ms) delay is to avoid working too much while waiting for + // deferredSaveThread to take the mutex and start the disk write. + const int delayMicroseconds = 100; + usleep(delayMicroseconds); + elapsedMilliseconds += (float)delayMicroseconds / 1000; + + std::lock_guard<std::mutex> lock(cache.mMutex); + saving = cache.mSavePending; + } + } }; } /* namespace skiapipeline */ @@ -81,6 +148,18 @@ bool folderExist(const std::string& folderName) { return false; } +/** + * Attempts to delete the given file, and asserts that either: + * 1. Deletion was successful, OR + * 2. The file did not exist. + * + * Tip: wrap calls to this in ASSERT_NO_FATAL_FAILURE(x) if a test should exit early if this fails. + */ +void deleteFileAssertSuccess(const std::string& filePath) { + int deleteResult = remove(filePath.c_str()); + ASSERT_TRUE(0 == deleteResult || ENOENT == errno); +} + inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) { return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() && 0 == memcmp(shader1->data(), shader2->data(), shader1->size()); @@ -91,6 +170,10 @@ inline bool checkShader(const sk_sp<SkData>& shader, const char* program) { return checkShader(shader, shader2); } +inline bool checkShader(const sk_sp<SkData>& shader, const std::string& program) { + return checkShader(shader, program.c_str()); +} + template <typename T> bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) { sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T)); @@ -101,6 +184,10 @@ void setShader(sk_sp<SkData>& shader, const char* program) { shader = SkData::MakeWithCString(program); } +void setShader(sk_sp<SkData>& shader, const std::string& program) { + setShader(shader, program.c_str()); +} + template <typename T> void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) { shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T)); @@ -124,13 +211,13 @@ TEST(ShaderCacheTest, testWriteAndRead) { std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; // remove any test files from previous test run - int deleteFile = remove(cacheFile1.c_str()); - ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1)); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2)); std::srand(0); // read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); - ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save + ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save ShaderCache::get().initShaderDiskCache(); // read a key - should not be found since the cache is empty @@ -184,7 +271,8 @@ TEST(ShaderCacheTest, testWriteAndRead) { ASSERT_TRUE(checkShader(outVS2, dataBuffer)); ShaderCacheTestUtils::terminate(ShaderCache::get(), false); - remove(cacheFile1.c_str()); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1)); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2)); } TEST(ShaderCacheTest, testCacheValidation) { @@ -196,13 +284,13 @@ TEST(ShaderCacheTest, testCacheValidation) { std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; // remove any test files from previous test run - int deleteFile = remove(cacheFile1.c_str()); - ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1)); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2)); std::srand(0); // generate identity and read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); - ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save + ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save std::vector<uint8_t> identity(1024); genRandomData(identity); ShaderCache::get().initShaderDiskCache( @@ -276,7 +364,81 @@ TEST(ShaderCacheTest, testCacheValidation) { } ShaderCacheTestUtils::terminate(ShaderCache::get(), false); - remove(cacheFile1.c_str()); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1)); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2)); +} + +using namespace android::uirenderer; +RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) { + if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) { + // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants. + GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan"; + } + if (!folderExist(getExternalStorageFolder())) { + // Don't run the test if external storage folder is not available + return; + } + std::string cacheFile = getExternalStorageFolder() + "/shaderCacheTest"; + GrDirectContext* grContext = renderThread.getGrContext(); + + // Remove any test files from previous test run + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile)); + + // The first iteration of this loop is to save an initial VkPipelineCache data blob to disk, + // which sets up the second iteration for a common scenario of comparing a "new" VkPipelineCache + // blob passed to "store" against the same blob that's already in the persistent cache from a + // previous launch. "reinitializeAllFields" is critical to emulate each iteration being as close + // to the state of a freshly launched app as possible, as the initial values of member variables + // like mInStoreVkPipelineInProgress and mOldPipelineCacheSize are critical to catch issues + // such as b/268205519 + for (int flushIteration = 1; flushIteration <= 2; flushIteration++) { + SCOPED_TRACE("Frame flush iteration " + std::to_string(flushIteration)); + // Reset *all* in-memory data and reload the cache from disk. + ShaderCacheTestUtils::reinitializeAllFields(ShaderCache::get()); + ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 10); // Delay must be > 0 to save. + ShaderCache::get().setFilename(cacheFile.c_str()); + ShaderCache::get().initShaderDiskCache(); + + // 1st iteration: store pipeline data to be read back on a subsequent "boot" of the "app". + // 2nd iteration: ensure that an initial frame flush (without storing any shaders) given the + // same pipeline data that's already on disk doesn't break the cache. + ShaderCache::get().onVkFrameFlushed(grContext); + ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get())); + } + + constexpr char shader1[] = "sassas"; + constexpr char shader2[] = "someVS"; + constexpr int numIterations = 3; + // Also do n iterations of separate "store some shaders then flush the frame" pairs to just + // double-check the cache also doesn't get stuck from that use case. + for (int saveIteration = 1; saveIteration <= numIterations; saveIteration++) { + SCOPED_TRACE("Shader save iteration " + std::to_string(saveIteration)); + // Write twice to the in-memory cache, which should start a deferred save with both queued. + sk_sp<SkData> inVS; + setShader(inVS, shader1 + std::to_string(saveIteration)); + ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString()); + setShader(inVS, shader2 + std::to_string(saveIteration)); + ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString()); + + // Simulate flush to also save latest pipeline info. + ShaderCache::get().onVkFrameFlushed(grContext); + ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get())); + } + + // Reload from disk to ensure saving succeeded. + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + ShaderCache::get().initShaderDiskCache(); + + // Read twice, ensure equal to last store. + sk_sp<SkData> outVS; + ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>()); + ASSERT_TRUE(checkShader(outVS, shader1 + std::to_string(numIterations))); + ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); + ASSERT_TRUE(checkShader(outVS, shader2 + std::to_string(numIterations))); + + // Clean up. + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile)); } } // namespace diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md index 4cb765dab741..488f8c728d82 100644 --- a/packages/SystemUI/docs/qs-tiles.md +++ b/packages/SystemUI/docs/qs-tiles.md @@ -301,9 +301,13 @@ This section describes necessary and recommended steps when implementing a Quick * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter. * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers. * Implement `isAvailable` so the tile will not be created when it's not necessary. -4. In `QSFactoryImpl`: - * Inject a `Provider` for the tile created before. - * Add a case to the `switch` with a unique String spec for the chosen tile. +4. Either create a new feature module or find an existing related feature module and add the following binding method: + * ```kotlin + @Binds + @IntoMap + @StringKey(YourNewTile.TILE_SPEC) // A unique word that will map to YourNewTile + fun bindYourNewTile(yourNewTile: YourNewTile): QSTileImpl<*> + ``` 5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. 6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to help the translators. 7. In [`SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt), add a new element to the map in `SubtitleArrayMapping` corresponding to the resource created in the previous step. diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 1a67691e30bf..22158571bcd6 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -452,12 +452,6 @@ -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt -packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt --packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt @@ -743,10 +737,6 @@ -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/ShadeExpansionStateManagerTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt diff --git a/packages/SystemUI/res/layout/font_scaling_dialog.xml b/packages/SystemUI/res/layout/font_scaling_dialog.xml new file mode 100644 index 000000000000..27c1e9d03df9 --- /dev/null +++ b/packages/SystemUI/res/layout/font_scaling_dialog.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<com.android.systemui.common.ui.view.SeekBarWithIconButtonsView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/font_scaling_slider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + app:max="6" + app:progress="0" + app:iconStartContentDescription="@string/font_scaling_smaller" + app:iconEndContentDescription="@string/font_scaling_larger"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 401dcf7e52f7..e6ac59e6b106 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2183,6 +2183,14 @@ <!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] --> <string name="inattentive_sleep_warning_title">Standby</string> + <!-- Font scaling --> + <!-- Font scaling: Quick Settings dialog title [CHAR LIMIT=30] --> + <string name="font_scaling_dialog_title">Font Size</string> + <!-- Content Description for the icon button to make fonts smaller. [CHAR LIMIT=30] --> + <string name="font_scaling_smaller">Make smaller</string> + <!-- Content Description for the icon button to make fonts larger. [CHAR LIMIT=30] --> + <string name="font_scaling_larger">Make larger</string> + <!-- Window Magnification strings --> <!-- Title for Magnification Window [CHAR LIMIT=NONE] --> <string name="magnification_window_title">Magnification Window</string> @@ -2466,7 +2474,10 @@ <string name="media_output_broadcast_update_error">Can\u2019t save. Try again.</string> <!-- The error message when Broadcast name/code update failed and can't change again[CHAR LIMIT=60] --> <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string> - + <!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] --> + <string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string> + <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] --> + <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string> <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]--> <string name="build_number_clip_data_label">Build number</string> @@ -2831,4 +2842,19 @@ [CHAR LIMIT=32] --> <string name="lock_screen_settings">Lock screen settings</string> + + <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]--> + <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string> + + <!-- Content description for camera blocked icon on dream [CHAR LIMIT=NONE] --> + <string name="camera_blocked_dream_overlay_content_description">Camera blocked</string> + + <!-- Content description for camera and microphone blocked icon on dream [CHAR LIMIT=NONE] --> + <string name="camera_and_microphone_blocked_dream_overlay_content_description">Camera and microphone blocked</string> + + <!-- Content description for camera and microphone disabled icon on dream [CHAR LIMIT=NONE] --> + <string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string> + + <!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] --> + <string name="priority_mode_dream_overlay_content_description">Priority mode on</string> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index 54ae84f97b17..ead1a100f75b 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -303,9 +303,18 @@ class ActiveUnlockConfig @Inject constructor( pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup") pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent") pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail") - pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=${ - onUnlockIntentWhenBiometricEnrolled.map { BiometricType.values()[it] } - }") + + val onUnlockIntentWhenBiometricEnrolledString = + onUnlockIntentWhenBiometricEnrolled.map { + for (biometricType in BiometricType.values()) { + if (biometricType.intValue == it) { + return@map biometricType.name + } + } + return@map "UNKNOWN" + } + pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" + + "$onUnlockIntentWhenBiometricEnrolledString") pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn") pw.println(" requestActiveUnlockOnFaceAcquireInfo=" + "$faceAcquireInfoToTriggerBiometricFailOn") diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt new file mode 100644 index 000000000000..799a4d597168 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt @@ -0,0 +1,76 @@ +/* + * 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.accessibility + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.ColorCorrectionTile +import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.DreamTile +import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.NightDisplayTile +import com.android.systemui.qs.tiles.OneHandedModeTile +import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface AccessibilityModule { + + /** Inject ColorInversionTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ColorInversionTile.TILE_SPEC) + fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*> + + /** Inject NightDisplayTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(NightDisplayTile.TILE_SPEC) + fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*> + + /** Inject ReduceBrightColorsTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ReduceBrightColorsTile.TILE_SPEC) + fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*> + + /** Inject OneHandedModeTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(OneHandedModeTile.TILE_SPEC) + fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*> + + /** Inject ColorCorrectionTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ColorCorrectionTile.TILE_SPEC) + fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*> + + /** Inject DreamTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(DreamTile.TILE_SPEC) + fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*> + + /** Inject FontScalingTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(FontScalingTile.TILE_SPEC) + fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt new file mode 100644 index 000000000000..54f933ae6d09 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt @@ -0,0 +1,107 @@ +/* + * 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.accessibility.fontscaling + +import android.content.Context +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.os.Bundle +import android.provider.Settings +import android.view.LayoutInflater +import android.widget.Button +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import com.android.systemui.R +import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.settings.SystemSettings + +/** The Dialog that contains a seekbar for changing the font size. */ +class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) : + SystemUIDialog(context) { + private val strEntryValues: Array<String> = + context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size) + private lateinit var title: TextView + private lateinit var doneButton: Button + private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView + + private val configuration: Configuration = + Configuration(context.getResources().getConfiguration()) + + override fun onCreate(savedInstanceState: Bundle?) { + setTitle(R.string.font_scaling_dialog_title) + setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null)) + setPositiveButton( + R.string.quick_settings_done, + /* onClick = */ null, + /* dismissOnClick = */ true + ) + super.onCreate(savedInstanceState) + + title = requireViewById(com.android.internal.R.id.alertTitle) + doneButton = requireViewById(com.android.internal.R.id.button1) + seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider) + + seekBarWithIconButtonsView.setMax((strEntryValues).size - 1) + + val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f) + seekBarWithIconButtonsView.setProgress(fontSizeValueToIndex(currentScale)) + + seekBarWithIconButtonsView.setOnSeekBarChangeListener( + object : OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[progress]) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + // Do nothing + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + // Do nothing + } + } + ) + doneButton.setOnClickListener { dismiss() } + } + + private fun fontSizeValueToIndex(value: Float): Int { + var lastValue = strEntryValues[0].toFloat() + for (i in 1 until strEntryValues.size) { + val thisValue = strEntryValues[i].toFloat() + if (value < lastValue + (thisValue - lastValue) * .5f) { + return i - 1 + } + lastValue = thisValue + } + return strEntryValues.size - 1 + } + + override fun onConfigurationChanged(configuration: Configuration) { + super.onConfigurationChanged(configuration) + + val configDiff = configuration.diff(this.configuration) + this.configuration.setTo(configuration) + + if (configDiff and ActivityInfo.CONFIG_FONT_SCALE != 0) { + title.post { + title.setTextAppearance(R.style.TextAppearance_Dialog_Title) + doneButton.setTextAppearance(R.style.Widget_Dialog_Button) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt new file mode 100644 index 000000000000..41737902983a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt @@ -0,0 +1,34 @@ +/* + * 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.battery + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.BatterySaverTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface BatterySaverModule { + + /** Inject BatterySaverTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(BatterySaverTile.TILE_SPEC) + fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java index e8288a0d2a87..826253947ce1 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java @@ -153,6 +153,13 @@ public class SeekBarWithIconButtonsView extends LinearLayout { } /** + * Sets max to the seekbar in the layout. + */ + public void setMax(int max) { + mSeekbar.setMax(max); + } + + /** * Sets progress to the seekbar in the layout. * If the progress is smaller than or equals to 0, the IconStart will be disabled. If the * progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index 6af8e73c8d25..d949d1119222 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -44,12 +44,15 @@ import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.controls.ui.ControlsUiControllerImpl import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.DeviceControlsTile import dagger.Binds import dagger.BindsOptionalOf import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey /** * Module for injecting classes in `com.android.systemui.controls`- @@ -149,4 +152,9 @@ abstract class ControlsModule { @IntoMap @ClassKey(ControlsActivity::class) abstract fun provideControlsActivity(activity: ControlsActivity): Activity + + @Binds + @IntoMap + @StringKey(DeviceControlsTile.TILE_SPEC) + abstract fun bindDeviceControlsTile(controlsTile: DeviceControlsTile): QSTileImpl<*> } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index fd690dfd5dfa..03a1dc068d3d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardViewController; +import com.android.systemui.battery.BatterySaverModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; @@ -40,6 +41,7 @@ import com.android.systemui.qs.dagger.QSModule; import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; +import com.android.systemui.rotationlock.RotationLockModule; import com.android.systemui.screenshot.ReferenceScreenshotModule; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.ShadeController; @@ -92,11 +94,13 @@ import dagger.Provides; */ @Module(includes = { AospPolicyModule.class, + BatterySaverModule.class, GestureModule.class, MediaModule.class, PowerModule.class, QSModule.class, ReferenceScreenshotModule.class, + RotationLockModule.class, StartCentralSurfacesModule.class, VolumeModule.class }) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index cb7c765d0549..bddd8a7c147c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -55,7 +55,6 @@ import com.android.systemui.theme.ThemeOverlayController import com.android.systemui.toast.ToastUI import com.android.systemui.usb.StorageNotification import com.android.systemui.util.NotificationChannels -import com.android.systemui.util.leak.GarbageMonitor import com.android.systemui.volume.VolumeUI import com.android.systemui.wmshell.WMShell import dagger.Binds @@ -107,12 +106,6 @@ abstract class SystemUICoreStartableModule { @ClassKey(FsiChromeViewBinder::class) abstract fun bindFsiChromeWindowBinder(sysui: FsiChromeViewBinder): CoreStartable - /** Inject into GarbageMonitor.Service. */ - @Binds - @IntoMap - @ClassKey(GarbageMonitor::class) - abstract fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable - /** Inject into GlobalActionsComponent. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index c3ee9bee19db..60fccef3ef57 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -28,6 +28,7 @@ import com.android.keyguard.dagger.ClockRegistryModule; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; +import com.android.systemui.accessibility.AccessibilityModule; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; @@ -57,10 +58,12 @@ import com.android.systemui.people.PeopleModule; import com.android.systemui.plugins.BcSmartspaceConfigPlugin; import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.privacy.PrivacyModule; +import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule; import com.android.systemui.qs.FgsManagerController; import com.android.systemui.qs.FgsManagerControllerImpl; import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; +import com.android.systemui.screenrecord.ScreenRecordModule; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.security.data.repository.SecurityRepositoryModule; import com.android.systemui.settings.DisplayTracker; @@ -70,6 +73,7 @@ import com.android.systemui.smartspace.dagger.SmartspaceModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.connectivity.ConnectivityModule; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; @@ -85,6 +89,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.PolicyModule; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule; import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule; @@ -97,6 +102,7 @@ import com.android.systemui.user.UserModule; import com.android.systemui.util.concurrency.SysUIConcurrencyModule; import com.android.systemui.util.dagger.UtilModule; import com.android.systemui.util.kotlin.CoroutinesModule; +import com.android.systemui.util.leak.GarbageMonitorModule; import com.android.systemui.util.sensors.SensorModule; import com.android.systemui.util.settings.SettingsUtilModule; import com.android.systemui.util.time.SystemClock; @@ -126,6 +132,7 @@ import dagger.Provides; * may not appreciate that. */ @Module(includes = { + AccessibilityModule.class, AppOpsModule.class, AssistModule.class, BiometricsModule.class, @@ -133,6 +140,7 @@ import dagger.Provides; ClipboardOverlayModule.class, ClockInfoModule.class, ClockRegistryModule.class, + ConnectivityModule.class, CoroutinesModule.class, DreamModule.class, ControlsModule.class, @@ -141,17 +149,21 @@ import dagger.Provides; FlagsModule.class, SystemPropertiesFlagsModule.class, FooterActionsModule.class, + GarbageMonitorModule.class, LogModule.class, MediaProjectionModule.class, MotionToolModule.class, PeopleHubModule.class, PeopleModule.class, PluginModule.class, + PolicyModule.class, PrivacyModule.class, + QRCodeScannerModule.class, ScreenshotModule.class, SensorModule.class, MultiUserUtilsModule.class, SecurityRepositoryModule.class, + ScreenRecordModule.class, SettingsUtilModule.class, SmartRepliesInflationModule.class, SmartspaceModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java index 102f2082ebd1..055cd52b23d6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java @@ -16,19 +16,23 @@ package com.android.systemui.dreams; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR; + import android.util.Log; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback; import com.android.systemui.dreams.conditions.DreamCondition; import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.condition.ConditionalCoreStartable; import javax.inject.Inject; +import javax.inject.Named; /** * A {@link CoreStartable} to retain a monitor for tracking dreaming. */ -public class DreamMonitor implements CoreStartable { +public class DreamMonitor extends ConditionalCoreStartable { private static final String TAG = "DreamMonitor"; // We retain a reference to the monitor so it is not garbage-collected. @@ -39,14 +43,17 @@ public class DreamMonitor implements CoreStartable { @Inject public DreamMonitor(Monitor monitor, DreamCondition dreamCondition, + @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor, DreamStatusBarStateCallback callback) { + super(pretextMonitor); mConditionMonitor = monitor; mDreamCondition = dreamCondition; mCallback = callback; } + @Override - public void start() { + protected void onStart() { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "started"); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java index 87c5f51ce13a..a2dcdf52ad3c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_SERVICE_COMPONENT; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -33,8 +34,9 @@ import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.util.Log; -import com.android.systemui.CoreStartable; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.condition.ConditionalCoreStartable; import javax.inject.Inject; import javax.inject.Named; @@ -43,7 +45,7 @@ import javax.inject.Named; * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be * the designated dream overlay component. */ -public class DreamOverlayRegistrant implements CoreStartable { +public class DreamOverlayRegistrant extends ConditionalCoreStartable { private static final String TAG = "DreamOverlayRegistrant"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final IDreamManager mDreamManager; @@ -102,7 +104,9 @@ public class DreamOverlayRegistrant implements CoreStartable { @Inject public DreamOverlayRegistrant(Context context, @Main Resources resources, - @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent) { + @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent, + @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) { + super(monitor); mContext = context; mResources = resources; mDreamManager = IDreamManager.Stub.asInterface( @@ -111,7 +115,7 @@ public class DreamOverlayRegistrant implements CoreStartable { } @Override - public void start() { + protected void onStart() { final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); filter.addDataSchemeSpecificPart(mOverlayServiceComponent.getPackageName(), diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index dd01be0ef031..5aebc3268b90 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -243,6 +243,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ */ private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) { mWindow = new PhoneWindow(mContext); + // Default to SystemUI name for TalkBack. + mWindow.setTitle(""); mWindow.setAttributes(layoutParams); mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 90c440c403ec..7394e2366ac9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -257,7 +257,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mConnectivityManager.getActiveNetwork()); final boolean available = capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); - showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available); + showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available, + R.string.wifi_unavailable_dream_overlay_content_description); } private void updateAlarmStatusIcon() { @@ -294,13 +295,16 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve @DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL; showIcon( DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED, - !micBlocked && cameraBlocked); + !micBlocked && cameraBlocked, + R.string.camera_blocked_dream_overlay_content_description); showIcon( DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED, - micBlocked && !cameraBlocked); + micBlocked && !cameraBlocked, + R.string.microphone_blocked_dream_overlay_content_description); showIcon( DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, - micBlocked && cameraBlocked); + micBlocked && cameraBlocked, + R.string.camera_and_microphone_blocked_dream_overlay_content_description); } private String buildNotificationsContentDescription(int notificationCount) { @@ -313,11 +317,13 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve private void updatePriorityModeStatusIcon() { showIcon( DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, - mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF); + mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF, + R.string.priority_mode_dream_overlay_content_description); } - private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show) { - showIcon(iconType, show, null); + private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show, + int contentDescriptionResId) { + showIcon(iconType, show, mResources.getString(contentDescriptionResId)); } private void showIcon( diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java index ee2f1af6a99b..244212b45790 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java @@ -16,27 +16,31 @@ package com.android.systemui.dreams.complication; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR; + import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; import com.android.settingslib.dream.DreamBackend; -import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.condition.ConditionalCoreStartable; import com.android.systemui.util.settings.SecureSettings; import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Named; /** * {@link ComplicationTypesUpdater} observes the state of available complication types set by the * user, and pushes updates to {@link DreamOverlayStateController}. */ @SysUISingleton -public class ComplicationTypesUpdater implements CoreStartable { +public class ComplicationTypesUpdater extends ConditionalCoreStartable { private final DreamBackend mDreamBackend; private final Executor mExecutor; private final SecureSettings mSecureSettings; @@ -48,7 +52,9 @@ public class ComplicationTypesUpdater implements CoreStartable { DreamBackend dreamBackend, @Main Executor executor, SecureSettings secureSettings, - DreamOverlayStateController dreamOverlayStateController) { + DreamOverlayStateController dreamOverlayStateController, + @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) { + super(monitor); mDreamBackend = dreamBackend; mExecutor = executor; mSecureSettings = secureSettings; @@ -56,7 +62,7 @@ public class ComplicationTypesUpdater implements CoreStartable { } @Override - public void start() { + public void onStart() { final ContentObserver settingsObserver = new ContentObserver(null /*handler*/) { @Override public void onChange(boolean selfChange) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java index 77e1fc91e6ee..bb1e6e2ef06d 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java @@ -18,11 +18,14 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR; import android.view.View; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.condition.ConditionalCoreStartable; import javax.inject.Inject; import javax.inject.Named; @@ -60,7 +63,7 @@ public class DreamClockTimeComplication implements Complication { * {@link CoreStartable} responsible for registering {@link DreamClockTimeComplication} with * SystemUI. */ - public static class Registrant implements CoreStartable { + public static class Registrant extends ConditionalCoreStartable { private final DreamOverlayStateController mDreamOverlayStateController; private final DreamClockTimeComplication mComplication; @@ -70,13 +73,15 @@ public class DreamClockTimeComplication implements Complication { @Inject public Registrant( DreamOverlayStateController dreamOverlayStateController, - DreamClockTimeComplication dreamClockTimeComplication) { + DreamClockTimeComplication dreamClockTimeComplication, + @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) { + super(monitor); mDreamOverlayStateController = dreamOverlayStateController; mComplication = dreamClockTimeComplication; } @Override - public void start() { + public void onStart() { mDreamOverlayStateController.addComplication(mComplication); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 1065b94508f8..7f395d863c3f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -21,6 +21,7 @@ import static com.android.systemui.controls.dagger.ControlsComponent.Visibility. import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE; import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR; import android.content.Context; import android.content.Intent; @@ -42,7 +43,9 @@ import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.ViewController; +import com.android.systemui.util.condition.ConditionalCoreStartable; import java.util.List; @@ -75,7 +78,7 @@ public class DreamHomeControlsComplication implements Complication { /** * {@link CoreStartable} for registering the complication with SystemUI on startup. */ - public static class Registrant implements CoreStartable { + public static class Registrant extends ConditionalCoreStartable { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; @@ -105,14 +108,16 @@ public class DreamHomeControlsComplication implements Complication { @Inject public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, - ControlsComponent controlsComponent) { + ControlsComponent controlsComponent, + @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) { + super(monitor); mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; } @Override - public void start() { + public void onStart() { mControlsComponent.getControlsListingController().ifPresent( c -> c.addCallback(mControlsCallback)); mDreamOverlayStateController.addCallback(mOverlayStateCallback); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java index c3aaf0cbf2d7..e39073bb6711 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams.complication; import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS; +import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR; import android.content.Context; import android.os.Parcelable; @@ -28,6 +29,8 @@ import com.android.systemui.CoreStartable; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.smartspace.DreamSmartspaceController; import com.android.systemui.plugins.BcSmartspaceDataPlugin; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.condition.ConditionalCoreStartable; import java.util.List; @@ -61,7 +64,7 @@ public class SmartSpaceComplication implements Complication { * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with * SystemUI. */ - public static class Registrant implements CoreStartable { + public static class Registrant extends ConditionalCoreStartable { private final DreamSmartspaceController mSmartSpaceController; private final DreamOverlayStateController mDreamOverlayStateController; private final SmartSpaceComplication mComplication; @@ -81,14 +84,16 @@ public class SmartSpaceComplication implements Complication { public Registrant( DreamOverlayStateController dreamOverlayStateController, SmartSpaceComplication smartSpaceComplication, - DreamSmartspaceController smartSpaceController) { + DreamSmartspaceController smartSpaceController, + @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) { + super(monitor); mDreamOverlayStateController = dreamOverlayStateController; mComplication = smartSpaceComplication; mSmartSpaceController = smartSpaceController; } @Override - public void start() { + public void onStart() { mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() { @Override public void onStateChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 0ab8c8e42b03..88c02b8aa790 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -29,13 +29,20 @@ import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule; +import com.android.systemui.process.condition.UserProcessCondition; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.shared.condition.Monitor; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executor; import javax.inject.Named; +import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.IntoSet; /** * Dagger Module providing Dream-related functionality. @@ -54,6 +61,8 @@ public interface DreamModule { String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled"; String DREAM_SUPPORTED = "dream_supported"; + String DREAM_PRETEXT_CONDITIONS = "dream_pretext_conditions"; + String DREAM_PRETEXT_MONITOR = "dream_prtext_monitor"; /** * Provides the dream component @@ -112,4 +121,19 @@ public interface DreamModule { static boolean providesDreamSupported(@Main Resources resources) { return resources.getBoolean(com.android.internal.R.bool.config_dreamsSupported); } + + /** */ + @Binds + @IntoSet + @Named(DREAM_PRETEXT_CONDITIONS) + Condition bindsUserProcessCondition(UserProcessCondition condition); + + /** */ + @Provides + @Named(DREAM_PRETEXT_MONITOR) + static Monitor providesDockerPretextMonitor( + @Main Executor executor, + @Named(DREAM_PRETEXT_CONDITIONS) Set<Condition> pretextConditions) { + return new Monitor(executor, pretextConditions); + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4d89c6cb9a9f..ff3e7298de56 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -100,7 +100,7 @@ object Flags { // TODO(b/260335638): Tracking Bug @JvmField val NOTIFICATION_INLINE_REPLY_ANIMATION = - unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true) + releasedFlag(174148361, "notification_inline_reply_animation") val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD = releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true) @@ -578,6 +578,12 @@ object Flags { val CONTROLS_MANAGEMENT_NEW_FLOWS = unreleasedFlag(2002, "controls_management_new_flows", teamfood = true) + // Enables removing app from Home control panel as a part of a new flow + // TODO(b/269132640): Tracking Bug + @JvmField + val APP_PANELS_REMOVE_APPS_ALLOWED = + unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = false) + // 2100 - Falsing Manager @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt index 5a9f7752277e..c9f645dddd8d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.app.StatusBarManager +import android.app.admin.DevicePolicyManager import android.content.Context import android.content.pm.PackageManager import com.android.systemui.R @@ -27,10 +28,14 @@ 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.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserTracker import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext @SysUISingleton class CameraQuickAffordanceConfig @@ -39,6 +44,9 @@ constructor( @Application private val context: Context, private val packageManager: PackageManager, private val cameraGestureHelper: Lazy<CameraGestureHelper>, + private val userTracker: UserTracker, + private val devicePolicyManager: DevicePolicyManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, ) : KeyguardQuickAffordanceConfig { override val key: String @@ -79,7 +87,12 @@ constructor( return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } - private fun isLaunchable(): Boolean { - return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) + private suspend fun isLaunchable(): Boolean { + return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) && + withContext(backgroundDispatcher) { + !devicePolicyManager.getCameraDisabled(null, userTracker.userId) && + devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) and + DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA == 0 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt index d9ec3b1c2f87..6f821a2b5228 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.quickaffordance import android.app.StatusBarManager +import android.app.admin.DevicePolicyManager import android.content.Context import android.content.Intent import com.android.systemui.ActivityIntentHelper @@ -29,10 +30,13 @@ 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.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.withContext @SysUISingleton class VideoCameraQuickAffordanceConfig @@ -42,6 +46,8 @@ constructor( private val cameraIntents: CameraIntentsWrapper, private val activityIntentHelper: ActivityIntentHelper, private val userTracker: UserTracker, + private val devicePolicyManager: DevicePolicyManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, ) : KeyguardQuickAffordanceConfig { private val intent: Intent by lazy { @@ -63,8 +69,8 @@ constructor( get() = R.drawable.ic_videocam override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> - get() = - flowOf( + get() = flow { + emit( if (isLaunchable()) { KeyguardQuickAffordanceConfig.LockScreenState.Visible( icon = @@ -77,6 +83,7 @@ constructor( KeyguardQuickAffordanceConfig.LockScreenState.Hidden } ) + } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { return if (isLaunchable()) { @@ -95,11 +102,14 @@ constructor( ) } - private fun isLaunchable(): Boolean { + private suspend fun isLaunchable(): Boolean { return activityIntentHelper.getTargetActivityInfo( intent, userTracker.userId, true, - ) != null + ) != null && + withContext(backgroundDispatcher) { + !devicePolicyManager.getCameraDisabled(null, userTracker.userId) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 520edef7d109..f5558a240a70 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -1343,9 +1343,9 @@ class MediaDataManager( if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) { logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) { - convertToResumePlayer(removed) + convertToResumePlayer(key, removed) } else if (mediaFlags.isRetainingPlayersEnabled()) { - handlePossibleRemoval(removed, notificationRemoved = true) + handlePossibleRemoval(key, removed, notificationRemoved = true) } else { notifyMediaDataRemoved(key) logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) @@ -1359,7 +1359,7 @@ class MediaDataManager( val entry = mediaEntries.remove(key) ?: return // Clear token since the session is no longer valid val updated = entry.copy(token = null) - handlePossibleRemoval(updated) + handlePossibleRemoval(key, updated) } /** @@ -1368,8 +1368,11 @@ class MediaDataManager( * if it was removed before becoming inactive. (Assumes that [removed] was removed from * [mediaEntries] before this function was called) */ - private fun handlePossibleRemoval(removed: MediaData, notificationRemoved: Boolean = false) { - val key = removed.notificationKey!! + private fun handlePossibleRemoval( + key: String, + removed: MediaData, + notificationRemoved: Boolean = false + ) { val hasSession = removed.token != null if (hasSession && removed.semanticActions != null) { // The app was using session actions, and the session is still valid: keep player @@ -1395,13 +1398,12 @@ class MediaDataManager( "($hasSession) gone for inactive player $key" ) } - convertToResumePlayer(removed) + convertToResumePlayer(key, removed) } } /** Set the given [MediaData] as a resume state player and notify listeners */ - private fun convertToResumePlayer(data: MediaData) { - val key = data.notificationKey!! + private fun convertToResumePlayer(key: String, data: MediaData) { if (DEBUG) Log.d(TAG, "Converting $key to resume") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = data.resumeAction?.let { getResumeMediaAction(it) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index b72923a5d22c..68d2c5c5f4c4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -164,13 +164,13 @@ constructor( mediaCarouselScrollHandler.scrollToStart() } } - private var currentlyExpanded = true + + @VisibleForTesting + var currentlyExpanded = true set(value) { if (field != value) { field = value - for (player in MediaPlayerData.players()) { - player.setListening(field) - } + updateSeekbarListening(mediaCarouselScrollHandler.visibleToUser) } } @@ -259,6 +259,7 @@ constructor( executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation, + this::updateSeekbarListening, this::closeGuts, falsingCollector, falsingManager, @@ -590,6 +591,17 @@ constructor( ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex) } } + // Check postcondition: mediaContent should have the same number of children as there + // are + // elements in mediaPlayers. + if (MediaPlayerData.players().size != mediaContent.childCount) { + Log.e( + TAG, + "Size of players list and number of views in carousel are out of sync. " + + "Players size is ${MediaPlayerData.players().size}. " + + "View count is ${mediaContent.childCount}." + ) + } } // Returns true if new player is added @@ -618,7 +630,9 @@ constructor( ) newPlayer.mediaViewHolder?.player?.setLayoutParams(lp) newPlayer.bindPlayer(data, key) - newPlayer.setListening(currentlyExpanded) + newPlayer.setListening( + mediaCarouselScrollHandler.visibleToUser && currentlyExpanded + ) MediaPlayerData.addMediaPlayer( key, data, @@ -665,17 +679,6 @@ constructor( updatePageIndicator() mediaCarouselScrollHandler.onPlayersChanged() mediaFrame.requiresRemeasuring = true - // Check postcondition: mediaContent should have the same number of children as there - // are - // elements in mediaPlayers. - if (MediaPlayerData.players().size != mediaContent.childCount) { - Log.e( - TAG, - "Size of players list and number of views in carousel are out of sync. " + - "Players size is ${MediaPlayerData.players().size}. " + - "View count is ${mediaContent.childCount}." - ) - } return existingPlayer == null } @@ -914,6 +917,13 @@ constructor( .toFloat() } + /** Update listening to seekbar. */ + private fun updateSeekbarListening(visibleToUser: Boolean) { + for (player in MediaPlayerData.players()) { + player.setListening(visibleToUser && currentlyExpanded) + } + } + /** Update the dimension of this carousel. */ private fun updateCarouselDimensions() { var width = 0 diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt index 36b2eda65fab..1ace3168780a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt @@ -57,6 +57,7 @@ class MediaCarouselScrollHandler( private val mainExecutor: DelayableExecutor, val dismissCallback: () -> Unit, private var translationChangedListener: () -> Unit, + private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit, private val closeGuts: (immediate: Boolean) -> Unit, private val falsingCollector: FalsingCollector, private val falsingManager: FalsingManager, @@ -177,6 +178,12 @@ class MediaCarouselScrollHandler( /** Whether the media card is visible to user if any */ var visibleToUser: Boolean = false + set(value) { + if (field != value) { + field = value + seekBarUpdateListener.invoke(field) + } + } /** Whether the quick setting is expanded or not */ var qsExpanded: Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 7f420a8d1055..767706209475 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -346,6 +346,11 @@ public class MediaControlPanel { mSeekBarViewModel.setListening(listening); } + @VisibleForTesting + public boolean getListening() { + return mSeekBarViewModel.getListening(); + } + /** Sets whether the user is touching the seek bar to change the track position. */ private void setIsScrubbing(boolean isScrubbing) { if (mMediaData == null || mMediaData.getSemanticActions() == null) { diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java index 7db293d96a50..245cf89a8337 100644 --- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java @@ -16,11 +16,16 @@ package com.android.systemui.process; +import javax.inject.Inject; + /** * A simple wrapper that provides access to process-related details. This facilitates testing by * providing a mockable target around these details. */ public class ProcessWrapper { + @Inject + public ProcessWrapper() {} + public int getUserHandleIdentifier() { return android.os.Process.myUserHandle().getIdentifier(); } diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt new file mode 100644 index 000000000000..62c99da5ed81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt @@ -0,0 +1,35 @@ +/* + * 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.qrcodescanner.dagger + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.QRCodeScannerTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface QRCodeScannerModule { + + /** + */ + @Binds + @IntoMap + @StringKey(QRCodeScannerTile.TILE_SPEC) + fun bindQRCodeScannerTile(qrCodeScannerTile: QRCodeScannerTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 25a5c61a5f7d..27ae1710467b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -30,6 +30,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.external.QSExternalModule; +import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -40,11 +41,14 @@ import com.android.systemui.statusbar.policy.SafetyController; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.util.settings.SecureSettings; +import java.util.Map; + import javax.inject.Named; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.Multibinds; /** * Module for QS dependencies @@ -53,6 +57,11 @@ import dagger.Provides; includes = {MediaModule.class, QSExternalModule.class, QSFlagsModule.class}) public interface QSModule { + /** A map of internal QS tiles. Ensures that this can be injected even if + * it is empty */ + @Multibinds + Map<String, QSTileImpl<?>> tileMap(); + @Provides @SysUISingleton static AutoTileManager provideAutoTileManager( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index dec6e7d93fa2..6b23f5d77145 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -27,76 +27,32 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.external.CustomTile; -import com.android.systemui.qs.tiles.AirplaneModeTile; -import com.android.systemui.qs.tiles.AlarmTile; -import com.android.systemui.qs.tiles.BatterySaverTile; -import com.android.systemui.qs.tiles.BluetoothTile; -import com.android.systemui.qs.tiles.CameraToggleTile; -import com.android.systemui.qs.tiles.CastTile; -import com.android.systemui.qs.tiles.ColorCorrectionTile; -import com.android.systemui.qs.tiles.ColorInversionTile; -import com.android.systemui.qs.tiles.DataSaverTile; -import com.android.systemui.qs.tiles.DeviceControlsTile; -import com.android.systemui.qs.tiles.DndTile; -import com.android.systemui.qs.tiles.DreamTile; -import com.android.systemui.qs.tiles.FlashlightTile; -import com.android.systemui.qs.tiles.FontScalingTile; -import com.android.systemui.qs.tiles.HotspotTile; -import com.android.systemui.qs.tiles.InternetTile; -import com.android.systemui.qs.tiles.LocationTile; -import com.android.systemui.qs.tiles.MicrophoneToggleTile; -import com.android.systemui.qs.tiles.NfcTile; -import com.android.systemui.qs.tiles.NightDisplayTile; -import com.android.systemui.qs.tiles.OneHandedModeTile; -import com.android.systemui.qs.tiles.QRCodeScannerTile; -import com.android.systemui.qs.tiles.QuickAccessWalletTile; -import com.android.systemui.qs.tiles.ReduceBrightColorsTile; -import com.android.systemui.qs.tiles.RotationLockTile; -import com.android.systemui.qs.tiles.ScreenRecordTile; -import com.android.systemui.qs.tiles.UiModeNightTile; -import com.android.systemui.qs.tiles.WorkModeTile; import com.android.systemui.util.leak.GarbageMonitor; +import java.util.Map; + import javax.inject.Inject; import javax.inject.Provider; import dagger.Lazy; +/** + * A factory that creates Quick Settings tiles based on a tileSpec + * + * To create a new tile within SystemUI, the tile class should extend {@link QSTileImpl} and have + * a public static final TILE_SPEC field which serves as a unique key for this tile. (e.g. {@link + * com.android.systemui.qs.tiles.DreamTile#TILE_SPEC}) + * + * After, create or find an existing Module class to house the tile's binding method (e.g. {@link + * com.android.systemui.accessibility.AccessibilityModule}). If creating a new module, add your + * module to the SystemUI dagger graph by including it in an appropriate module. + */ @SysUISingleton public class QSFactoryImpl implements QSFactory { private static final String TAG = "QSFactory"; - private final Provider<InternetTile> mInternetTileProvider; - private final Provider<BluetoothTile> mBluetoothTileProvider; - private final Provider<DndTile> mDndTileProvider; - private final Provider<ColorCorrectionTile> mColorCorrectionTileProvider; - private final Provider<ColorInversionTile> mColorInversionTileProvider; - private final Provider<AirplaneModeTile> mAirplaneModeTileProvider; - private final Provider<WorkModeTile> mWorkModeTileProvider; - private final Provider<RotationLockTile> mRotationLockTileProvider; - private final Provider<FlashlightTile> mFlashlightTileProvider; - private final Provider<LocationTile> mLocationTileProvider; - private final Provider<CastTile> mCastTileProvider; - private final Provider<HotspotTile> mHotspotTileProvider; - private final Provider<BatterySaverTile> mBatterySaverTileProvider; - private final Provider<DataSaverTile> mDataSaverTileProvider; - private final Provider<NightDisplayTile> mNightDisplayTileProvider; - private final Provider<NfcTile> mNfcTileProvider; - private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider; - private final Provider<UiModeNightTile> mUiModeNightTileProvider; - private final Provider<ScreenRecordTile> mScreenRecordTileProvider; - private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider; - private final Provider<CameraToggleTile> mCameraToggleTileProvider; - private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider; - private final Provider<DeviceControlsTile> mDeviceControlsTileProvider; - private final Provider<AlarmTile> mAlarmTileProvider; - private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider; - private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider; - private final Provider<OneHandedModeTile> mOneHandedModeTileProvider; - private final Provider<DreamTile> mDreamTileProvider; - private final Provider<FontScalingTile> mFontScalingTileProvider; - + protected final Map<String, Provider<QSTileImpl<?>>> mTileMap; private final Lazy<QSHost> mQsHostLazy; private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; @@ -104,67 +60,10 @@ public class QSFactoryImpl implements QSFactory { public QSFactoryImpl( Lazy<QSHost> qsHostLazy, Provider<CustomTile.Builder> customTileBuilderProvider, - Provider<InternetTile> internetTileProvider, - Provider<BluetoothTile> bluetoothTileProvider, - Provider<DndTile> dndTileProvider, - Provider<ColorInversionTile> colorInversionTileProvider, - Provider<AirplaneModeTile> airplaneModeTileProvider, - Provider<WorkModeTile> workModeTileProvider, - Provider<RotationLockTile> rotationLockTileProvider, - Provider<FlashlightTile> flashlightTileProvider, - Provider<LocationTile> locationTileProvider, - Provider<CastTile> castTileProvider, - Provider<HotspotTile> hotspotTileProvider, - Provider<BatterySaverTile> batterySaverTileProvider, - Provider<DataSaverTile> dataSaverTileProvider, - Provider<NightDisplayTile> nightDisplayTileProvider, - Provider<NfcTile> nfcTileProvider, - Provider<GarbageMonitor.MemoryTile> memoryTileProvider, - Provider<UiModeNightTile> uiModeNightTileProvider, - Provider<ScreenRecordTile> screenRecordTileProvider, - Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider, - Provider<CameraToggleTile> cameraToggleTileProvider, - Provider<MicrophoneToggleTile> microphoneToggleTileProvider, - Provider<DeviceControlsTile> deviceControlsTileProvider, - Provider<AlarmTile> alarmTileProvider, - Provider<QuickAccessWalletTile> quickAccessWalletTileProvider, - Provider<QRCodeScannerTile> qrCodeScannerTileProvider, - Provider<OneHandedModeTile> oneHandedModeTileProvider, - Provider<ColorCorrectionTile> colorCorrectionTileProvider, - Provider<DreamTile> dreamTileProvider, - Provider<FontScalingTile> fontScalingTileProvider) { + Map<String, Provider<QSTileImpl<?>>> tileMap) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; - - mInternetTileProvider = internetTileProvider; - mBluetoothTileProvider = bluetoothTileProvider; - mDndTileProvider = dndTileProvider; - mColorInversionTileProvider = colorInversionTileProvider; - mAirplaneModeTileProvider = airplaneModeTileProvider; - mWorkModeTileProvider = workModeTileProvider; - mRotationLockTileProvider = rotationLockTileProvider; - mFlashlightTileProvider = flashlightTileProvider; - mLocationTileProvider = locationTileProvider; - mCastTileProvider = castTileProvider; - mHotspotTileProvider = hotspotTileProvider; - mBatterySaverTileProvider = batterySaverTileProvider; - mDataSaverTileProvider = dataSaverTileProvider; - mNightDisplayTileProvider = nightDisplayTileProvider; - mNfcTileProvider = nfcTileProvider; - mMemoryTileProvider = memoryTileProvider; - mUiModeNightTileProvider = uiModeNightTileProvider; - mScreenRecordTileProvider = screenRecordTileProvider; - mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider; - mCameraToggleTileProvider = cameraToggleTileProvider; - mMicrophoneToggleTileProvider = microphoneToggleTileProvider; - mDeviceControlsTileProvider = deviceControlsTileProvider; - mAlarmTileProvider = alarmTileProvider; - mQuickAccessWalletTileProvider = quickAccessWalletTileProvider; - mQRCodeScannerTileProvider = qrCodeScannerTileProvider; - mOneHandedModeTileProvider = oneHandedModeTileProvider; - mColorCorrectionTileProvider = colorCorrectionTileProvider; - mDreamTileProvider = dreamTileProvider; - mFontScalingTileProvider = fontScalingTileProvider; + mTileMap = tileMap; } /** Creates a tile with a type based on {@code tileSpec} */ @@ -181,63 +80,10 @@ public class QSFactoryImpl implements QSFactory { @Nullable protected QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. - switch (tileSpec) { - case "internet": - return mInternetTileProvider.get(); - case "bt": - return mBluetoothTileProvider.get(); - case "dnd": - return mDndTileProvider.get(); - case "inversion": - return mColorInversionTileProvider.get(); - case "airplane": - return mAirplaneModeTileProvider.get(); - case "work": - return mWorkModeTileProvider.get(); - case "rotation": - return mRotationLockTileProvider.get(); - case "flashlight": - return mFlashlightTileProvider.get(); - case "location": - return mLocationTileProvider.get(); - case "cast": - return mCastTileProvider.get(); - case "hotspot": - return mHotspotTileProvider.get(); - case "battery": - return mBatterySaverTileProvider.get(); - case "saver": - return mDataSaverTileProvider.get(); - case "night": - return mNightDisplayTileProvider.get(); - case "nfc": - return mNfcTileProvider.get(); - case "dark": - return mUiModeNightTileProvider.get(); - case "screenrecord": - return mScreenRecordTileProvider.get(); - case "reduce_brightness": - return mReduceBrightColorsTileProvider.get(); - case "cameratoggle": - return mCameraToggleTileProvider.get(); - case "mictoggle": - return mMicrophoneToggleTileProvider.get(); - case "controls": - return mDeviceControlsTileProvider.get(); - case "alarm": - return mAlarmTileProvider.get(); - case "wallet": - return mQuickAccessWalletTileProvider.get(); - case "qr_code_scanner": - return mQRCodeScannerTileProvider.get(); - case "onehanded": - return mOneHandedModeTileProvider.get(); - case "color_correction": - return mColorCorrectionTileProvider.get(); - case "dream": - return mDreamTileProvider.get(); - case "font_scaling": - return mFontScalingTileProvider.get(); + if (mTileMap.containsKey(tileSpec) + // We should not return a Garbage Monitory Tile if the build is not Debuggable + && (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) { + return mTileMap.get(tileSpec).get(); } // Custom tiles @@ -246,13 +92,6 @@ public class QSFactoryImpl implements QSFactory { mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext()); } - // Debug tiles. - if (Build.IS_DEBUGGABLE) { - if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) { - return mMemoryTileProvider.get(); - } - } - // Broken tiles. Log.w(TAG, "No stock tile spec: " + tileSpec); return null; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 033dbe0f82ee..92a83bba8a68 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -57,6 +57,9 @@ import dagger.Lazy; /** Quick settings tile: Airplane mode **/ public class AirplaneModeTile extends QSTileImpl<BooleanState> { + + public static final String TILE_SPEC = "airplane"; + private final SettingObserver mSetting; private final BroadcastDispatcher mBroadcastDispatcher; private final Lazy<ConnectivityManager> mLazyConnectivityManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 14e0f707d3b5..2ca452e45ecf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -118,4 +118,8 @@ class AlarmTile @Inject constructor( override fun getLongClickIntent(): Intent? { return null } -}
\ No newline at end of file + + companion object { + const val TILE_SPEC = "alarm" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index ee49b294dfcd..027a464251c9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -48,6 +48,8 @@ import javax.inject.Inject; public class BatterySaverTile extends QSTileImpl<BooleanState> implements BatteryController.BatteryStateChangeCallback { + public static final String TILE_SPEC = "battery"; + private final BatteryController mBatteryController; @VisibleForTesting protected final SettingObserver mSetting; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 9a0d0d9656ca..df1c8dfdde96 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -55,6 +55,9 @@ import javax.inject.Inject; /** Quick settings tile: Bluetooth **/ public class BluetoothTile extends QSTileImpl<BooleanState> { + + public static final String TILE_SPEC = "bt"; + private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); private final BluetoothController mController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index ee41f1d1077d..93e5f1efbdc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -45,6 +45,8 @@ import javax.inject.Inject; public class CameraToggleTile extends SensorPrivacyToggleTile { + public static final String TILE_SPEC = "cameratoggle"; + @Inject protected CameraToggleTile(QSHost host, @Background Looper backgroundLooper, 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 dce137f5b9f3..8d984817ba77 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -66,7 +66,9 @@ import javax.inject.Inject; /** Quick settings tile: Cast **/ public class CastTile extends QSTileImpl<BooleanState> { - private static final String INTERACTION_JANK_TAG = "cast"; + public static final String TILE_SPEC = "cast"; + + private static final String INTERACTION_JANK_TAG = TILE_SPEC; private static final Intent CAST_SETTINGS = new Intent(Settings.ACTION_CAST_SETTINGS); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java index 6dfcf5ccc258..b6205d5df63d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -48,6 +48,8 @@ import javax.inject.Inject; /** Quick settings tile: Color correction **/ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { + public static final String TILE_SPEC = "color_correction"; + private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction); private final SettingObserver mSetting; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index a31500c6bb18..9a44e83ad9a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -49,6 +49,7 @@ import javax.inject.Inject; /** Quick settings tile: Invert colors **/ public class ColorInversionTile extends QSTileImpl<BooleanState> { + public static final String TILE_SPEC = "inversion"; private final SettingObserver mSetting; @Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 2fc99f323611..add517e18516 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -48,6 +48,8 @@ import javax.inject.Inject; public class DataSaverTile extends QSTileImpl<BooleanState> implements DataSaverController.Listener{ + public static final String TILE_SPEC = "saver"; + private static final String INTERACTION_JANK_TAG = "start_data_saver"; private final DataSaverController mDataSaverController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 41d854969e20..01164fb0a009 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -159,4 +159,8 @@ class DeviceControlsTile @Inject constructor( override fun getTileLabel(): CharSequence { return mContext.getText(controlsComponent.getTileTitleId()) } + + companion object { + const val TILE_SPEC = "controls" + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 8b7f53fa5a3f..434fe45f47c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -67,6 +67,8 @@ import javax.inject.Inject; /** Quick settings tile: Do not disturb **/ public class DndTile extends QSTileImpl<BooleanState> { + public static final String TILE_SPEC = "dnd"; + private static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index 5bc209a840cb..53774e82602b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -60,6 +60,8 @@ import javax.inject.Named; /** Quick settings tile: Screensaver (dream) **/ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { + public static final String TILE_SPEC = "dream"; + private static final String LOG_TAG = "QSDream"; // TODO: consider 1 animated icon instead private final Icon mIconDocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index a74792687289..e091a750fbec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -49,6 +49,7 @@ import javax.inject.Inject; public class FlashlightTile extends QSTileImpl<BooleanState> implements FlashlightController.FlashlightListener { + public static final String TILE_SPEC = "flashlight"; private final FlashlightController mFlashlightController; @Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index 4d8f89edd969..721046d217c9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -19,8 +19,12 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.view.View +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.R +import com.android.systemui.accessibility.fontscaling.FontScalingDialog +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -30,6 +34,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject class FontScalingTile @@ -42,7 +48,9 @@ constructor( metricsLogger: MetricsLogger, statusBarStateController: StatusBarStateController, activityStarter: ActivityStarter, - qsLogger: QSLogger + qsLogger: QSLogger, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val systemSettings: SystemSettings ) : QSTileImpl<QSTile.State?>( host, @@ -54,7 +62,7 @@ constructor( activityStarter, qsLogger ) { - private val mIcon = ResourceIcon.get(R.drawable.ic_qs_font_scaling) + private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling) override fun isAvailable(): Boolean { return false @@ -66,11 +74,24 @@ constructor( return state } - override fun handleClick(view: View?) {} + override fun handleClick(view: View?) { + mUiHandler.post { + val dialog: SystemUIDialog = FontScalingDialog(mContext, systemSettings) + if (view != null) { + dialogLaunchAnimator.showFromView( + dialog, + view, + DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG) + ) + } else { + dialog.show() + } + } + } override fun handleUpdateState(state: QSTile.State?, arg: Any?) { state?.label = mContext.getString(R.string.quick_settings_font_scaling_label) - state?.icon = mIcon + state?.icon = icon } override fun getLongClickIntent(): Intent? { @@ -80,4 +101,9 @@ constructor( override fun getTileLabel(): CharSequence { return mContext.getString(R.string.quick_settings_font_scaling_label) } + + companion object { + const val TILE_SPEC = "font_scaling" + private const val INTERACTION_JANK_TAG = "font_scaling" + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 624def60276b..6bf8b7666054 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -51,6 +51,7 @@ import javax.inject.Inject; /** Quick settings tile: Hotspot **/ public class HotspotTile extends QSTileImpl<BooleanState> { + public static final String TILE_SPEC = "hotspot"; private final HotspotController mHotspotController; private final DataSaverController mDataSaverController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 1c60486a6909..12e9aebc58f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -67,6 +67,9 @@ import javax.inject.Inject; /** Quick settings tile: Internet **/ public class InternetTile extends QSTileImpl<SignalState> { + + public static final String TILE_SPEC = "internet"; + private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS); protected final NetworkController mController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 9466a694ea1b..89d402a3af5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -48,6 +48,8 @@ import javax.inject.Inject; /** Quick settings tile: Location **/ public class LocationTile extends QSTileImpl<BooleanState> { + public static final String TILE_SPEC = "location"; + private final LocationController mController; private final KeyguardStateController mKeyguard; private final Callback mCallback = new Callback(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index e54709562c10..2e475d40d55f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -45,6 +45,8 @@ import javax.inject.Inject; public class MicrophoneToggleTile extends SensorPrivacyToggleTile { + public static final String TILE_SPEC = "mictoggle"; + @Inject protected MicrophoneToggleTile(QSHost host, @Background Looper backgroundLooper, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index a61f0ce0c864..e189f80a7c23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -51,7 +51,9 @@ import javax.inject.Inject; /** Quick settings tile: Enable/Disable NFC **/ public class NfcTile extends QSTileImpl<BooleanState> { - private static final String NFC = "nfc"; + public static final String TILE_SPEC = "nfc"; + + private static final String NFC = TILE_SPEC; private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_nfc); @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 0e9f6599522f..aacd53bde51c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -61,6 +61,8 @@ import javax.inject.Inject; public class NightDisplayTile extends QSTileImpl<BooleanState> implements NightDisplayListener.Callback { + public static final String TILE_SPEC = "night"; + /** * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the * nearest hour and add on the AM/PM indicator. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index 7e1712455e72..ae67d99ea2fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -47,6 +47,9 @@ import javax.inject.Inject; /** Quick settings tile: One-handed mode **/ public class OneHandedModeTile extends QSTileImpl<BooleanState> { + + public static final String TILE_SPEC = "onehanded"; + private final Icon mIcon = ResourceIcon.get( com.android.internal.R.drawable.ic_qs_one_handed_mode); private final SettingObserver mSetting; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index 6d50b562cd02..92f52724ca0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -44,6 +44,9 @@ import javax.inject.Inject; /** Quick settings tile: QR Code Scanner **/ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { + + public static final String TILE_SPEC = "qr_code_scanner"; + private static final String TAG = "QRCodeScanner"; private final CharSequence mLabel = mContext.getString(R.string.qr_code_scanner_title); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 248c78e557cc..4a3c56328006 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -62,6 +62,8 @@ import javax.inject.Inject; /** Quick settings tile: Quick access wallet **/ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { + public static final String TILE_SPEC = "wallet"; + private static final String TAG = "QuickAccessWalletTile"; private static final String FEATURE_CHROME_OS = "org.chromium.arc"; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index 1dac33909ba7..10f1ce4946c8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -49,6 +49,7 @@ import javax.inject.Named; public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> implements ReduceBrightColorsController.Listener{ + public static final String TILE_SPEC = "reduce_brightness"; private final boolean mIsAvailable; private final ReduceBrightColorsController mReduceBrightColorsController; private boolean mIsListening; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 600874f0d01a..8888c733c3c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -57,6 +57,9 @@ import javax.inject.Inject; /** Quick settings tile: Rotation **/ public class RotationLockTile extends QSTileImpl<BooleanState> implements BatteryController.BatteryStateChangeCallback { + + public static final String TILE_SPEC = "rotation"; + private static final String EMPTY_SECONDARY_STRING = ""; private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index ad000690a354..07b50c9a66f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -54,6 +54,9 @@ import javax.inject.Inject; */ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> implements RecordingController.RecordingStateChangeCallback { + + public static final String TILE_SPEC = "screenrecord"; + private static final String TAG = "ScreenRecordTile"; private static final String INTERACTION_JANK_TAG = "screen_record"; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 92f6690a13e7..809689cb806d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -17,7 +17,6 @@ package com.android.systemui.qs.tiles; import android.app.UiModeManager; -import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.os.Handler; @@ -60,6 +59,9 @@ import javax.inject.Inject; public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements ConfigurationController.ConfigurationListener, BatteryController.BatteryStateChangeCallback { + + public static final String TILE_SPEC = "dark"; + public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a"); private final UiModeManager mUiModeManager; private final BatteryController mBatteryController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 72c6bfe371ce..6a5c99032457 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -49,6 +49,9 @@ import javax.inject.Inject; /** Quick settings tile: Work profile on/off */ public class WorkModeTile extends QSTileImpl<BooleanState> implements ManagedProfileController.Callback { + + public static final String TILE_SPEC = "work"; + private final Icon mIcon = ResourceIcon.get(R.drawable.stat_sys_managed_profile_status); private final ManagedProfileController mProfileController; diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockModule.kt new file mode 100644 index 000000000000..9abe90fb05d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockModule.kt @@ -0,0 +1,34 @@ +/* + * 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.rotationlock + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.RotationLockTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface RotationLockModule { + + /** Inject RotationLockTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(RotationLockTile.TILE_SPEC) + fun bindRotationLockTile(rotationLockTile: RotationLockTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt new file mode 100644 index 000000000000..7467805d73ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenrecord + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.ScreenRecordTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface ScreenRecordModule { + /** Inject ScreenRecordTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ScreenRecordTile.TILE_SPEC) + fun bindScreenRecordTile(screenRecordTile: ScreenRecordTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt new file mode 100644 index 000000000000..1099810f972f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -0,0 +1,73 @@ +/* + * 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.connectivity + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.AirplaneModeTile +import com.android.systemui.qs.tiles.BluetoothTile +import com.android.systemui.qs.tiles.CastTile +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.NfcTile +import dagger.Binds +import dagger.Module +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 + @StringKey(BluetoothTile.TILE_SPEC) + fun bindBluetoothTile(bluetoothTile: BluetoothTile): QSTileImpl<*> + + /** Inject CastTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(CastTile.TILE_SPEC) + fun bindCastTile(castTile: CastTile): QSTileImpl<*> + + /** Inject HotspotTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(HotspotTile.TILE_SPEC) + fun bindHotspotTile(hotspotTile: HotspotTile): QSTileImpl<*> + + /** Inject AirplaneModeTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(AirplaneModeTile.TILE_SPEC) + fun bindAirplaneModeTile(airplaneModeTile: AirplaneModeTile): QSTileImpl<*> + + /** Inject DataSaverTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(DataSaverTile.TILE_SPEC) + fun bindDataSaverTile(dataSaverTile: DataSaverTile): QSTileImpl<*> + + /** Inject NfcTile into tileMap in QSModule */ + @Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt index 2f34516285cf..16c4027ef645 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model +import android.os.ParcelUuid + /** * SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the * subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be @@ -29,4 +31,7 @@ data class SubscriptionModel( * filtering in certain cases. See [MobileIconsInteractor] for the filtering logic */ val isOpportunistic: Boolean = false, + + /** Subscriptions in the same group may be filtered or treated as a single subscription */ + val groupUuid: ParcelUuid? = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index 938c7346f702..8f6a87b089f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -28,8 +28,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index c9049d893f4a..73ce5e616b82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -52,8 +52,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -376,6 +376,7 @@ constructor( SubscriptionModel( subscriptionId = subscriptionId, isOpportunistic = isOpportunistic, + groupUuid = groupUuid, ) companion object { 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 72d5113e5938..5a2e11e8de88 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 @@ -150,6 +150,12 @@ constructor( val info1 = unfilteredSubs[0] val info2 = unfilteredSubs[1] + + // Filtering only applies to subscriptions in the same group + if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) { + return@combine unfilteredSubs + } + // If both subscriptions are primary, show both if (!info1.isOpportunistic && !info2.isOpportunistic) { return@combine unfilteredSubs @@ -186,7 +192,7 @@ constructor( * validated bit from the old active network (A) while data is changing to the new one (B). * * This condition only applies if - * 1. A and B are in the same subscription group (e.c. for CBRS data switching) and + * 1. A and B are in the same subscription group (e.g. for CBRS data switching) and * 2. A was validated before the switch * * The goal of this is to minimize the flickering in the UI of the cellular indicator 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 ac4d55c3a29c..08c14e743bb6 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 @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import kotlinx.coroutines.flow.StateFlow /** Provides data related to the wifi state. */ 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 2cb81c809716..e0e0ed795e4a 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 @@ -23,9 +23,9 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel 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 javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi 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 a19c3c3e86a6..a4fbc2c93647 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 @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo import com.android.systemui.dagger.qualifiers.Application 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.data.model.WifiNetworkModel 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 javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job 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 5d4a6664a19a..86a668a2e842 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 @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow 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 c45b420780b9..7b486c1998cd 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 @@ -43,9 +43,9 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange 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.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope 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 86dcd18c643c..96ab074c6e56 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 @@ -21,8 +21,8 @@ import com.android.systemui.dagger.SysUISingleton 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.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -58,25 +58,29 @@ interface WifiInteractor { } @SysUISingleton -class WifiInteractorImpl @Inject constructor( +class WifiInteractorImpl +@Inject +constructor( connectivityRepository: ConnectivityRepository, wifiRepository: WifiRepository, ) : WifiInteractor { - override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info -> - when (info) { - is WifiNetworkModel.Unavailable -> null - is WifiNetworkModel.Invalid -> null - is WifiNetworkModel.Inactive -> null - is WifiNetworkModel.CarrierMerged -> null - is WifiNetworkModel.Active -> when { - info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> - info.passpointProviderFriendlyName - info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid - else -> null + override val ssid: Flow<String?> = + wifiRepository.wifiNetwork.map { info -> + when (info) { + is WifiNetworkModel.Unavailable -> null + is WifiNetworkModel.Invalid -> null + is WifiNetworkModel.Inactive -> null + is WifiNetworkModel.CarrierMerged -> null + is WifiNetworkModel.Active -> + when { + info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> + info.passpointProviderFriendlyName + info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + else -> null + } } } - } override val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled @@ -86,7 +90,6 @@ class WifiInteractorImpl @Inject constructor( override val activity: StateFlow<DataActivityModel> = wifiRepository.wifiActivity - override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map { - it.contains(ConnectivitySlot.WIFI) - } + override val isForceHidden: Flow<Boolean> = + connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.WIFI) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index da2daf2c55ea..0923d7848d8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.wifi.data.model +package com.android.systemui.statusbar.pipeline.wifi.shared.model -import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID +import android.telephony.SubscriptionManager import androidx.annotation.VisibleForTesting -import com.android.systemui.log.table.TableRowLogger import com.android.systemui.log.table.Diffable -import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS +import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { @@ -57,9 +57,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } - /** - * A model representing that the wifi information we received was invalid in some way. - */ + /** A model representing that the wifi information we received was invalid in some way. */ data class Invalid( /** A description of why the wifi information was invalid. */ val invalidReason: String, @@ -142,21 +140,17 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { */ val subscriptionId: Int, - /** - * The signal level, guaranteed to be 0 <= level <= numberOfLevels. - */ + /** The signal level, guaranteed to be 0 <= level <= numberOfLevels. */ val level: Int, - /** - * The maximum possible level. - */ - val numberOfLevels: Int = DEFAULT_NUM_LEVELS, + /** The maximum possible level. */ + val numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS, ) : WifiNetworkModel() { init { require(level in MIN_VALID_LEVEL..numberOfLevels) { "0 <= wifi level <= $numberOfLevels required; level was $level" } - require(subscriptionId != INVALID_SUBSCRIPTION_ID) { + require(subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { "subscription ID cannot be invalid" } } @@ -208,9 +202,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */ val isValidated: Boolean = false, - /** - * The wifi signal level, guaranteed to be 0 <= level <= 4. - */ + /** The wifi signal level, guaranteed to be 0 <= level <= 4. */ val level: Int, /** See [android.net.wifi.WifiInfo.ssid]. */ @@ -255,8 +247,10 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) { row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint) } - if (prevVal.isOnlineSignUpForPasspointAccessPoint != - isOnlineSignUpForPasspointAccessPoint) { + if ( + prevVal.isOnlineSignUpForPasspointAccessPoint != + isOnlineSignUpForPasspointAccessPoint + ) { row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint) } if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) { @@ -281,29 +275,29 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { // Only include the passpoint-related values in the string if we have them. (Most // networks won't have them so they'll be mostly clutter.) val passpointString = - if (isPasspointAccessPoint || - isOnlineSignUpForPasspointAccessPoint || - passpointProviderFriendlyName != null) { + if ( + isPasspointAccessPoint || + isOnlineSignUpForPasspointAccessPoint || + passpointProviderFriendlyName != null + ) { ", isPasspointAp=$isPasspointAccessPoint, " + "isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " + "passpointName=$passpointProviderFriendlyName" - } else { - "" - } + } else { + "" + } return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " + "level=$level, ssid=$ssid$passpointString)" } companion object { - @VisibleForTesting - internal const val MAX_VALID_LEVEL = 4 + @VisibleForTesting internal const val MAX_VALID_LEVEL = 4 } } companion object { - @VisibleForTesting - internal const val MIN_VALID_LEVEL = 0 + @VisibleForTesting internal const val MIN_VALID_LEVEL = 0 } } 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 95431afb71bb..0f5ff91866fa 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 @@ -25,8 +25,6 @@ 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.statusbar.pipeline.dagger.WifiTableLog -import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS @@ -34,13 +32,15 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_IC import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel +import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor 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 javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -55,15 +55,12 @@ import kotlinx.coroutines.flow.stateIn /** * Models the UI state for the status bar wifi icon. * - * This class exposes three view models, one per status bar location: - * - [home] - * - [keyguard] - * - [qs] - * In order to get the UI state for the wifi icon, you must use one of those view models (whichever - * is correct for your location). + * This class exposes three view models, one per status bar location: [home], [keyguard], and [qs]. + * In order to get the UI state for the wifi icon, you must use one of those view models (whichever + * is correct for your location). * - * Internally, this class maintains the current state of the wifi icon and notifies those three - * view models of any changes. + * Internally, this class maintains the current state of the wifi icon and notifies those three view + * models of any changes. */ @SysUISingleton class WifiViewModel @@ -85,12 +82,13 @@ constructor( 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.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 { @@ -114,25 +112,25 @@ constructor( /** The wifi icon that should be displayed. */ private val wifiIcon: StateFlow<WifiIcon> = combine( - interactor.isEnabled, - interactor.isDefault, - interactor.isForceHidden, - interactor.wifiNetwork, - ) { isEnabled, isDefault, isForceHidden, wifiNetwork -> - if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) { - return@combine WifiIcon.Hidden - } + interactor.isEnabled, + interactor.isDefault, + interactor.isForceHidden, + interactor.wifiNetwork, + ) { isEnabled, isDefault, isForceHidden, wifiNetwork -> + if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) { + return@combine WifiIcon.Hidden + } - val icon = wifiNetwork.icon() + val icon = wifiNetwork.icon() - return@combine when { - isDefault -> icon - wifiConstants.alwaysShowIconIfEnabled -> icon - !connectivityConstants.hasDataCapabilities -> icon - wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon - else -> WifiIcon.Hidden + return@combine when { + isDefault -> icon + wifiConstants.alwaysShowIconIfEnabled -> icon + !connectivityConstants.hasDataCapabilities -> icon + wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon + else -> WifiIcon.Hidden + } } - } .logDiffsForTable( wifiTableLogBuffer, columnPrefix = "", @@ -147,34 +145,34 @@ constructor( /** The wifi activity status. Null if we shouldn't display the activity status. */ private val activity: Flow<DataActivityModel?> = if (!connectivityConstants.shouldShowActivityConfig) { - flowOf(null) - } else { - combine(interactor.activity, interactor.ssid) { activity, ssid -> - when (ssid) { - null -> null - else -> activity + flowOf(null) + } else { + combine(interactor.activity, interactor.ssid) { activity, ssid -> + when (ssid) { + null -> null + else -> activity + } } } - } - .distinctUntilChanged() - .logOutputChange(logger, "activity") - .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null) + .distinctUntilChanged() + .logOutputChange(logger, "activity") + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null) private val isActivityInViewVisible: Flow<Boolean> = - activity - .map { it?.hasActivityIn == true } - .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) + activity + .map { it?.hasActivityIn == true } + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) private val isActivityOutViewVisible: Flow<Boolean> = - activity - .map { it?.hasActivityOut == true } - .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) + activity + .map { it?.hasActivityOut == true } + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) private val isActivityContainerVisible: Flow<Boolean> = - combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut -> - activityIn || activityOut - } - .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) + combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut -> + activityIn || activityOut + } + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false) // TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the // airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt new file mode 100644 index 000000000000..2a18b8149637 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use mHost 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.policy + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.AlarmTile +import com.android.systemui.qs.tiles.CameraToggleTile +import com.android.systemui.qs.tiles.DndTile +import com.android.systemui.qs.tiles.FlashlightTile +import com.android.systemui.qs.tiles.LocationTile +import com.android.systemui.qs.tiles.MicrophoneToggleTile +import com.android.systemui.qs.tiles.UiModeNightTile +import com.android.systemui.qs.tiles.WorkModeTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface PolicyModule { + + /** Inject DndTile into tileMap in QSModule */ + @Binds @IntoMap @StringKey(DndTile.TILE_SPEC) fun bindDndTile(dndTile: DndTile): QSTileImpl<*> + + /** Inject WorkModeTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(WorkModeTile.TILE_SPEC) + fun bindWorkModeTile(workModeTile: WorkModeTile): QSTileImpl<*> + + /** Inject FlashlightTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(FlashlightTile.TILE_SPEC) + fun bindFlashlightTile(flashlightTile: FlashlightTile): QSTileImpl<*> + + /** Inject LocationTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(LocationTile.TILE_SPEC) + fun bindLocationTile(locationTile: LocationTile): QSTileImpl<*> + + /** Inject CameraToggleTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(CameraToggleTile.TILE_SPEC) + fun bindCameraToggleTile(cameraToggleTile: CameraToggleTile): QSTileImpl<*> + + /** Inject MicrophoneToggleTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(MicrophoneToggleTile.TILE_SPEC) + fun bindMicrophoneToggleTile(microphoneToggleTile: MicrophoneToggleTile): QSTileImpl<*> + + /** Inject AlarmTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(AlarmTile.TILE_SPEC) + fun bindAlarmTile(alarmTile: AlarmTile): QSTileImpl<*> + + @Binds + @IntoMap + @StringKey(UiModeNightTile.TILE_SPEC) + fun bindUiModeNightTile(uiModeNightTile: UiModeNightTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java index 2b29885db682..f7c8bac1b478 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java +++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java @@ -21,6 +21,7 @@ import android.os.UserHandle; import com.android.settingslib.users.EditUserInfoController; import com.android.systemui.user.data.repository.UserRepositoryModule; +import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule; import com.android.systemui.user.ui.dialog.UserDialogModule; import dagger.Binds; @@ -36,6 +37,7 @@ import dagger.multibindings.IntoMap; includes = { UserDialogModule.class, UserRepositoryModule.class, + HeadlessSystemUserModeModule.class, } ) public abstract class UserModule { diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt new file mode 100644 index 000000000000..756e6a1a5b15 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.domain.interactor + +import android.os.UserManager +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +interface HeadlessSystemUserMode { + + fun isHeadlessSystemUserMode(): Boolean +} + +@SysUISingleton +class HeadlessSystemUserModeImpl @Inject constructor() : HeadlessSystemUserMode { + override fun isHeadlessSystemUserMode(): Boolean { + return UserManager.isHeadlessSystemUserMode() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt new file mode 100644 index 000000000000..0efa2d8b6a36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt @@ -0,0 +1,26 @@ +/* + * 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.user.domain.interactor + +import dagger.Binds + +@dagger.Module +interface HeadlessSystemUserModeModule { + + @Binds + fun bindIsHeadlessSystemUserMode(impl: HeadlessSystemUserModeImpl): HeadlessSystemUserMode +} diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index c0ba3cc352b0..3f895ad0b5b4 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -86,6 +86,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val featureFlags: FeatureFlags, private val manager: UserManager, + private val headlessSystemUserMode: HeadlessSystemUserMode, @Application private val applicationScope: CoroutineScope, telephonyInteractor: TelephonyInteractor, broadcastDispatcher: BroadcastDispatcher, @@ -560,7 +561,10 @@ constructor( actionType = action, isRestricted = isRestricted, isSwitchToEnabled = - canSwitchUsers(selectedUserId) && + canSwitchUsers( + selectedUserId = selectedUserId, + isAction = true, + ) && // If the user is auto-created is must not be currently resetting. !(isGuestUserAutoCreated && isGuestUserResetting), ) @@ -712,10 +716,32 @@ constructor( } } - private suspend fun canSwitchUsers(selectedUserId: Int): Boolean { - return withContext(backgroundDispatcher) { - manager.getUserSwitchability(UserHandle.of(selectedUserId)) - } == UserManager.SWITCHABILITY_STATUS_OK + private suspend fun canSwitchUsers( + selectedUserId: Int, + isAction: Boolean = false, + ): Boolean { + val isHeadlessSystemUserMode = + withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() } + // Whether menu item should be active. True if item is a user or if any user has + // signed in since reboot or in all cases for non-headless system user mode. + val isItemEnabled = !isAction || !isHeadlessSystemUserMode || isAnyUserUnlocked() + return isItemEnabled && + withContext(backgroundDispatcher) { + manager.getUserSwitchability(UserHandle.of(selectedUserId)) + } == UserManager.SWITCHABILITY_STATUS_OK + } + + private suspend fun isAnyUserUnlocked(): Boolean { + return manager + .getUsers( + /* excludePartial= */ true, + /* excludeDying= */ true, + /* excludePreCreated= */ true + ) + .any { user -> + user.id != UserHandle.USER_SYSTEM && + withContext(backgroundDispatcher) { manager.isUserUnlocked(user.userHandle) } + } } @SuppressLint("UseCompatLoadingForDrawables") diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java index b41bca0d77e1..8d32a4833471 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java @@ -43,11 +43,6 @@ public abstract class ConditionalCoreStartable implements CoreStartable { @Override public final void start() { - if (mConditionSet == null || mConditionSet.isEmpty()) { - onStart(); - return; - } - mStartToken = mMonitor.addSubscription( new Monitor.Subscription.Builder(allConditionsMet -> { if (allConditionsMet) { @@ -63,11 +58,6 @@ public abstract class ConditionalCoreStartable implements CoreStartable { @Override public final void onBootCompleted() { - if (mConditionSet == null || mConditionSet.isEmpty()) { - bootCompleted(); - return; - } - mBootCompletedToken = mMonitor.addSubscription( new Monitor.Subscription.Builder(allConditionsMet -> { if (allConditionsMet) { diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt new file mode 100644 index 000000000000..c74e71f668f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.leak + +import com.android.systemui.CoreStartable +import com.android.systemui.qs.tileimpl.QSTileImpl +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface GarbageMonitorModule { + /** Inject into GarbageMonitor.Service. */ + @Binds + @IntoMap + @ClassKey(GarbageMonitor::class) + fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable + + @Binds + @IntoMap + @StringKey(GarbageMonitor.MemoryTile.TILE_SPEC) + fun bindMemoryTile(memoryTile: GarbageMonitor.MemoryTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java index 2c901d285939..9429d8991090 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java @@ -22,6 +22,8 @@ import android.service.quickaccesswallet.QuickAccessWalletClient; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.QuickAccessWalletTile; import com.android.systemui.wallet.ui.WalletActivity; import java.util.concurrent.Executor; @@ -31,6 +33,7 @@ import dagger.Module; import dagger.Provides; import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; +import dagger.multibindings.StringKey; /** @@ -52,4 +55,11 @@ public abstract class WalletModule { @Background Executor bgExecutor) { return QuickAccessWalletClient.create(context, bgExecutor); } + + /** */ + @Binds + @IntoMap + @StringKey(QuickAccessWalletTile.TILE_SPEC) + public abstract QSTileImpl<?> bindQuickAccessWalletTile( + QuickAccessWalletTile quickAccessWalletTile); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index e8d50ca4bc76..badeb27e7696 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -24,13 +24,19 @@ import android.os.Handler import android.os.PowerManager import android.os.PowerManager.WAKE_REASON_BIOMETRIC import android.os.UserHandle -import android.provider.Settings +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE +import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq -import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.FakeSettings import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -41,20 +47,11 @@ import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.io.PrintWriter @SmallTest class ActiveUnlockConfigTest : SysuiTestCase() { - private val fakeWakeUri = Uri.Builder().appendPath("wake").build() - private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build() - private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build() - private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build() - private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build() - private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build() - private val fakeWakeupsConsideredUnlockIntents = - Uri.Builder().appendPath("wakeups-considered-unlock-intent").build() - - @Mock - private lateinit var secureSettings: SecureSettings + private lateinit var secureSettings: FakeSettings @Mock private lateinit var contentResolver: ContentResolver @Mock @@ -63,33 +60,20 @@ class ActiveUnlockConfigTest : SysuiTestCase() { private lateinit var dumpManager: DumpManager @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var mockPrintWriter: PrintWriter @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> private lateinit var activeUnlockConfig: ActiveUnlockConfig + private var currentUser: Int = 0 @Before fun setUp() { MockitoAnnotations.initMocks(this) - `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE)) - .thenReturn(fakeWakeUri) - `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) - .thenReturn(fakeUnlockIntentUri) - `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) - .thenReturn(fakeBioFailUri) - `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS)) - .thenReturn(fakeFaceErrorsUri) - `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) - .thenReturn(fakeFaceAcquiredUri) - `when`(secureSettings.getUriFor( - Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)) - .thenReturn(fakeUnlockIntentBioEnroll) - `when`(secureSettings.getUriFor( - Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) - .thenReturn(fakeWakeupsConsideredUnlockIntents) - + currentUser = KeyguardUpdateMonitor.getCurrentUser() + secureSettings = FakeSettings() activeUnlockConfig = ActiveUnlockConfig( handler, secureSettings, @@ -105,8 +89,6 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun onWakeupSettingChanged() { - verifyRegisterSettingObserver() - // GIVEN no active unlock settings enabled assertFalse( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -114,9 +96,8 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) // WHEN unlock on wake is allowed - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, - 0, 0)).thenReturn(1) - updateSetting(fakeWakeUri) + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_WAKE, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)) // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure assertTrue( @@ -135,8 +116,6 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun onUnlockIntentSettingChanged() { - verifyRegisterSettingObserver() - // GIVEN no active unlock settings enabled assertFalse( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -144,9 +123,8 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) // WHEN unlock on biometric failed is allowed - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, - 0, 0)).thenReturn(1) - updateSetting(fakeUnlockIntentUri) + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -159,21 +137,19 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun onBioFailSettingChanged() { - verifyRegisterSettingObserver() - // GIVEN no active unlock settings enabled and triggering unlock intent on biometric // enrollment setting is disabled (empty string is disabled, null would use the default) - `when`(secureSettings.getStringForUser( - Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - 0)).thenReturn("") - updateSetting(fakeUnlockIntentBioEnroll) + secureSettings.putStringForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser) + updateSetting(secureSettings.getUriFor( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED + )) assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)) // WHEN unlock on biometric failed is allowed - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, - 0, 0)).thenReturn(1) - updateSetting(fakeBioFailUri) + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -186,17 +162,14 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun faceErrorSettingsChanged() { - verifyRegisterSettingObserver() - // GIVEN unlock on biometric fail - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, - 0, 0)).thenReturn(1) - updateSetting(fakeBioFailUri) + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // WHEN face error timeout (3), allow trigger active unlock - `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, - 0)).thenReturn("3") - updateSetting(fakeFaceAcquiredUri) + secureSettings.putStringForUser( + ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)) // THEN active unlock triggers allowed on error TIMEOUT assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( @@ -208,19 +181,17 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun faceAcquiredSettingsChanged() { - verifyRegisterSettingObserver() - // GIVEN unlock on biometric fail - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, - 0, 0)).thenReturn(1) - updateSetting(fakeBioFailUri) + secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, "1", currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger - `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, - 0)).thenReturn( + secureSettings.putStringForUser( + ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + - "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}") - updateSetting(fakeFaceAcquiredUri) + "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}", + currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( @@ -236,23 +207,23 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun triggerOnUnlockIntentWhenBiometricEnrolledNone() { - verifyRegisterSettingObserver() - // GIVEN unlock on biometric fail - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, - 0, 0)).thenReturn(1) - updateSetting(fakeBioFailUri) + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // GIVEN fingerprint and face are NOT enrolled activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor - `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false) + `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) // WHEN unlock intent is allowed when NO biometrics are enrolled (0) - `when`(secureSettings.getStringForUser( - Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - 0)).thenReturn("${ActiveUnlockConfig.BiometricType.NONE.intValue}") - updateSetting(fakeUnlockIntentBioEnroll) + + secureSettings.putStringForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser) + updateSetting(secureSettings.getUriFor( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED + )) // THEN active unlock triggers allowed on unlock intent assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -261,12 +232,9 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun triggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() { - verifyRegisterSettingObserver() - // GIVEN unlock on biometric fail - `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, - 0, 0)).thenReturn(1) - updateSetting(fakeBioFailUri) + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) // GIVEN fingerprint and face are both enrolled activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor @@ -275,12 +243,14 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs // are enrolled - `when`(secureSettings.getStringForUser( - Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, - 0)).thenReturn( + secureSettings.putStringForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" + - "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}") - updateSetting(fakeUnlockIntentBioEnroll) + "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}", + currentUser) + updateSetting(secureSettings.getUriFor( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED + )) // THEN active unlock triggers NOT allowed on unlock intent assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -305,13 +275,12 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun isWakeupConsideredUnlockIntent_singleValue() { - verifyRegisterSettingObserver() - // GIVEN lift is considered an unlock intent - `when`(secureSettings.getStringForUser( - Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, - 0)).thenReturn(PowerManager.WAKE_REASON_LIFT.toString()) - updateSetting(fakeWakeupsConsideredUnlockIntents) + secureSettings.putIntForUser( + ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, + PowerManager.WAKE_REASON_LIFT, + currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN only WAKE_REASON_LIFT is considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { @@ -325,17 +294,15 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun isWakeupConsideredUnlockIntent_multiValue() { - verifyRegisterSettingObserver() - // GIVEN lift and tap are considered an unlock intent - `when`(secureSettings.getStringForUser( - Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, - 0)).thenReturn( + secureSettings.putStringForUser( + ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, PowerManager.WAKE_REASON_LIFT.toString() + "|" + - PowerManager.WAKE_REASON_TAP.toString() + PowerManager.WAKE_REASON_TAP.toString(), + currentUser ) - updateSetting(fakeWakeupsConsideredUnlockIntents) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { @@ -354,13 +321,10 @@ class ActiveUnlockConfigTest : SysuiTestCase() { @Test fun isWakeupConsideredUnlockIntent_emptyValues() { - verifyRegisterSettingObserver() - // GIVEN lift and tap are considered an unlock intent - `when`(secureSettings.getStringForUser( - Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, - 0)).thenReturn(" ") - updateSetting(fakeWakeupsConsideredUnlockIntents) + secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ", + currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)) // THEN no wake up gestures are considered an unlock intent for (wakeReason in 0..WAKE_REASON_BIOMETRIC) { @@ -373,7 +337,23 @@ class ActiveUnlockConfigTest : SysuiTestCase() { PowerManager.WAKE_REASON_UNFOLD_DEVICE)) } + @Test + fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() { + // GIVEN an invalid input (-1) + secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + "-1", currentUser) + + // WHEN the setting updates + updateSetting(secureSettings.getUriFor( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED + )) + + // THEN no exception thrown + activeUnlockConfig.dump(mockPrintWriter, emptyArray()) + } + private fun updateSetting(uri: Uri) { + verifyRegisterSettingObserver() settingsObserverCaptor.value.onChange( false, listOf(uri), @@ -383,13 +363,17 @@ class ActiveUnlockConfigTest : SysuiTestCase() { } private fun verifyRegisterSettingObserver() { - verifyRegisterSettingObserver(fakeWakeUri) - verifyRegisterSettingObserver(fakeUnlockIntentUri) - verifyRegisterSettingObserver(fakeBioFailUri) - verifyRegisterSettingObserver(fakeFaceErrorsUri) - verifyRegisterSettingObserver(fakeFaceAcquiredUri) - verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll) - verifyRegisterSettingObserver(fakeWakeupsConsideredUnlockIntents) + verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)) + verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) + verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) + verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)) + verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) + verifyRegisterSettingObserver(secureSettings.getUriFor( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED + )) + verifyRegisterSettingObserver(secureSettings.getUriFor( + ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS + )) } private fun verifyRegisterSettingObserver(uri: Uri) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt new file mode 100644 index 000000000000..777dd4e0b4a3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt @@ -0,0 +1,107 @@ +/* + * 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.accessibility.fontscaling + +import android.os.Handler +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.widget.ImageView +import android.widget.SeekBar +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView +import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.SystemSettings +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for [FontScalingDialog]. */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class FontScalingDialogTest : SysuiTestCase() { + private lateinit var fontScalingDialog: FontScalingDialog + private lateinit var systemSettings: SystemSettings + private val fontSizeValueArray: Array<String> = + mContext + .getResources() + .getStringArray(com.android.settingslib.R.array.entryvalues_font_size) + + @Before + fun setUp() { + val mainHandler = Handler(TestableLooper.get(this).getLooper()) + systemSettings = FakeSettings() + fontScalingDialog = FontScalingDialog(mContext, systemSettings as FakeSettings) + } + + @Test + fun showTheDialog_seekbarIsShowingCorrectProgress() { + fontScalingDialog.show() + + val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!! + val progress: Int = seekBar.getProgress() + val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f) + + assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat()) + + fontScalingDialog.dismiss() + } + + @Test + fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() { + fontScalingDialog.show() + + val iconEnd: ImageView = fontScalingDialog.findViewById(R.id.icon_end)!! + val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = + fontScalingDialog.findViewById(R.id.font_scaling_slider)!! + val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!! + + seekBarWithIconButtonsView.setProgress(0) + + iconEnd.performClick() + + val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f) + assertThat(seekBar.getProgress()).isEqualTo(1) + assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat()) + + fontScalingDialog.dismiss() + } + + @Test + fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() { + fontScalingDialog.show() + + val iconStart: ImageView = fontScalingDialog.findViewById(R.id.icon_start)!! + val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = + fontScalingDialog.findViewById(R.id.font_scaling_slider)!! + val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!! + + seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1) + + iconStart.performClick() + + val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f) + assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2) + assertThat(currentScale) + .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat()) + + fontScalingDialog.dismiss() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java index 9f4a7c820efc..b3329eb5f5b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java @@ -32,7 +32,9 @@ import androidx.test.filters.SmallTest; import com.android.settingslib.dream.DreamBackend; import com.android.systemui.SysuiTestCase; +import com.android.systemui.condition.SelfExecutingMonitor; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -66,13 +68,16 @@ public class ComplicationTypesUpdaterTest extends SysuiTestCase { private ComplicationTypesUpdater mController; + private Monitor mMonitor; + @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>()); + mMonitor = SelfExecutingMonitor.createInstance(); mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor, - mSecureSettings, mDreamOverlayStateController); + mSecureSettings, mDreamOverlayStateController, mMonitor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java index ec448f94ba83..f6662d05c817 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java @@ -29,7 +29,9 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.condition.SelfExecutingMonitor; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; import org.junit.Before; import org.junit.Test; @@ -69,10 +71,13 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { @Mock private ComplicationLayoutParams mLayoutParams; + private Monitor mMonitor; + @Before public void setup() { MockitoAnnotations.initMocks(this); when(mDreamClockTimeViewHolderProvider.get()).thenReturn(mDreamClockTimeViewHolder); + mMonitor = SelfExecutingMonitor.createInstance(); } /** @@ -83,7 +88,8 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase { final DreamClockTimeComplication.Registrant registrant = new DreamClockTimeComplication.Registrant( mDreamOverlayStateController, - mComplication); + mComplication, + mMonitor); registrant.start(); verify(mDreamOverlayStateController).addComplication(eq(mComplication)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index 0e249b347c95..3312c4335ab4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -38,6 +38,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.view.LaunchableImageView; +import com.android.systemui.condition.SelfExecutingMonitor; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.controller.ControlsController; import com.android.systemui.controls.controller.StructureInfo; @@ -46,6 +47,7 @@ import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.shared.condition.Monitor; import org.junit.Before; import org.junit.Test; @@ -101,6 +103,8 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Captor private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor; + private Monitor mMonitor; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -112,6 +116,8 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { Optional.of(mControlsListingController)); when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE); when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView); + + mMonitor = SelfExecutingMonitor.createInstance(); } @Test @@ -126,7 +132,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(false); @@ -139,7 +145,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(false); @@ -152,7 +158,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(false); @@ -165,7 +171,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(true); @@ -178,7 +184,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(true); @@ -191,7 +197,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setServiceAvailable(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java index c8b2b2556828..ef62abfe36de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java @@ -30,9 +30,12 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.condition.SelfExecutingMonitor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.smartspace.DreamSmartspaceController; import com.android.systemui.plugins.BcSmartspaceDataPlugin; +import com.android.systemui.shared.condition.Condition; +import com.android.systemui.shared.condition.Monitor; import org.junit.Before; import org.junit.Test; @@ -43,6 +46,8 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -60,9 +65,14 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { @Mock private View mBcSmartspaceView; + private Monitor mMonitor; + + private final Set<Condition> mPreconditions = new HashSet<>(); + @Before public void setup() { MockitoAnnotations.initMocks(this); + mMonitor = SelfExecutingMonitor.createInstance(); } /** @@ -79,7 +89,8 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { return new SmartSpaceComplication.Registrant( mDreamOverlayStateController, mComplication, - mSmartspaceController); + mSmartspaceController, + mMonitor); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt index db18ba61c578..5bb8367432fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt @@ -18,15 +18,19 @@ package com.android.systemui.keyguard.data.quickaffordance import android.app.StatusBarManager +import android.app.admin.DevicePolicyManager import android.content.Context import android.content.pm.PackageManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.camera.CameraGestureHelper +import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before @@ -44,21 +48,28 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var cameraGestureHelper: CameraGestureHelper @Mock private lateinit var context: Context @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: CameraQuickAffordanceConfig + private lateinit var testScope: TestScope @Before fun setUp() { MockitoAnnotations.initMocks(this) - setLaunchable(true) + setLaunchable() + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) underTest = CameraQuickAffordanceConfig( context, packageManager, - ) { - cameraGestureHelper - } + { cameraGestureHelper }, + userTracker, + devicePolicyManager, + testDispatcher, + ) } @Test @@ -73,23 +84,57 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun `getPickerScreenState - default when launchable`() = runTest { - setLaunchable(true) + fun `getPickerScreenState - default when launchable`() = + testScope.runTest { + setLaunchable(true) - Truth.assertThat(underTest.getPickerScreenState()) - .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) - } + Truth.assertThat(underTest.getPickerScreenState()) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) + } @Test - fun `getPickerScreenState - unavailable when not launchable`() = runTest { - setLaunchable(false) + fun `getPickerScreenState - unavailable when camera app not installed`() = + testScope.runTest { + setLaunchable(isCameraAppInstalled = false) - Truth.assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) - } + Truth.assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } + + @Test + fun `getPickerScreenState - unavailable when camera disabled by admin`() = + testScope.runTest { + setLaunchable(isCameraDisabledByDeviceAdmin = true) + + Truth.assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } + + @Test + fun `getPickerScreenState - unavailable when secure camera disabled by admin`() = + testScope.runTest { + setLaunchable(isSecureCameraDisabledByDeviceAdmin = true) + + Truth.assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } - private fun setLaunchable(isLaunchable: Boolean) { + private fun setLaunchable( + isCameraAppInstalled: Boolean = true, + isCameraDisabledByDeviceAdmin: Boolean = false, + isSecureCameraDisabledByDeviceAdmin: Boolean = false, + ) { whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) - .thenReturn(isLaunchable) + .thenReturn(isCameraAppInstalled) + whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId)) + .thenReturn(isCameraDisabledByDeviceAdmin) + whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) + .thenReturn( + if (isSecureCameraDisabledByDeviceAdmin) { + DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA + } else { + 0 + } + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt index 5bd86bd0f49b..f1b9c5f0fff8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.app.admin.DevicePolicyManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.ActivityIntentHelper @@ -24,11 +25,14 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.camera.CameraIntentsWrapper import com.android.systemui.coroutines.collectLastValue import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any 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.runTest import org.junit.Before import org.junit.Test @@ -44,59 +48,94 @@ import org.mockito.MockitoAnnotations class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var activityIntentHelper: ActivityIntentHelper + @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: VideoCameraQuickAffordanceConfig + private lateinit var userTracker: UserTracker + private lateinit var testScope: TestScope @Before fun setUp() { MockitoAnnotations.initMocks(this) + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + userTracker = FakeUserTracker() underTest = VideoCameraQuickAffordanceConfig( context = context, cameraIntents = CameraIntentsWrapper(context), activityIntentHelper = activityIntentHelper, - userTracker = FakeUserTracker(), + userTracker = userTracker, + devicePolicyManager = devicePolicyManager, + backgroundDispatcher = testDispatcher, ) } @Test - fun `lockScreenState - visible when launchable`() = runTest { - setLaunchable(true) + fun `lockScreenState - visible when launchable`() = + testScope.runTest { + setLaunchable() - val lockScreenState = collectLastValue(underTest.lockScreenState) + val lockScreenState = collectLastValue(underTest.lockScreenState) - assertThat(lockScreenState()) - .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java) - } + assertThat(lockScreenState()) + .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java) + } @Test - fun `lockScreenState - hidden when not launchable`() = runTest { - setLaunchable(false) + fun `lockScreenState - hidden when app not installed on device`() = + testScope.runTest { + setLaunchable(isVideoCameraAppInstalled = false) - val lockScreenState = collectLastValue(underTest.lockScreenState) + val lockScreenState = collectLastValue(underTest.lockScreenState) - assertThat(lockScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } + assertThat(lockScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } @Test - fun `getPickerScreenState - default when launchable`() = runTest { - setLaunchable(true) + fun `lockScreenState - hidden when camera disabled by admin`() = + testScope.runTest { + setLaunchable(isCameraDisabledByAdmin = true) - assertThat(underTest.getPickerScreenState()) - .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) - } + val lockScreenState = collectLastValue(underTest.lockScreenState) + + assertThat(lockScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } @Test - fun `getPickerScreenState - unavailable when not launchable`() = runTest { - setLaunchable(false) + fun `getPickerScreenState - default when launchable`() = + testScope.runTest { + setLaunchable() - assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) - } + assertThat(underTest.getPickerScreenState()) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java) + } - private fun setLaunchable(isLaunchable: Boolean) { + @Test + fun `getPickerScreenState - unavailable when app not installed on device`() = + testScope.runTest { + setLaunchable(isVideoCameraAppInstalled = false) + + assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } + + @Test + fun `getPickerScreenState - unavailable when camera disabled by admin`() = + testScope.runTest { + setLaunchable(isCameraDisabledByAdmin = true) + + assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } + + private fun setLaunchable( + isVideoCameraAppInstalled: Boolean = true, + isCameraDisabledByAdmin: Boolean = false, + ) { whenever( activityIntentHelper.getTargetActivityInfo( any(), @@ -105,11 +144,13 @@ class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() { ) ) .thenReturn( - if (isLaunchable) { + if (isVideoCameraAppInstalled) { mock() } else { null } ) + whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId)) + .thenReturn(isCameraDisabledByAdmin) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 9f12329321e1..a07a714ebc77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -1897,6 +1897,20 @@ class MediaDataManagerTest : SysuiTestCase() { .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } + @Test + fun testSessionDestroyed_noNotificationKey_stillRemoved() { + whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + + // When a notiifcation is added and then removed before it is fully processed + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + backgroundExecutor.runAllReady() + mediaDataManager.onNotificationRemoved(KEY) + + // We still make sure to remove it + verify(listener).onMediaDataRemoved(eq(KEY)) + } + /** Helper function to add a media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 997198e116c0..a72634bcb807 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -61,7 +61,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -69,6 +68,8 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.floatThat import org.mockito.Mockito.mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -107,7 +108,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> @Captor lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener> - @Captor lateinit var newConfig: ArgumentCaptor<Configuration> @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener> @Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -150,7 +150,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { MediaPlayerData.clear() } - @Ignore("b/253229241") @Test fun testPlayerOrdering() { // Test values: key, data, last active time @@ -327,7 +326,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { } } - @Ignore("b/253229241") @Test fun testOrderWithSmartspace_prioritized() { testPlayerOrdering() @@ -335,7 +333,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { // If smartspace is prioritized MediaPlayerData.addMediaRecommendation( SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA, + EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), panel, true, clock @@ -345,7 +343,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) } - @Ignore("b/253229241") @Test fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() { testPlayerOrdering() @@ -362,7 +359,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec) } - @Ignore("b/253229241") @Test fun testOrderWithSmartspace_notPrioritized() { testPlayerOrdering() @@ -370,7 +366,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { // If smartspace is not prioritized MediaPlayerData.addMediaRecommendation( SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA, + EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), panel, false, clock @@ -381,7 +377,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec) } - @Ignore("b/253229241") @Test fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() { testPlayerOrdering() @@ -419,7 +414,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } - @Ignore("b/253229241") @Test fun testSwipeDismiss_logged() { mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke() @@ -427,7 +421,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logSwipeDismiss() } - @Ignore("b/253229241") @Test fun testSettingsButton_logged() { mediaCarouselController.settingsButton.callOnClick() @@ -435,18 +428,16 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselSettings() } - @Ignore("b/253229241") @Test fun testLocationChangeQs_logged() { mediaCarouselController.onDesiredLocationChanged( - MediaHierarchyManager.LOCATION_QS, + LOCATION_QS, mediaHostState, animate = false ) - verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS) + verify(logger).logCarouselPosition(LOCATION_QS) } - @Ignore("b/253229241") @Test fun testLocationChangeQqs_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -457,7 +448,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS) } - @Ignore("b/253229241") @Test fun testLocationChangeLockscreen_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -468,7 +458,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN) } - @Ignore("b/253229241") @Test fun testLocationChangeDream_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -479,7 +468,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY) } - @Ignore("b/253229241") @Test fun testRecommendationRemoved_logged() { val packageName = "smartspace package" @@ -493,7 +481,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) } - @Ignore("b/253229241") @Test fun testMediaLoaded_ScrollToActivePlayer() { listener.value.onMediaDataLoaded( @@ -551,7 +538,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } - @Ignore("b/253229241") @Test fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { listener.value.onSmartspaceMediaDataLoaded( @@ -595,7 +581,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(playerIndex, 0) } - @Ignore("b/253229241") @Test fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() { var result = false @@ -607,7 +592,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(true, result) } - @Ignore("b/253229241") @Test fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() { var result = false @@ -621,7 +605,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(true, result) } - @Ignore("b/253229241") @Test fun testGetCurrentVisibleMediaContentIntent() { val clickIntent1 = mock(PendingIntent::class.java) @@ -668,7 +651,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) } - @Ignore("b/253229241") @Test fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { val delta = 0.0001F @@ -690,7 +672,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta } } - @Ignore("b/253229241") @Test fun testOnConfigChanged_playersAreAddedBack() { listener.value.onMediaDataLoaded( @@ -716,7 +697,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { val playersSize = MediaPlayerData.players().size - configListener.value.onConfigChanged(capture(newConfig)) + configListener.value.onConfigChanged(Configuration()) assertEquals(playersSize, MediaPlayerData.players().size) assertEquals( @@ -796,4 +777,59 @@ class MediaCarouselControllerTest : SysuiTestCase() { job.cancel() } + + @Test + fun testInvisibleToUserAndExpanded_playersNotListening() { + // Add players to carousel. + testPlayerOrdering() + + // Make the carousel visible to user in expanded layout. + mediaCarouselController.currentlyExpanded = true + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true + + // panel is the player for each MediaPlayerData. + // Verify that seekbar listening attribute in media control panel is set to true. + verify(panel, times(MediaPlayerData.players().size)).listening = true + + // Make the carousel invisible to user. + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = false + + // panel is the player for each MediaPlayerData. + // Verify that seekbar listening attribute in media control panel is set to false. + verify(panel, times(MediaPlayerData.players().size)).listening = false + } + + @Test + fun testVisibleToUserAndExpanded_playersListening() { + // Add players to carousel. + testPlayerOrdering() + + // Make the carousel visible to user in expanded layout. + mediaCarouselController.currentlyExpanded = true + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true + + // panel is the player for each MediaPlayerData. + // Verify that seekbar listening attribute in media control panel is set to true. + verify(panel, times(MediaPlayerData.players().size)).listening = true + } + + @Test + fun testUMOCollapsed_playersNotListening() { + // Add players to carousel. + testPlayerOrdering() + + // Make the carousel in collapsed layout. + mediaCarouselController.currentlyExpanded = false + + // panel is the player for each MediaPlayerData. + // Verify that seekbar listening attribute in media control panel is set to false. + verify(panel, times(MediaPlayerData.players().size)).listening = false + + // Make the carousel visible to user. + reset(panel) + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true + + // Verify that seekbar listening attribute in media control panel is set to false. + verify(panel, times(MediaPlayerData.players().size)).listening = false + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index 4635b4843a91..e222542e5c53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -52,14 +52,15 @@ import com.android.systemui.qs.tiles.UiModeNightTile import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.util.leak.GarbageMonitor import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.Mock import org.mockito.Mockito.inOrder -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations private val specMap = mapOf( "internet" to InternetTile::class.java, @@ -140,40 +141,43 @@ class QSFactoryImplTest : SysuiTestCase() { whenever(qsHost.userContext).thenReturn(mContext) whenever(customTileBuilder.build()).thenReturn(customTile) + val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>( + "internet" to Provider { internetTile }, + "bt" to Provider { bluetoothTile }, + "dnd" to Provider { dndTile }, + "inversion" to Provider { colorInversionTile }, + "airplane" to Provider { airplaneTile }, + "work" to Provider { workTile }, + "rotation" to Provider { rotationTile }, + "flashlight" to Provider { flashlightTile }, + "location" to Provider { locationTile }, + "cast" to Provider { castTile }, + "hotspot" to Provider { hotspotTile }, + "battery" to Provider { batterySaverTile }, + "saver" to Provider { dataSaverTile }, + "night" to Provider { nightDisplayTile }, + "nfc" to Provider { nfcTile }, + "dark" to Provider { darkModeTile }, + "screenrecord" to Provider { screenRecordTile }, + "reduce_brightness" to Provider { reduceBrightColorsTile }, + "cameratoggle" to Provider { cameraToggleTile }, + "mictoggle" to Provider { microphoneToggleTile }, + "controls" to Provider { deviceControlsTile }, + "alarm" to Provider { alarmTile }, + "wallet" to Provider { quickAccessWalletTile }, + "qr_code_scanner" to Provider { qrCodeScannerTile }, + "onehanded" to Provider { oneHandedModeTile }, + "color_correction" to Provider { colorCorrectionTile }, + "dream" to Provider { dreamTile }, + "font_scaling" to Provider { fontScalingTile } + ) + factory = QSFactoryImpl( { qsHost }, { customTileBuilder }, - { internetTile }, - { bluetoothTile }, - { dndTile }, - { colorInversionTile }, - { airplaneTile }, - { workTile }, - { rotationTile }, - { flashlightTile }, - { locationTile }, - { castTile }, - { hotspotTile }, - { batterySaverTile }, - { dataSaverTile }, - { nightDisplayTile }, - { nfcTile }, - { memoryTile }, - { darkModeTile }, - { screenRecordTile }, - { reduceBrightColorsTile }, - { cameraToggleTile }, - { microphoneToggleTile }, - { deviceControlsTile }, - { alarmTile }, - { quickAccessWalletTile }, - { qrCodeScannerTile }, - { oneHandedModeTile }, - { colorCorrectionTile }, - { dreamTile }, - { fontScalingTile } + tileMap, ) - // When adding/removing tiles, fix also [specMap] + // When adding/removing tiles, fix also [specMap] and [tileMap] } @Test @@ -209,4 +213,4 @@ class QSFactoryImplTest : SysuiTestCase() { inOrder.verify(tile).initialize() inOrder.verify(tile).postStale() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt new file mode 100644 index 000000000000..57abae0889ca --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt @@ -0,0 +1,81 @@ +/* + * 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.dialog + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class FontScalingTileTest : SysuiTestCase() { + @Mock private lateinit var qsHost: QSTileHost + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var qsLogger: QSLogger + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + + private lateinit var testableLooper: TestableLooper + private lateinit var fontScalingTile: FontScalingTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + `when`(qsHost.getContext()).thenReturn(mContext) + fontScalingTile = + FontScalingTile( + qsHost, + testableLooper.looper, + Handler(testableLooper.looper), + FalsingManagerFake(), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + dialogLaunchAnimator, + FakeSettings() + ) + fontScalingTile.initialize() + } + + @Test + fun isNotAvailable_whenNotSupportedDevice_returnsFalse() { + val isAvailable = fontScalingTile.isAvailable() + + assertThat(isAvailable).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java index 9eccbb6303ab..aa1636d8a030 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java @@ -249,6 +249,21 @@ public class ConditionMonitorTest extends SysuiTestCase { } @Test + public void addCallback_preCondition_noConditions_reportAllConditionsMet() { + final Monitor + monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1))); + final Monitor.Callback callback = mock( + Monitor.Callback.class); + + monitor.addSubscription(new Monitor.Subscription.Builder(callback).build()); + mExecutor.runAllReady(); + verify(callback, never()).onConditionsChanged(true); + mCondition1.fakeUpdateCondition(true); + mExecutor.runAllReady(); + verify(callback).onConditionsChanged(true); + } + + @Test public void removeCallback_noFailureOnDoubleRemove() { final Condition condition = mock( Condition.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index abb45619e1dd..f0f213bc0d58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -26,8 +26,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectio import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi 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 c02ca01cedc4..cd4d8472763f 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 @@ -32,8 +32,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock 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 673e5599fce7..8090205a0c1f 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 @@ -44,8 +44,8 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConn import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq @@ -896,21 +896,31 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // Subscription 1 private const val SUB_1_ID = 1 + private val GROUP_1 = ParcelUuid(UUID.randomUUID()) private val SUB_1 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) - whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID())) + whenever(it.groupUuid).thenReturn(GROUP_1) } - private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID) + private val MODEL_1 = + SubscriptionModel( + subscriptionId = SUB_1_ID, + groupUuid = GROUP_1, + ) // Subscription 2 private const val SUB_2_ID = 2 + private val GROUP_2 = ParcelUuid(UUID.randomUUID()) private val SUB_2 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) - whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID())) + whenever(it.groupUuid).thenReturn(GROUP_2) } - private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID) + private val MODEL_2 = + SubscriptionModel( + subscriptionId = SUB_2_ID, + groupUuid = GROUP_2, + ) // Subs 3 and 4 are considered to be in the same group ------------------------------------ private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID()) 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 f8a978300dd3..bbca0011483f 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 @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor +import android.os.ParcelUuid import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.settingslib.mobile.MobileMappings @@ -34,6 +35,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.util.UUID import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -104,6 +106,21 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2 + @Test + fun filteredSubscriptions_moreThanTwo_doesNotFilter() = + testScope.runTest { + connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) + + job.cancel() + } + @Test fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = testScope.runTest { @@ -118,10 +135,50 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test - fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() = + fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() = testScope.runTest { connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP)) + + job.cancel() + } + + @Test + fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() = + testScope.runTest { + val (sub1, sub2) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_2_ID), + opportunistic = Pair(true, true), + grouped = false, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub2)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) + + var latest: List<SubscriptionModel>? = null + val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(listOf(sub1, sub2)) + + job.cancel() + } + + @Test + fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() = + testScope.runTest { + val (sub3, sub4) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub3, sub4)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) @@ -129,15 +186,21 @@ class MobileIconsInteractorTest : SysuiTestCase() { val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) // Filtered subscriptions should show the active one when the config is false - assertThat(latest).isEqualTo(listOf(SUB_3_OPP)) + assertThat(latest).isEqualTo(listOf(sub3)) job.cancel() } @Test - fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() = + fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() = testScope.runTest { - connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) + val (sub3, sub4) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub3, sub4)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) @@ -146,15 +209,21 @@ class MobileIconsInteractorTest : SysuiTestCase() { val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) // Filtered subscriptions should show the active one when the config is false - assertThat(latest).isEqualTo(listOf(SUB_4_OPP)) + assertThat(latest).isEqualTo(listOf(sub4)) job.cancel() } @Test - fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() = + fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() = testScope.runTest { - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(false, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) @@ -164,15 +233,21 @@ class MobileIconsInteractorTest : SysuiTestCase() { // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true - assertThat(latest).isEqualTo(listOf(SUB_1)) + assertThat(latest).isEqualTo(listOf(sub1)) job.cancel() } @Test - fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() = + fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() = testScope.runTest { - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP)) + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(false, true), + grouped = true, + ) + connectionsRepository.setSubscriptions(listOf(sub1, sub3)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) @@ -182,7 +257,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true - assertThat(latest).isEqualTo(listOf(SUB_1)) + assertThat(latest).isEqualTo(listOf(sub1)) job.cancel() } @@ -642,6 +717,33 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + /** + * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions + * flow. + */ + private fun createSubscriptionPair( + subscriptionIds: Pair<Int, Int>, + opportunistic: Pair<Boolean, Boolean> = Pair(false, false), + grouped: Boolean = false, + ): Pair<SubscriptionModel, SubscriptionModel> { + val groupUuid = if (grouped) ParcelUuid(UUID.randomUUID()) else null + val sub1 = + SubscriptionModel( + subscriptionId = subscriptionIds.first, + isOpportunistic = opportunistic.first, + groupUuid = groupUuid, + ) + + val sub2 = + SubscriptionModel( + subscriptionId = subscriptionIds.second, + isOpportunistic = opportunistic.second, + groupUuid = groupUuid, + ) + + return Pair(sub1, sub2) + } + companion object { private val tableLogBuffer = TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock()) @@ -655,11 +757,21 @@ class MobileIconsInteractorTest : SysuiTestCase() { private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer) private const val SUB_3_ID = 3 - private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true) + private val SUB_3_OPP = + SubscriptionModel( + subscriptionId = SUB_3_ID, + isOpportunistic = true, + groupUuid = ParcelUuid(UUID.randomUUID()), + ) private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer) private const val SUB_4_ID = 4 - private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true) + private val SUB_4_OPP = + SubscriptionModel( + subscriptionId = SUB_4_ID, + isOpportunistic = true, + groupUuid = ParcelUuid(UUID.randomUUID()), + ) private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer) } } 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 f5837d698c51..1bf431b4ea13 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 @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt index 3c4e85bd231e..9cf08c03b5d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test 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 7099f1f0af2d..db791bbb1f1f 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 @@ -36,8 +36,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel 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.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -83,13 +83,14 @@ class WifiRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) whenever( - broadcastDispatcher.broadcastFlow( - any(), - nullable(), - anyInt(), - nullable(), + broadcastDispatcher.broadcastFlow( + any(), + nullable(), + anyInt(), + nullable(), + ) ) - ).thenReturn(flowOf(Unit)) + .thenReturn(flowOf(Unit)) executor = FakeExecutor(FakeSystemClock()) scope = CoroutineScope(IMMEDIATE) underTest = createRepo() @@ -101,150 +102,152 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) { - whenever(wifiManager.isWifiEnabled).thenReturn(true) + fun isWifiEnabled_initiallyGetsWifiManagerValue() = + runBlocking(IMMEDIATE) { + whenever(wifiManager.isWifiEnabled).thenReturn(true) - underTest = createRepo() + underTest = createRepo() - assertThat(underTest.isWifiEnabled.value).isTrue() - } + assertThat(underTest.isWifiEnabled.value).isTrue() + } @Test - fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) { - // We need to call launch on the flows so that they start updating - val networkJob = underTest.wifiNetwork.launchIn(this) - val enabledJob = underTest.isWifiEnabled.launchIn(this) + fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = + runBlocking(IMMEDIATE) { + // We need to call launch on the flows so that they start updating + val networkJob = underTest.wifiNetwork.launchIn(this) + val enabledJob = underTest.isWifiEnabled.launchIn(this) - whenever(wifiManager.isWifiEnabled).thenReturn(true) - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) - ) + whenever(wifiManager.isWifiEnabled).thenReturn(true) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - assertThat(underTest.isWifiEnabled.value).isTrue() + assertThat(underTest.isWifiEnabled.value).isTrue() - whenever(wifiManager.isWifiEnabled).thenReturn(false) - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) - ) + whenever(wifiManager.isWifiEnabled).thenReturn(false) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - assertThat(underTest.isWifiEnabled.value).isFalse() + assertThat(underTest.isWifiEnabled.value).isFalse() - networkJob.cancel() - enabledJob.cancel() - } + networkJob.cancel() + enabledJob.cancel() + } @Test - fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) { - // We need to call launch on the flows so that they start updating - val networkJob = underTest.wifiNetwork.launchIn(this) - val enabledJob = underTest.isWifiEnabled.launchIn(this) + fun isWifiEnabled_networkLost_valueUpdated() = + runBlocking(IMMEDIATE) { + // We need to call launch on the flows so that they start updating + val networkJob = underTest.wifiNetwork.launchIn(this) + val enabledJob = underTest.isWifiEnabled.launchIn(this) - whenever(wifiManager.isWifiEnabled).thenReturn(true) - getNetworkCallback().onLost(NETWORK) + whenever(wifiManager.isWifiEnabled).thenReturn(true) + getNetworkCallback().onLost(NETWORK) - assertThat(underTest.isWifiEnabled.value).isTrue() + assertThat(underTest.isWifiEnabled.value).isTrue() - whenever(wifiManager.isWifiEnabled).thenReturn(false) - getNetworkCallback().onLost(NETWORK) + whenever(wifiManager.isWifiEnabled).thenReturn(false) + getNetworkCallback().onLost(NETWORK) - assertThat(underTest.isWifiEnabled.value).isFalse() + assertThat(underTest.isWifiEnabled.value).isFalse() - networkJob.cancel() - enabledJob.cancel() - } + networkJob.cancel() + enabledJob.cancel() + } @Test - fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) { - val intentFlow = MutableSharedFlow<Unit>() - whenever( - broadcastDispatcher.broadcastFlow( - any(), - nullable(), - anyInt(), - nullable(), - ) - ).thenReturn(intentFlow) - underTest = createRepo() + fun isWifiEnabled_intentsReceived_valueUpdated() = + runBlocking(IMMEDIATE) { + val intentFlow = MutableSharedFlow<Unit>() + whenever( + broadcastDispatcher.broadcastFlow( + any(), + nullable(), + anyInt(), + nullable(), + ) + ) + .thenReturn(intentFlow) + underTest = createRepo() - val job = underTest.isWifiEnabled.launchIn(this) + val job = underTest.isWifiEnabled.launchIn(this) - whenever(wifiManager.isWifiEnabled).thenReturn(true) - intentFlow.emit(Unit) + whenever(wifiManager.isWifiEnabled).thenReturn(true) + intentFlow.emit(Unit) - assertThat(underTest.isWifiEnabled.value).isTrue() + assertThat(underTest.isWifiEnabled.value).isTrue() - whenever(wifiManager.isWifiEnabled).thenReturn(false) - intentFlow.emit(Unit) + whenever(wifiManager.isWifiEnabled).thenReturn(false) + intentFlow.emit(Unit) - assertThat(underTest.isWifiEnabled.value).isFalse() + assertThat(underTest.isWifiEnabled.value).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) { - val intentFlow = MutableSharedFlow<Unit>() - whenever( - broadcastDispatcher.broadcastFlow( - any(), - nullable(), - anyInt(), - nullable(), - ) - ).thenReturn(intentFlow) - underTest = createRepo() - - val networkJob = underTest.wifiNetwork.launchIn(this) - val enabledJob = underTest.isWifiEnabled.launchIn(this) - - whenever(wifiManager.isWifiEnabled).thenReturn(false) - intentFlow.emit(Unit) - assertThat(underTest.isWifiEnabled.value).isFalse() - - whenever(wifiManager.isWifiEnabled).thenReturn(true) - getNetworkCallback().onLost(NETWORK) - assertThat(underTest.isWifiEnabled.value).isTrue() - - whenever(wifiManager.isWifiEnabled).thenReturn(false) - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) - ) - assertThat(underTest.isWifiEnabled.value).isFalse() + fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = + runBlocking(IMMEDIATE) { + val intentFlow = MutableSharedFlow<Unit>() + whenever( + broadcastDispatcher.broadcastFlow( + any(), + nullable(), + anyInt(), + nullable(), + ) + ) + .thenReturn(intentFlow) + underTest = createRepo() + + val networkJob = underTest.wifiNetwork.launchIn(this) + val enabledJob = underTest.isWifiEnabled.launchIn(this) + + whenever(wifiManager.isWifiEnabled).thenReturn(false) + intentFlow.emit(Unit) + assertThat(underTest.isWifiEnabled.value).isFalse() + + whenever(wifiManager.isWifiEnabled).thenReturn(true) + getNetworkCallback().onLost(NETWORK) + assertThat(underTest.isWifiEnabled.value).isTrue() + + whenever(wifiManager.isWifiEnabled).thenReturn(false) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat(underTest.isWifiEnabled.value).isFalse() - whenever(wifiManager.isWifiEnabled).thenReturn(true) - intentFlow.emit(Unit) - assertThat(underTest.isWifiEnabled.value).isTrue() + whenever(wifiManager.isWifiEnabled).thenReturn(true) + intentFlow.emit(Unit) + assertThat(underTest.isWifiEnabled.value).isTrue() - networkJob.cancel() - enabledJob.cancel() - } + networkJob.cancel() + enabledJob.cancel() + } @Test - fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) { - val job = underTest.isWifiDefault.launchIn(this) + fun isWifiDefault_initiallyGetsDefault() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) - assertThat(underTest.isWifiDefault.value).isFalse() + assertThat(underTest.isWifiDefault.value).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) { - val job = underTest.isWifiDefault.launchIn(this) + fun isWifiDefault_wifiNetwork_isTrue() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) - } + val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) } - getDefaultNetworkCallback().onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo) - ) + getDefaultNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) - assertThat(underTest.isWifiDefault.value).isTrue() + assertThat(underTest.isWifiDefault.value).isTrue() - job.cancel() - } + job.cancel() + } /** Regression test for b/266628069. */ @Test @@ -252,16 +255,18 @@ class WifiRepositoryImplTest : SysuiTestCase() { runBlocking(IMMEDIATE) { val job = underTest.isWifiDefault.launchIn(this) - val transportInfo = VpnTransportInfo( - /* type= */ 0, - /* sessionId= */ "sessionId", - ) - val networkCapabilities = mock<NetworkCapabilities>().also { - whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true) - whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) - whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) - whenever(it.transportInfo).thenReturn(transportInfo) - } + val transportInfo = + VpnTransportInfo( + /* type= */ 0, + /* sessionId= */ "sessionId", + ) + val networkCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(transportInfo) + } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities) @@ -276,16 +281,18 @@ class WifiRepositoryImplTest : SysuiTestCase() { runBlocking(IMMEDIATE) { val job = underTest.isWifiDefault.launchIn(this) - val transportInfo = VpnTransportInfo( - /* type= */ 0, - /* sessionId= */ "sessionId", - ) - val networkCapabilities = mock<NetworkCapabilities>().also { - whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true) - whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) - whenever(it.transportInfo).thenReturn(transportInfo) - } + val transportInfo = + VpnTransportInfo( + /* type= */ 0, + /* sessionId= */ "sessionId", + ) + val networkCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(transportInfo) + } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities) @@ -295,31 +302,34 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) { - val job = underTest.isWifiDefault.launchIn(this) + fun isWifiDefault_cellularVcnNetwork_isTrue() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) - } + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) - assertThat(underTest.isWifiDefault.value).isTrue() + assertThat(underTest.isWifiDefault.value).isTrue() - job.cancel() - } + job.cancel() + } @Test fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() = runBlocking(IMMEDIATE) { val job = underTest.isWifiDefault.launchIn(this) - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) - } + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) @@ -329,117 +339,115 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) { - val job = underTest.isWifiDefault.launchIn(this) + fun isWifiDefault_cellularNotVcnNetwork_isFalse() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.transportInfo).thenReturn(mock()) - } + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) - assertThat(underTest.isWifiDefault.value).isFalse() + assertThat(underTest.isWifiDefault.value).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) { - val job = underTest.isWifiDefault.launchIn(this) + fun isWifiDefault_wifiNetworkLost_isFalse() = + runBlocking(IMMEDIATE) { + val job = underTest.isWifiDefault.launchIn(this) - // First, add a network - getDefaultNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - assertThat(underTest.isWifiDefault.value).isTrue() + // First, add a network + getDefaultNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat(underTest.isWifiDefault.value).isTrue() - // WHEN the network is lost - getDefaultNetworkCallback().onLost(NETWORK) + // WHEN the network is lost + getDefaultNetworkCallback().onLost(NETWORK) - // THEN we update to false - assertThat(underTest.isWifiDefault.value).isFalse() + // THEN we update to false + assertThat(underTest.isWifiDefault.value).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_initiallyGetsDefault() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT) + assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT) - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) - whenever(this.isPrimary).thenReturn(true) - } - val network = mock<Network>().apply { - whenever(this.getNetId()).thenReturn(NETWORK_ID) - } + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } + val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } - getNetworkCallback().onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) + getNetworkCallback() + .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo)) - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) - assertThat(latestActive.ssid).isEqualTo(SSID) + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - } + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + } - getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) - assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() - job.cancel() - } + job.cancel() + } @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) - } + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } - getNetworkCallback().onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) @@ -450,26 +458,25 @@ class WifiRepositoryImplTest : SysuiTestCase() { fun wifiNetwork_isCarrierMerged_getsCorrectValues() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) val rssi = -57 - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.isPrimary).thenReturn(true) - whenever(this.isCarrierMerged).thenReturn(true) - whenever(this.rssi).thenReturn(rssi) - whenever(this.subscriptionId).thenReturn(567) - } + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.rssi).thenReturn(rssi) + whenever(this.subscriptionId).thenReturn(567) + } whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2) whenever(wifiManager.maxSignalLevel).thenReturn(5) - getNetworkCallback().onCapabilitiesChanged( - NETWORK, - createWifiNetworkCapabilities(wifiInfo), - ) + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged @@ -483,73 +490,71 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun wifiNetwork_notValidated_networkNotValidated() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_notValidated_networkNotValidated() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false) - ) + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false) + ) - assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse() + assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_validated_networkValidated() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_validated_networkValidated() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true) - ) + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true) + ) - assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue() + assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) - whenever(this.isPrimary).thenReturn(false) - } + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } - getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) - assertThat(latest is WifiNetworkModel.Inactive).isTrue() + assertThat(latest is WifiNetworkModel.Inactive).isTrue() - job.cancel() - } + job.cancel() + } /** Regression test for b/266628069. */ @Test fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - val transportInfo = VpnTransportInfo( - /* type= */ 0, - /* sessionId= */ "sessionId", - ) + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val transportInfo = + VpnTransportInfo( + /* type= */ 0, + /* sessionId= */ "sessionId", + ) getNetworkCallback() .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo)) @@ -559,86 +564,82 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) - } + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } - getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) - assertThat(latestActive.ssid).isEqualTo(SSID) + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) - whenever(this.isPrimary).thenReturn(false) - } - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo)) - } + fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(false) + } + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo)) + } - getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) - assertThat(latest is WifiNetworkModel.Inactive).isTrue() + assertThat(latest is WifiNetworkModel.Inactive).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.transportInfo).thenReturn(mock()) - } + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.transportInfo).thenReturn(mock()) + } - getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) + getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) - assertThat(latest is WifiNetworkModel.Inactive).isTrue() + assertThat(latest is WifiNetworkModel.Inactive).isTrue() - job.cancel() - } + job.cancel() + } @Test fun wifiNetwork_cellularAndWifiTransports_usesCellular() = runBlocking(IMMEDIATE) { var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - val capabilities = mock<NetworkCapabilities>().apply { - whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) - whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) - whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) - } + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val capabilities = + mock<NetworkCapabilities>().apply { + whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO)) + } getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities) @@ -651,309 +652,280 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - // Start with the original network - getNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - // WHEN we update to a new primary network - val newNetworkId = 456 - val newNetwork = mock<Network>().apply { - whenever(this.getNetId()).thenReturn(newNetworkId) - } - val newSsid = "CD" - val newWifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(newSsid) - whenever(this.isPrimary).thenReturn(true) - } + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we update to a new primary network + val newNetworkId = 456 + val newNetwork = + mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) } + val newSsid = "CD" + val newWifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } - getNetworkCallback().onCapabilitiesChanged( - newNetwork, createWifiNetworkCapabilities(newWifiInfo) - ) + getNetworkCallback() + .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo)) - // THEN we use the new network - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.networkId).isEqualTo(newNetworkId) - assertThat(latestActive.ssid).isEqualTo(newSsid) + // THEN we use the new network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(newNetworkId) + assertThat(latestActive.ssid).isEqualTo(newSsid) - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - // Start with the original network - getNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - // WHEN we notify of a new but non-primary network - val newNetworkId = 456 - val newNetwork = mock<Network>().apply { - whenever(this.getNetId()).thenReturn(newNetworkId) - } - val newSsid = "EF" - val newWifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(newSsid) - whenever(this.isPrimary).thenReturn(false) - } + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + // WHEN we notify of a new but non-primary network + val newNetworkId = 456 + val newNetwork = + mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) } + val newSsid = "EF" + val newWifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(false) + } - getNetworkCallback().onCapabilitiesChanged( - newNetwork, createWifiNetworkCapabilities(newWifiInfo) - ) + getNetworkCallback() + .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo)) - // THEN we still use the original network - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) - assertThat(latestActive.ssid).isEqualTo(SSID) + // THEN we still use the original network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - val wifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) - whenever(this.isPrimary).thenReturn(true) - } + fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - // Start with the original network - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(wifiInfo, isValidated = true) - ) + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } - // WHEN we keep the same network ID but change the SSID - val newSsid = "CD" - val newWifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(newSsid) - whenever(this.isPrimary).thenReturn(true) - } + // Start with the original network + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo, isValidated = true) + ) + + // WHEN we keep the same network ID but change the SSID + val newSsid = "CD" + val newWifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(newSsid) + whenever(this.isPrimary).thenReturn(true) + } - getNetworkCallback().onCapabilitiesChanged( - NETWORK, createWifiNetworkCapabilities(newWifiInfo, isValidated = false) - ) + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(newWifiInfo, isValidated = false) + ) - // THEN we've updated to the new SSID - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) - assertThat(latestActive.ssid).isEqualTo(newSsid) - assertThat(latestActive.isValidated).isFalse() + // THEN we've updated to the new SSID + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(newSsid) + assertThat(latestActive.isValidated).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand - getNetworkCallback().onLost(NETWORK) + // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand + getNetworkCallback().onLost(NETWORK) - // THEN there's no crash and we still have no network - assertThat(latest is WifiNetworkModel.Inactive).isTrue() + // THEN there's no crash and we still have no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - getNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) - // WHEN we lose our current network - getNetworkCallback().onLost(NETWORK) + // WHEN we lose our current network + getNetworkCallback().onLost(NETWORK) - // THEN we update to no network - assertThat(latest is WifiNetworkModel.Inactive).isTrue() + // THEN we update to no network + assertThat(latest is WifiNetworkModel.Inactive).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - getNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) - // WHEN we lose an unknown network - val unknownNetwork = mock<Network>().apply { - whenever(this.getNetId()).thenReturn(543) - } - getNetworkCallback().onLost(unknownNetwork) + // WHEN we lose an unknown network + val unknownNetwork = mock<Network>().apply { whenever(this.getNetId()).thenReturn(543) } + getNetworkCallback().onLost(unknownNetwork) - // THEN we still have our previous network - assertThat(latest is WifiNetworkModel.Active).isTrue() - val latestActive = latest as WifiNetworkModel.Active - assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) - assertThat(latestActive.ssid).isEqualTo(SSID) + // THEN we still have our previous network + assertThat(latest is WifiNetworkModel.Active).isTrue() + val latestActive = latest as WifiNetworkModel.Active + assertThat(latestActive.networkId).isEqualTo(NETWORK_ID) + assertThat(latestActive.ssid).isEqualTo(SSID) - job.cancel() - } + job.cancel() + } @Test - fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = runBlocking(IMMEDIATE) { - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) + fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = + runBlocking(IMMEDIATE) { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) - getNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID) - // WHEN we update to a new network... - val newNetworkId = 89 - val newNetwork = mock<Network>().apply { - whenever(this.getNetId()).thenReturn(newNetworkId) - } - getNetworkCallback().onCapabilitiesChanged( - newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO) - ) - // ...and lose the old network - getNetworkCallback().onLost(NETWORK) + // WHEN we update to a new network... + val newNetworkId = 89 + val newNetwork = + mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) } + getNetworkCallback() + .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + // ...and lose the old network + getNetworkCallback().onLost(NETWORK) - // THEN we still have the new network - assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId) + // THEN we still have the new network + assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId) - job.cancel() - } + job.cancel() + } /** Regression test for b/244173280. */ @Test - fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = runBlocking(IMMEDIATE) { - var latest1: WifiNetworkModel? = null - val job1 = underTest - .wifiNetwork - .onEach { latest1 = it } - .launchIn(this) - - getNetworkCallback() - .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) - - assertThat(latest1 is WifiNetworkModel.Active).isTrue() - val latest1Active = latest1 as WifiNetworkModel.Active - assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID) - assertThat(latest1Active.ssid).isEqualTo(SSID) - - // WHEN we add a second subscriber after having already emitted a value - var latest2: WifiNetworkModel? = null - val job2 = underTest - .wifiNetwork - .onEach { latest2 = it } - .launchIn(this) - - // THEN the second subscribe receives the already-emitted value - assertThat(latest2 is WifiNetworkModel.Active).isTrue() - val latest2Active = latest2 as WifiNetworkModel.Active - assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID) - assertThat(latest2Active.ssid).isEqualTo(SSID) - - job1.cancel() - job2.cancel() - } + fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = + runBlocking(IMMEDIATE) { + var latest1: WifiNetworkModel? = null + val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this) + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)) + + assertThat(latest1 is WifiNetworkModel.Active).isTrue() + val latest1Active = latest1 as WifiNetworkModel.Active + assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID) + assertThat(latest1Active.ssid).isEqualTo(SSID) + + // WHEN we add a second subscriber after having already emitted a value + var latest2: WifiNetworkModel? = null + val job2 = underTest.wifiNetwork.onEach { latest2 = it }.launchIn(this) + + // THEN the second subscribe receives the already-emitted value + assertThat(latest2 is WifiNetworkModel.Active).isTrue() + val latest2Active = latest2 as WifiNetworkModel.Active + assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID) + assertThat(latest2Active.ssid).isEqualTo(SSID) + + job1.cancel() + job2.cancel() + } @Test - fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) { - var latest: DataActivityModel? = null - val job = underTest - .wifiActivity - .onEach { latest = it } - .launchIn(this) + fun wifiActivity_callbackGivesNone_activityFlowHasNone() = + runBlocking(IMMEDIATE) { + var latest: DataActivityModel? = null + val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) - getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE) + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE) - assertThat(latest).isEqualTo( - DataActivityModel(hasActivityIn = false, hasActivityOut = false) - ) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false)) - job.cancel() - } + job.cancel() + } @Test - fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) { - var latest: DataActivityModel? = null - val job = underTest - .wifiActivity - .onEach { latest = it } - .launchIn(this) + fun wifiActivity_callbackGivesIn_activityFlowHasIn() = + runBlocking(IMMEDIATE) { + var latest: DataActivityModel? = null + val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) - getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN) + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN) - assertThat(latest).isEqualTo( - DataActivityModel(hasActivityIn = true, hasActivityOut = false) - ) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false)) - job.cancel() - } + job.cancel() + } @Test - fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) { - var latest: DataActivityModel? = null - val job = underTest - .wifiActivity - .onEach { latest = it } - .launchIn(this) + fun wifiActivity_callbackGivesOut_activityFlowHasOut() = + runBlocking(IMMEDIATE) { + var latest: DataActivityModel? = null + val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) - getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT) + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT) - assertThat(latest).isEqualTo( - DataActivityModel(hasActivityIn = false, hasActivityOut = true) - ) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true)) - job.cancel() - } + job.cancel() + } @Test - fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) { - var latest: DataActivityModel? = null - val job = underTest - .wifiActivity - .onEach { latest = it } - .launchIn(this) + fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = + runBlocking(IMMEDIATE) { + var latest: DataActivityModel? = null + val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) - getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT) + getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT) - assertThat(latest).isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) + assertThat(latest) + .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true)) - job.cancel() - } + job.cancel() + } private fun createRepo(): WifiRepositoryImpl { return WifiRepositoryImpl( @@ -998,14 +970,13 @@ class WifiRepositoryImplTest : SysuiTestCase() { private companion object { const val NETWORK_ID = 45 - val NETWORK = mock<Network>().apply { - whenever(this.getNetId()).thenReturn(NETWORK_ID) - } + val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) } const val SSID = "AB" - val PRIMARY_WIFI_INFO: WifiInfo = mock<WifiInfo>().apply { - whenever(this.ssid).thenReturn(SSID) - whenever(this.isPrimary).thenReturn(true) - } + val PRIMARY_WIFI_INFO: WifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + whenever(this.isPrimary).thenReturn(true) + } } } 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 089a170aa2be..fc2277b9c803 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 @@ -22,8 +22,8 @@ import com.android.systemui.SysuiTestCase 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.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -57,10 +57,7 @@ class WifiInteractorImplTest : SysuiTestCase() { wifiRepository.setWifiNetwork(WifiNetworkModel.Unavailable) var latest: String? = "default" - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) + val job = underTest.ssid.onEach { latest = it }.launchIn(this) assertThat(latest).isNull() @@ -68,238 +65,223 @@ class WifiInteractorImplTest : SysuiTestCase() { } @Test - fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + fun ssid_inactiveNetwork_outputsNull() = + runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) - var latest: String? = "default" - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) + var latest: String? = "default" + val job = underTest.ssid.onEach { latest = it }.launchIn(this) - assertThat(latest).isNull() + assertThat(latest).isNull() - job.cancel() - } + job.cancel() + } @Test - fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1) - ) + fun ssid_carrierMergedNetwork_outputsNull() = + runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork( + WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1) + ) - var latest: String? = "default" - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) + var latest: String? = "default" + val job = underTest.ssid.onEach { latest = it }.launchIn(this) - assertThat(latest).isNull() + assertThat(latest).isNull() - job.cancel() - } + job.cancel() + } @Test - fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(WifiNetworkModel.Active( - networkId = 1, - level = 1, - isPasspointAccessPoint = true, - passpointProviderFriendlyName = "friendly", - )) - - var latest: String? = null - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) - - assertThat(latest).isEqualTo("friendly") - - job.cancel() - } + fun ssid_isPasspointAccessPoint_outputsPasspointName() = + runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active( + networkId = 1, + level = 1, + isPasspointAccessPoint = true, + passpointProviderFriendlyName = "friendly", + ) + ) + + var latest: String? = null + val job = underTest.ssid.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo("friendly") + + job.cancel() + } @Test - fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(WifiNetworkModel.Active( - networkId = 1, - level = 1, - isOnlineSignUpForPasspointAccessPoint = true, - passpointProviderFriendlyName = "friendly", - )) - - var latest: String? = null - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) - - assertThat(latest).isEqualTo("friendly") - - job.cancel() - } + fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = + runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active( + networkId = 1, + level = 1, + isOnlineSignUpForPasspointAccessPoint = true, + passpointProviderFriendlyName = "friendly", + ) + ) + + var latest: String? = null + val job = underTest.ssid.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo("friendly") + + job.cancel() + } @Test - fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(WifiNetworkModel.Active( - networkId = 1, - level = 1, - ssid = WifiManager.UNKNOWN_SSID, - )) - - var latest: String? = "default" - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) - - assertThat(latest).isNull() - - job.cancel() - } + fun ssid_unknownSsid_outputsNull() = + runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active( + networkId = 1, + level = 1, + ssid = WifiManager.UNKNOWN_SSID, + ) + ) + + var latest: String? = "default" + val job = underTest.ssid.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } @Test - fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) { - wifiRepository.setWifiNetwork(WifiNetworkModel.Active( - networkId = 1, - level = 1, - ssid = "MyAwesomeWifiNetwork", - )) - - var latest: String? = null - val job = underTest - .ssid - .onEach { latest = it } - .launchIn(this) - - assertThat(latest).isEqualTo("MyAwesomeWifiNetwork") - - job.cancel() - } + fun ssid_validSsid_outputsSsid() = + runBlocking(IMMEDIATE) { + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active( + networkId = 1, + level = 1, + ssid = "MyAwesomeWifiNetwork", + ) + ) + + var latest: String? = null + val job = underTest.ssid.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo("MyAwesomeWifiNetwork") + + job.cancel() + } @Test - fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest - .isEnabled - .onEach { latest = it } - .launchIn(this) - - wifiRepository.setIsWifiEnabled(true) - yield() - assertThat(latest).isTrue() - - wifiRepository.setIsWifiEnabled(false) - yield() - assertThat(latest).isFalse() - - wifiRepository.setIsWifiEnabled(true) - yield() - assertThat(latest).isTrue() - - job.cancel() - } + fun isEnabled_matchesRepoIsEnabled() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isEnabled.onEach { latest = it }.launchIn(this) + + wifiRepository.setIsWifiEnabled(true) + yield() + assertThat(latest).isTrue() + + wifiRepository.setIsWifiEnabled(false) + yield() + assertThat(latest).isFalse() + + wifiRepository.setIsWifiEnabled(true) + yield() + assertThat(latest).isTrue() + + job.cancel() + } @Test - fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest - .isDefault - .onEach { latest = it } - .launchIn(this) - - wifiRepository.setIsWifiDefault(true) - yield() - assertThat(latest).isTrue() - - wifiRepository.setIsWifiDefault(false) - yield() - assertThat(latest).isFalse() - - wifiRepository.setIsWifiDefault(true) - yield() - assertThat(latest).isTrue() - - job.cancel() - } + fun isDefault_matchesRepoIsDefault() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefault.onEach { latest = it }.launchIn(this) + + wifiRepository.setIsWifiDefault(true) + yield() + assertThat(latest).isTrue() + + wifiRepository.setIsWifiDefault(false) + yield() + assertThat(latest).isFalse() + + wifiRepository.setIsWifiDefault(true) + yield() + assertThat(latest).isTrue() + + job.cancel() + } @Test - fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) { - val wifiNetwork = WifiNetworkModel.Active( - networkId = 45, - isValidated = true, - level = 3, - ssid = "AB", - passpointProviderFriendlyName = "friendly" - ) - wifiRepository.setWifiNetwork(wifiNetwork) - - var latest: WifiNetworkModel? = null - val job = underTest - .wifiNetwork - .onEach { latest = it } - .launchIn(this) - - assertThat(latest).isEqualTo(wifiNetwork) - - job.cancel() - } + fun wifiNetwork_matchesRepoWifiNetwork() = + runBlocking(IMMEDIATE) { + val wifiNetwork = + WifiNetworkModel.Active( + networkId = 45, + isValidated = true, + level = 3, + ssid = "AB", + passpointProviderFriendlyName = "friendly" + ) + wifiRepository.setWifiNetwork(wifiNetwork) + + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(wifiNetwork) + + job.cancel() + } @Test - fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) { - var latest: DataActivityModel? = null - val job = underTest - .activity - .onEach { latest = it } - .launchIn(this) - - val activity1 = DataActivityModel(hasActivityIn = true, hasActivityOut = true) - wifiRepository.setWifiActivity(activity1) - yield() - assertThat(latest).isEqualTo(activity1) - - val activity2 = DataActivityModel(hasActivityIn = false, hasActivityOut = false) - wifiRepository.setWifiActivity(activity2) - yield() - assertThat(latest).isEqualTo(activity2) - - val activity3 = DataActivityModel(hasActivityIn = true, hasActivityOut = false) - wifiRepository.setWifiActivity(activity3) - yield() - assertThat(latest).isEqualTo(activity3) - - job.cancel() - } + fun activity_matchesRepoWifiActivity() = + runBlocking(IMMEDIATE) { + var latest: DataActivityModel? = null + val job = underTest.activity.onEach { latest = it }.launchIn(this) + + val activity1 = DataActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity1) + yield() + assertThat(latest).isEqualTo(activity1) + + val activity2 = DataActivityModel(hasActivityIn = false, hasActivityOut = false) + wifiRepository.setWifiActivity(activity2) + yield() + assertThat(latest).isEqualTo(activity2) + + val activity3 = DataActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity3) + yield() + assertThat(latest).isEqualTo(activity3) + + job.cancel() + } @Test - fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) { - connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) + fun isForceHidden_repoHasWifiHidden_outputsTrue() = + runBlocking(IMMEDIATE) { + connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) - var latest: Boolean? = null - val job = underTest - .isForceHidden - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() = runBlocking(IMMEDIATE) { - connectivityRepository.setForceHiddenIcons(setOf()) + fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() = + runBlocking(IMMEDIATE) { + connectivityRepository.setForceHiddenIcons(setOf()) - var latest: Boolean? = null - val job = underTest - .isForceHidden - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) - assertThat(latest).isFalse() + assertThat(latest).isFalse() - job.cancel() - } + job.cancel() + } } private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index 824cebdc3c08..ab4e93ceee84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.wifi.data.model +package com.android.systemui.statusbar.pipeline.wifi.shared.model import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableRowLogger -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL import com.google.common.truth.Truth.assertThat import org.junit.Test 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 b8ace2f04a61..60f564e5f2dc 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 @@ -38,11 +38,11 @@ import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneMod import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel 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.WifiConstants +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import com.android.systemui.util.mockito.whenever @@ -62,16 +62,11 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { private lateinit var testableLooper: TestableLooper - @Mock - private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags - @Mock - private lateinit var logger: ConnectivityPipelineLogger - @Mock - private lateinit var tableLogBuffer: TableLogBuffer - @Mock - private lateinit var connectivityConstants: ConnectivityConstants - @Mock - private lateinit var wifiConstants: WifiConstants + @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags + @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock private lateinit var connectivityConstants: ConnectivityConstants + @Mock private lateinit var wifiConstants: WifiConstants private lateinit var airplaneModeRepository: FakeAirplaneModeRepository private lateinit var connectivityRepository: FakeConnectivityRepository private lateinit var wifiRepository: FakeWifiRepository @@ -91,25 +86,28 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { wifiRepository.setIsWifiEnabled(true) interactor = WifiInteractorImpl(connectivityRepository, wifiRepository) scope = CoroutineScope(Dispatchers.Unconfined) - airplaneModeViewModel = AirplaneModeViewModelImpl( - AirplaneModeInteractor( - airplaneModeRepository, - connectivityRepository, - ), - logger, - scope, - ) - viewModel = WifiViewModel( - airplaneModeViewModel, - connectivityConstants, - context, - logger, - tableLogBuffer, - interactor, - scope, - statusBarPipelineFlags, - wifiConstants, - ).home + airplaneModeViewModel = + AirplaneModeViewModelImpl( + AirplaneModeInteractor( + airplaneModeRepository, + connectivityRepository, + ), + logger, + scope, + ) + viewModel = + WifiViewModel( + airplaneModeViewModel, + connectivityConstants, + context, + logger, + tableLogBuffer, + interactor, + scope, + statusBarPipelineFlags, + wifiConstants, + ) + .home } // Note: The following tests are more like integration tests, since they stand up a full 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 b9328377772a..648d7a5f0f55 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 @@ -36,11 +36,11 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository -import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel 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.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.google.common.truth.Truth.assertThat 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 e5cfec9c08c0..45ebb3903332 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 @@ -29,11 +29,11 @@ import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger 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.model.WifiNetworkModel 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.WifiConstants +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -79,14 +79,15 @@ class WifiViewModelTest : SysuiTestCase() { wifiRepository.setIsWifiEnabled(true) interactor = WifiInteractorImpl(connectivityRepository, wifiRepository) scope = CoroutineScope(IMMEDIATE) - airplaneModeViewModel = AirplaneModeViewModelImpl( - AirplaneModeInteractor( - airplaneModeRepository, - connectivityRepository, - ), - logger, - scope, - ) + airplaneModeViewModel = + AirplaneModeViewModelImpl( + AirplaneModeInteractor( + airplaneModeRepository, + connectivityRepository, + ), + logger, + scope, + ) createAndSetViewModel() } @@ -104,451 +105,386 @@ class WifiViewModelTest : SysuiTestCase() { // instances. There are also some tests that verify all 3 instances received the same data. @Test - fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) { - var latestHome: WifiIcon? = null - val jobHome = underTest - .home - .wifiIcon - .onEach { latestHome = it } - .launchIn(this) - - var latestKeyguard: WifiIcon? = null - val jobKeyguard = underTest - .keyguard - .wifiIcon - .onEach { latestKeyguard = it } - .launchIn(this) - - var latestQs: WifiIcon? = null - val jobQs = underTest - .qs - .wifiIcon - .onEach { latestQs = it } - .launchIn(this) - - wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( - NETWORK_ID, - isValidated = true, - level = 1 + fun wifiIcon_allLocationViewModelsReceiveSameData() = + runBlocking(IMMEDIATE) { + var latestHome: WifiIcon? = null + val jobHome = underTest.home.wifiIcon.onEach { latestHome = it }.launchIn(this) + + var latestKeyguard: WifiIcon? = null + val jobKeyguard = + underTest.keyguard.wifiIcon.onEach { latestKeyguard = it }.launchIn(this) + + var latestQs: WifiIcon? = null + val jobQs = underTest.qs.wifiIcon.onEach { latestQs = it }.launchIn(this) + + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1) ) - ) - yield() + yield() - assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java) - assertThat(latestHome).isEqualTo(latestKeyguard) - assertThat(latestKeyguard).isEqualTo(latestQs) + assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java) + assertThat(latestHome).isEqualTo(latestKeyguard) + assertThat(latestKeyguard).isEqualTo(latestQs) - jobHome.cancel() - jobKeyguard.cancel() - jobQs.cancel() - } + jobHome.cancel() + jobKeyguard.cancel() + jobQs.cancel() + } @Test - fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - - var activityIn: Boolean? = null - val activityInJob = underTest - .home - .isActivityInViewVisible - .onEach { activityIn = it } - .launchIn(this) - - var activityOut: Boolean? = null - val activityOutJob = underTest - .home - .isActivityOutViewVisible - .onEach { activityOut = it } - .launchIn(this) - - var activityContainer: Boolean? = null - val activityContainerJob = underTest - .home - .isActivityContainerVisible - .onEach { activityContainer = it } - .launchIn(this) - - // Verify that on launch, we receive false. - assertThat(activityIn).isFalse() - assertThat(activityOut).isFalse() - assertThat(activityContainer).isFalse() - - activityInJob.cancel() - activityOutJob.cancel() - activityContainerJob.cancel() - } + fun activity_showActivityConfigFalse_outputsFalse() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var activityIn: Boolean? = null + val activityInJob = + underTest.home.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this) + + var activityOut: Boolean? = null + val activityOutJob = + underTest.home.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this) + + var activityContainer: Boolean? = null + val activityContainerJob = + underTest.home.isActivityContainerVisible + .onEach { activityContainer = it } + .launchIn(this) + + // Verify that on launch, we receive false. + assertThat(activityIn).isFalse() + assertThat(activityOut).isFalse() + assertThat(activityContainer).isFalse() + + activityInJob.cancel() + activityOutJob.cancel() + activityContainerJob.cancel() + } @Test - fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - - var activityIn: Boolean? = null - val activityInJob = underTest - .home - .isActivityInViewVisible - .onEach { activityIn = it } - .launchIn(this) - - var activityOut: Boolean? = null - val activityOutJob = underTest - .home - .isActivityOutViewVisible - .onEach { activityOut = it } - .launchIn(this) - - var activityContainer: Boolean? = null - val activityContainerJob = underTest - .home - .isActivityContainerVisible - .onEach { activityContainer = it } - .launchIn(this) - - // WHEN we update the repo to have activity - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() - - // THEN we didn't update to the new activity (because our config is false) - assertThat(activityIn).isFalse() - assertThat(activityOut).isFalse() - assertThat(activityContainer).isFalse() - - activityInJob.cancel() - activityOutJob.cancel() - activityContainerJob.cancel() - } + fun activity_showActivityConfigFalse_noUpdatesReceived() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var activityIn: Boolean? = null + val activityInJob = + underTest.home.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this) + + var activityOut: Boolean? = null + val activityOutJob = + underTest.home.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this) + + var activityContainer: Boolean? = null + val activityContainerJob = + underTest.home.isActivityContainerVisible + .onEach { activityContainer = it } + .launchIn(this) + + // WHEN we update the repo to have activity + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + // THEN we didn't update to the new activity (because our config is false) + assertThat(activityIn).isFalse() + assertThat(activityOut).isFalse() + assertThat(activityContainer).isFalse() + + activityInJob.cancel() + activityOutJob.cancel() + activityContainerJob.cancel() + } @Test - fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() + fun activity_nullSsid_outputsFalse() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1)) - - var activityIn: Boolean? = null - val activityInJob = underTest - .home - .isActivityInViewVisible - .onEach { activityIn = it } - .launchIn(this) - - var activityOut: Boolean? = null - val activityOutJob = underTest - .home - .isActivityOutViewVisible - .onEach { activityOut = it } - .launchIn(this) - - var activityContainer: Boolean? = null - val activityContainerJob = underTest - .home - .isActivityContainerVisible - .onEach { activityContainer = it } - .launchIn(this) - - // WHEN we update the repo to have activity - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() - - // THEN we still output false because our network's SSID is null - assertThat(activityIn).isFalse() - assertThat(activityOut).isFalse() - assertThat(activityContainer).isFalse() - - activityInJob.cancel() - activityOutJob.cancel() - activityContainerJob.cancel() - } + wifiRepository.setWifiNetwork( + WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1) + ) + + var activityIn: Boolean? = null + val activityInJob = + underTest.home.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this) + + var activityOut: Boolean? = null + val activityOutJob = + underTest.home.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this) + + var activityContainer: Boolean? = null + val activityContainerJob = + underTest.home.isActivityContainerVisible + .onEach { activityContainer = it } + .launchIn(this) + + // WHEN we update the repo to have activity + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + // THEN we still output false because our network's SSID is null + assertThat(activityIn).isFalse() + assertThat(activityOut).isFalse() + assertThat(activityContainer).isFalse() + + activityInJob.cancel() + activityOutJob.cancel() + activityContainerJob.cancel() + } @Test - fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - - var latestHome: Boolean? = null - val jobHome = underTest - .home - .isActivityInViewVisible - .onEach { latestHome = it } - .launchIn(this) - - var latestKeyguard: Boolean? = null - val jobKeyguard = underTest - .keyguard - .isActivityInViewVisible - .onEach { latestKeyguard = it } - .launchIn(this) - - var latestQs: Boolean? = null - val jobQs = underTest - .qs - .isActivityInViewVisible - .onEach { latestQs = it } - .launchIn(this) - - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() - - assertThat(latestHome).isTrue() - assertThat(latestKeyguard).isTrue() - assertThat(latestQs).isTrue() - - jobHome.cancel() - jobKeyguard.cancel() - jobQs.cancel() - } + fun activity_allLocationViewModelsReceiveSameData() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + + var latestHome: Boolean? = null + val jobHome = + underTest.home.isActivityInViewVisible.onEach { latestHome = it }.launchIn(this) + + var latestKeyguard: Boolean? = null + val jobKeyguard = + underTest.keyguard.isActivityInViewVisible + .onEach { latestKeyguard = it } + .launchIn(this) + + var latestQs: Boolean? = null + val jobQs = underTest.qs.isActivityInViewVisible.onEach { latestQs = it }.launchIn(this) + + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() + + assertThat(latestHome).isTrue() + assertThat(latestKeyguard).isTrue() + assertThat(latestQs).isTrue() + + jobHome.cancel() + jobKeyguard.cancel() + jobQs.cancel() + } @Test - fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityIn_hasActivityInTrue_outputsTrue() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityInViewVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = underTest.home.isActivityInViewVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityIn_hasActivityInFalse_outputsFalse() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityInViewVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = underTest.home.isActivityInViewVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isFalse() + assertThat(latest).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityOut_hasActivityOutTrue_outputsTrue() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityOutViewVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = underTest.home.isActivityOutViewVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityOut_hasActivityOutFalse_outputsFalse() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityOutViewVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = underTest.home.isActivityOutViewVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isFalse() + assertThat(latest).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityContainer_hasActivityInTrue_outputsTrue() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityContainerVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = + underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityContainer_hasActivityOutTrue_outputsTrue() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityContainerVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = + underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityContainer_inAndOutTrue_outputsTrue() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityContainerVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = + underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) { - whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) - createAndSetViewModel() - wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) + fun activityContainer_inAndOutFalse_outputsFalse() = + runBlocking(IMMEDIATE) { + whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK) - var latest: Boolean? = null - val job = underTest - .home - .isActivityContainerVisible - .onEach { latest = it } - .launchIn(this) + var latest: Boolean? = null + val job = + underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this) - val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false) - wifiRepository.setWifiActivity(activity) - yield() + val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false) + wifiRepository.setWifiActivity(activity) + yield() - assertThat(latest).isFalse() + assertThat(latest).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun airplaneSpacer_notAirplaneMode_outputsFalse() = runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest - .qs - .isAirplaneSpacerVisible - .onEach { latest = it } - .launchIn(this) + fun airplaneSpacer_notAirplaneMode_outputsFalse() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.qs.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this) - airplaneModeRepository.setIsAirplaneMode(false) - yield() + airplaneModeRepository.setIsAirplaneMode(false) + yield() - assertThat(latest).isFalse() + assertThat(latest).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun airplaneSpacer_airplaneForceHidden_outputsFalse() = runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest - .qs - .isAirplaneSpacerVisible - .onEach { latest = it } - .launchIn(this) + fun airplaneSpacer_airplaneForceHidden_outputsFalse() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.qs.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this) - airplaneModeRepository.setIsAirplaneMode(true) - connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE)) - yield() + airplaneModeRepository.setIsAirplaneMode(true) + connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE)) + yield() - assertThat(latest).isFalse() + assertThat(latest).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun airplaneSpacer_airplaneIconVisible_outputsTrue() = runBlocking(IMMEDIATE) { - var latest: Boolean? = null - val job = underTest - .qs - .isAirplaneSpacerVisible - .onEach { latest = it } - .launchIn(this) + fun airplaneSpacer_airplaneIconVisible_outputsTrue() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.qs.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this) - airplaneModeRepository.setIsAirplaneMode(true) - yield() + airplaneModeRepository.setIsAirplaneMode(true) + yield() - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + job.cancel() + } private fun createAndSetViewModel() { // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow // creations rely on certain config values that we mock out in individual tests. This method // allows tests to create the view model only after those configs are correctly set up. - underTest = WifiViewModel( - airplaneModeViewModel, - connectivityConstants, - context, - logger, - tableLogBuffer, - interactor, - scope, - statusBarPipelineFlags, - wifiConstants, - ) + underTest = + WifiViewModel( + airplaneModeViewModel, + connectivityConstants, + context, + logger, + tableLogBuffer, + interactor, + scope, + statusBarPipelineFlags, + wifiConstants, + ) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index 8660d097c0df..0257ebd5c6d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -28,7 +28,6 @@ import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.test.filters.SmallTest -import com.android.internal.R.drawable.ic_account_circle import com.android.internal.logging.UiEventLogger import com.android.systemui.GuestResetOrExitSessionReceiver import com.android.systemui.GuestResumeSessionReceiver @@ -87,6 +86,7 @@ class UserInteractorTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var manager: UserManager + @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @Mock private lateinit var activityManager: ActivityManager @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var devicePolicyManager: DevicePolicyManager @@ -145,6 +145,7 @@ class UserInteractorTest : SysuiTestCase() { featureFlags = featureFlags, ), manager = manager, + headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( @@ -848,6 +849,50 @@ class UserInteractorTest : SysuiTestCase() { assertThat(selectedUser()).isNotNull() } + @Test + fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() = + testScope.runTest { + keyguardRepository.setKeyguardShowing(true) + whenever(manager.getUserSwitchability(any())) + .thenReturn(UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED) + val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList() + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) + userRepository.setSettings( + UserSwitcherSettingsModel( + isUserSwitcherEnabled = true, + isAddUsersFromLockscreen = true + ) + ) + + runCurrent() + underTest.userRecords.value + .filter { it.info == null } + .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() } + } + + @Test + fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() = + testScope.runTest { + keyguardRepository.setKeyguardShowing(true) + whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true) + whenever(manager.isUserUnlocked(anyInt())).thenReturn(false) + val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList() + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) + userRepository.setSettings( + UserSwitcherSettingsModel( + isUserSwitcherEnabled = true, + isAddUsersFromLockscreen = true + ) + ) + + runCurrent() + underTest.userRecords.value + .filter { it.info == null } + .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() } + } + private fun assertUsers( models: List<UserModel>?, count: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 8a35cb05038a..2fedb875b3e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor +import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.mockito.mock @@ -71,6 +72,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var activityManager: ActivityManager @Mock private lateinit var manager: UserManager + @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var uiEventLogger: UiEventLogger @@ -252,6 +254,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { ), featureFlags = featureFlags, manager = manager, + headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 1337d1bdb7ca..166b90943847 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor +import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper @@ -72,6 +73,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var activityManager: ActivityManager @Mock private lateinit var manager: UserManager + @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var uiEventLogger: UiEventLogger @@ -154,6 +156,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { ), featureFlags = featureFlags, manager = manager, + headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java index 5ef62c1e7e8d..b367a603ec67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java @@ -60,6 +60,11 @@ public class ConditionalCoreStartableTest extends SysuiTestCase { mCallback = callback; } + public FakeConditionalCoreStartable(Monitor monitor, Callback callback) { + super(monitor); + mCallback = callback; + } + @Override protected void onStart() { mCallback.onStart(); @@ -122,6 +127,31 @@ public class ConditionalCoreStartableTest extends SysuiTestCase { verify(mMonitor).removeSubscription(mSubscriptionToken); } + @Test + public void testOnStartCallbackWithNoConditions() { + final CoreStartable coreStartable = + new FakeConditionalCoreStartable(mMonitor, + mCallback); + + when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken); + coreStartable.start(); + + final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass( + Monitor.Subscription.class); + verify(mMonitor).addSubscription(subscriptionCaptor.capture()); + + final Monitor.Subscription subscription = subscriptionCaptor.getValue(); + + assertThat(subscription.getConditions()).isEmpty(); + + verify(mCallback, never()).onStart(); + + subscription.getCallback().onConditionsChanged(true); + + verify(mCallback).onStart(); + verify(mMonitor).removeSubscription(mSubscriptionToken); + } + /** * Verifies that {@link ConditionalCoreStartable#bootCompleted()} ()} is predicated on diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java new file mode 100644 index 000000000000..7ee05d02b3cb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java @@ -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.condition; + +import androidx.annotation.NonNull; + +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +/** + * {@link SelfExecutingMonitor} creates a monitor that independently executes its logic through + * a {@link FakeExecutor}, which is ran at when a subscription is added and removed. + */ +public class SelfExecutingMonitor extends Monitor { + private final FakeExecutor mExecutor; + + /** + * Default constructor that allows specifying the FakeExecutor to use. + */ + public SelfExecutingMonitor(FakeExecutor executor) { + super(executor); + mExecutor = executor; + } + + @Override + public Subscription.Token addSubscription(@NonNull Subscription subscription) { + final Subscription.Token result = super.addSubscription(subscription); + mExecutor.runAllReady(); + return result; + } + + @Override + public void removeSubscription(@NonNull Subscription.Token token) { + super.removeSubscription(token); + mExecutor.runNextReady(); + } + + /** + * Creates a {@link SelfExecutingMonitor} with a self-managed {@link FakeExecutor}. Use only + * for cases where condition state only will be set at when a subscription is added. + */ + public static SelfExecutingMonitor createInstance() { + final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + final FakeExecutor mExecutor = new FakeExecutor(mFakeSystemClock); + return new SelfExecutingMonitor(mExecutor); + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 0589cfc0967b..cf880eba20f7 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1512,6 +1512,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE: if (msg.arg1 != BluetoothProfile.HEADSET) { synchronized (mDeviceStateLock) { + mBtHelper.onBtProfileDisconnected(msg.arg1); mDeviceInventory.onBtProfileDisconnected(msg.arg1); } } else { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 6cd42f87aede..f95982138564 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -279,7 +279,11 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); - mA2dp.setAvrcpAbsoluteVolume(index); + try { + mA2dp.setAvrcpAbsoluteVolume(index); + } catch (Exception e) { + Log.e(TAG, "Exception while changing abs volume", e); + } } /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec( @@ -287,7 +291,12 @@ public class BtHelper { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } - final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + final BluetoothCodecStatus btCodecStatus = null; + try { + mA2dp.getCodecStatus(device); + } catch (Exception e) { + Log.e(TAG, "Exception while getting status of " + device, e); + } if (btCodecStatus == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } @@ -421,7 +430,11 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex)); - mLeAudio.setVolume(volume); + try { + mLeAudio.setVolume(volume); + } catch (Exception e) { + Log.e(TAG, "Exception while setting LE volume", e); + } } /*package*/ synchronized void setHearingAidVolume(int index, int streamType, @@ -447,7 +460,11 @@ public class BtHelper { AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); } - mHearingAid.setVolume(gainDB); + try { + mHearingAid.setVolume(gainDB); + } catch (Exception e) { + Log.i(TAG, "Exception while setting hearing aid volume", e); + } } /*package*/ synchronized void onBroadcastScoConnectionState(int state) { @@ -487,6 +504,35 @@ public class BtHelper { mBluetoothHeadset = null; } + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") + /*package*/ synchronized void onBtProfileDisconnected(int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dp = null; + break; + case BluetoothProfile.HEARING_AID: + mHearingAid = null; + break; + case BluetoothProfile.LE_AUDIO: + mLeAudio = null; + break; + + case BluetoothProfile.A2DP_SINK: + case BluetoothProfile.LE_AUDIO_BROADCAST: + // shouldn't be received here as profile doesn't involve BtHelper + Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper " + + BluetoothProfile.getProfileName(profile)); + break; + + default: + // Not a valid profile to disconnect + Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " + + BluetoothProfile.getProfileName(profile)); + break; + } + } + + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { onHeadsetProfileConnected((BluetoothHeadset) proxy); @@ -672,7 +718,6 @@ public class BtHelper { public void onServiceConnected(int profile, BluetoothProfile proxy) { switch(profile) { case BluetoothProfile.A2DP: - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: @@ -682,6 +727,10 @@ public class BtHelper { mDeviceBroker.postBtProfileConnected(profile, proxy); break; + case BluetoothProfile.A2DP_SINK: + // no A2DP sink functionality handled by BtHelper + case BluetoothProfile.LE_AUDIO_BROADCAST: + // no broadcast functionality handled by BtHelper default: break; } @@ -690,14 +739,19 @@ public class BtHelper { switch (profile) { case BluetoothProfile.A2DP: - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: - case BluetoothProfile.LE_AUDIO_BROADCAST: + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "BT profile service: disconnecting " + + BluetoothProfile.getProfileName(profile) + " profile")); mDeviceBroker.postBtProfileDisconnected(profile); break; + case BluetoothProfile.A2DP_SINK: + // no A2DP sink functionality handled by BtHelper + case BluetoothProfile.LE_AUDIO_BROADCAST: + // no broadcast functionality handled by BtHelper default: break; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 787bfb00a554..7d390415952e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -228,7 +228,16 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Override public void onError(int errorCode, int vendorCode) { - super.onError(errorCode, vendorCode); + if (getContext().getResources().getBoolean(R.bool.config_powerPressMapping) + && errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR + && vendorCode == getContext().getResources() + .getInteger(R.integer.config_powerPressCode)) { + // Translating vendor code to internal code + super.onError(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED, + 0 /* vendorCode */); + } else { + super.onError(errorCode, vendorCode); + } if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) { BiometricNotificationUtils.showBadCalibrationNotification(getContext()); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 612d90670888..14b19fb9461a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -37,6 +37,7 @@ import android.os.RemoteException; import android.util.Slog; import android.view.accessibility.AccessibilityManager; +import com.android.internal.R; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -143,7 +144,17 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps } }); mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH); - super.onAcquired(acquiredInfo, vendorCode); + + if (getContext().getResources().getBoolean(R.bool.config_powerPressMapping) + && acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR + && vendorCode == getContext().getResources() + .getInteger(R.integer.config_powerPressCode)) { + // Translating vendor code to internal code + super.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED, + 0 /* vendorCode */); + } else { + super.onAcquired(acquiredInfo, vendorCode); + } } @Override @@ -270,8 +281,5 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps } @Override - public void onPowerPressed() { - onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED, - 0 /* vendorCode */); - } + public void onPowerPressed() {} } diff --git a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java index dc5299077cc9..1fb00eff86d7 100644 --- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java @@ -16,6 +16,8 @@ package com.android.server.location.injector; +import static com.android.server.location.LocationManagerService.TAG; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,6 +25,7 @@ import android.content.IntentFilter; import android.os.SystemClock; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.util.Log; import com.android.server.FgThread; @@ -67,8 +70,12 @@ public class SystemEmergencyHelper extends EmergencyHelper { } synchronized (SystemEmergencyHelper.this) { - mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber( - intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)); + try { + mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber( + intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)); + } catch (IllegalStateException e) { + Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e); + } } } }, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL)); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 651bb930c49b..5d1a5810a01e 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3262,8 +3262,7 @@ public final class PowerManagerService extends SystemService } final PowerGroup powerGroup = mPowerGroups.get(groupId); wakefulness = powerGroup.getWakefulnessLocked(); - if ((wakefulness == WAKEFULNESS_DREAMING || wakefulness == WAKEFULNESS_DOZING) && - powerGroup.isSandmanSummonedLocked() && powerGroup.isReadyLocked()) { + if (powerGroup.isSandmanSummonedLocked() && powerGroup.isReadyLocked()) { startDreaming = canDreamLocked(powerGroup) || canDozeLocked(powerGroup); powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ false); } else { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 00e318816a74..db8079a9cd2f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -45,6 +45,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; +import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; @@ -1736,7 +1737,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { ? activityRecord.getOrganizedTaskFragment() : taskFragment.getOrganizedTaskFragment(); if (organizedTf != null && organizedTf.getAnimationParams() - .getAnimationBackgroundColor() != 0) { + .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) { // This window is embedded and has an animation background color set on the // TaskFragment. Pass this color with this window, so the handler can use it as // the animation background color if needed, @@ -1748,10 +1749,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Task parentTask = activityRecord != null ? activityRecord.getTask() : taskFragment.getTask(); - backgroundColor = ColorUtils.setAlphaComponent( - parentTask.getTaskDescription().getBackgroundColor(), 255); + backgroundColor = parentTask.getTaskDescription().getBackgroundColor(); } - change.setBackgroundColor(backgroundColor); + // Set to opaque for animation background to prevent it from exposing the blank + // background or content below. + change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255)); } change.setRotation(info.mRotation, endRotation); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 9a20354bcf81..ce032442e4af 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -33,6 +33,7 @@ import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -3168,7 +3169,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< ? activityRecord.getOrganizedTaskFragment() : taskFragment.getOrganizedTaskFragment(); if (organizedTf != null && organizedTf.getAnimationParams() - .getAnimationBackgroundColor() != 0) { + .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) { // This window is embedded and has an animation background color set on the // TaskFragment. Pass this color with this window, so the handler can use it // as the animation background color if needed, @@ -3181,11 +3182,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final Task parentTask = activityRecord != null ? activityRecord.getTask() : taskFragment.getTask(); - backgroundColorForTransition = ColorUtils.setAlphaComponent( - parentTask.getTaskDescription().getBackgroundColor(), 255); + backgroundColorForTransition = parentTask.getTaskDescription() + .getBackgroundColor(); } } - animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition); + // Set to opaque for animation background to prevent it from exposing the blank + // background or content below. + animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent( + backgroundColorForTransition, 255)); } animationRunnerBuilder.build() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 3c735e335e75..4915c642abd9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -35,6 +37,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.content.ComponentName; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; @@ -54,6 +57,7 @@ import android.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -335,6 +339,21 @@ public class FingerprintAuthenticationClientTest { showHideOverlay(c -> c.onLockoutPermanent()); } + @Test + public void testPowerPressForwardsErrorMessage() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(); + final int testVendorPowerPressCode = 1; + when(mContext.getOrCreateTestableResources().getResources() + .getBoolean(R.bool.config_powerPressMapping)).thenReturn(true); + when(mContext.getOrCreateTestableResources().getResources() + .getInteger(R.integer.config_powerPressCode)).thenReturn(testVendorPowerPressCode); + + client.onError(FINGERPRINT_ERROR_VENDOR, testVendorPowerPressCode); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED), anyInt()); + } + private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block) throws RemoteException { final FingerprintAuthenticationClient client = createClient(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index 837b55397416..7e29a768db4a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -16,7 +16,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; import static com.google.common.truth.Truth.assertThat; @@ -28,10 +28,10 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.PointerContext; @@ -48,6 +48,7 @@ import android.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -66,7 +67,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import java.util.ArrayList; import java.util.function.Consumer; @Presubmit @@ -258,11 +258,16 @@ public class FingerprintEnrollClientTest { @Test public void testPowerPressForwardsAcquireMessage() throws RemoteException { final FingerprintEnrollClient client = createClient(); - client.start(mCallback); - client.onPowerPressed(); + final int testVendorPowerPressCode = 1; + when(mContext.getOrCreateTestableResources().getResources() + .getBoolean(R.bool.config_powerPressMapping)).thenReturn(true); + when(mContext.getOrCreateTestableResources().getResources() + .getInteger(R.integer.config_powerPressCode)).thenReturn(testVendorPowerPressCode); + + client.onAcquired(FINGERPRINT_ACQUIRED_VENDOR, testVendorPowerPressCode); verify(mClientMonitorCallbackConverter).onAcquired(anyInt(), - eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt()); + eq(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt()); } private void showHideOverlay(Consumer<FingerprintEnrollClient> block) |