diff options
54 files changed, 1384 insertions, 1024 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index f09036bc6162..207abb216c37 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -33157,7 +33157,6 @@ package android.os { public static class PerformanceHintManager.Session implements java.io.Closeable { method public void close(); method public void reportActualWorkDuration(long); - method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration); method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean); method public void setThreads(@NonNull int[]); method public void updateTargetWorkDuration(long); @@ -33500,7 +33499,6 @@ package android.os { method public static boolean setCurrentTimeMillis(long); method public static void sleep(long); method public static long uptimeMillis(); - method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos(); } public class TestLooperManager { @@ -33766,22 +33764,6 @@ package android.os { method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes); } - @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable { - ctor public WorkDuration(); - ctor public WorkDuration(long, long, long, long); - method public int describeContents(); - method public long getActualCpuDurationNanos(); - method public long getActualGpuDurationNanos(); - method public long getActualTotalDurationNanos(); - method public long getWorkPeriodStartTimestampNanos(); - method public void setActualCpuDurationNanos(long); - method public void setActualGpuDurationNanos(long); - method public void setActualTotalDurationNanos(long); - method public void setWorkPeriodStartTimestampNanos(long); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR; - } - public class WorkSource implements android.os.Parcelable { ctor public WorkSource(); ctor public WorkSource(android.os.WorkSource); diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl index fe85da26e610..6b43e73d10e7 100644 --- a/core/java/android/os/IHintSession.aidl +++ b/core/java/android/os/IHintSession.aidl @@ -17,8 +17,6 @@ package android.os; -import android.os.WorkDuration; - /** {@hide} */ oneway interface IHintSession { void updateTargetWorkDuration(long targetDurationNanos); @@ -26,5 +24,4 @@ oneway interface IHintSession { void close(); void sendHint(int hint); void setMode(int mode, boolean enabled); - void reportActualWorkDuration2(in WorkDuration[] workDurations); } diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index e0059105c21f..11084b88fad1 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -103,7 +103,7 @@ public final class PerformanceHintManager { * Any call in this class will change its internal data, so you must do your own thread * safety to protect from racing. * - * All timings should be in {@link SystemClock#uptimeNanos()}. + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. */ public static class Session implements Closeable { private long mNativeSessionPtr; @@ -269,40 +269,6 @@ public final class PerformanceHintManager { public @Nullable int[] getThreadIds() { return nativeGetThreadIds(mNativeSessionPtr); } - - /** - * Reports the work duration for the last cycle of work. - * - * The system will attempt to adjust the core placement of the threads within the thread - * group and/or the frequency of the core on which they are run to bring the actual duration - * close to the target duration. - * - * @param workDuration the work duration of each component. - * @throws IllegalArgumentException if work period start timestamp is not positive, or - * actual total duration is not positive, or actual CPU duration is not positive, - * or actual GPU duration is negative. - */ - @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) - public void reportActualWorkDuration(@NonNull WorkDuration workDuration) { - if (workDuration.mWorkPeriodStartTimestampNanos <= 0) { - throw new IllegalArgumentException( - "the work period start timestamp should be positive."); - } - if (workDuration.mActualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); - } - if (workDuration.mActualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); - } - if (workDuration.mActualGpuDurationNanos < 0) { - throw new IllegalArgumentException( - "the actual GPU duration should be non negative."); - } - nativeReportActualWorkDuration(mNativeSessionPtr, - workDuration.mWorkPeriodStartTimestampNanos, - workDuration.mActualTotalDurationNanos, - workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos); - } } private static native long nativeAcquireManager(); @@ -319,7 +285,4 @@ public final class PerformanceHintManager { private static native void nativeSetThreads(long nativeSessionPtr, int[] tids); private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr, boolean enabled); - private static native void nativeReportActualWorkDuration(long nativeSessionPtr, - long workPeriodStartTimestampNanos, long actualTotalDurationNanos, - long actualCpuDurationNanos, long actualGpuDurationNanos); } diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index e2a58338230c..49a0bd3289aa 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -16,7 +16,6 @@ package android.os; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.IAlarmManager; import android.app.time.UnixEpochTime; @@ -203,8 +202,8 @@ public final class SystemClock { * Returns nanoseconds since boot, not counting time spent in deep sleep. * * @return nanoseconds of non-sleep uptime since boot. + * @hide */ - @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) @CriticalNative @android.ravenwood.annotation.RavenwoodReplace public static native long uptimeNanos(); diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java deleted file mode 100644 index 4fdc34fb60d1..000000000000 --- a/core/java/android/os/WorkDuration.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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 android.os; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; - -import java.util.Objects; - -/** - * WorkDuration contains the measured time in nano seconds of the workload - * in each component, see - * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ -@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) -public final class WorkDuration implements Parcelable { - long mWorkPeriodStartTimestampNanos = 0; - long mActualTotalDurationNanos = 0; - long mActualCpuDurationNanos = 0; - long mActualGpuDurationNanos = 0; - long mTimestampNanos = 0; - - public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() { - @Override - public WorkDuration createFromParcel(Parcel in) { - return new WorkDuration(in); - } - - @Override - public WorkDuration[] newArray(int size) { - return new WorkDuration[size]; - } - }; - - public WorkDuration() {} - - public WorkDuration(long workPeriodStartTimestampNanos, - long actualTotalDurationNanos, - long actualCpuDurationNanos, - long actualGpuDurationNanos) { - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - mActualTotalDurationNanos = actualTotalDurationNanos; - mActualCpuDurationNanos = actualCpuDurationNanos; - mActualGpuDurationNanos = actualGpuDurationNanos; - } - - /** - * @hide - */ - public WorkDuration(long workPeriodStartTimestampNanos, - long actualTotalDurationNanos, - long actualCpuDurationNanos, - long actualGpuDurationNanos, - long timestampNanos) { - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - mActualTotalDurationNanos = actualTotalDurationNanos; - mActualCpuDurationNanos = actualCpuDurationNanos; - mActualGpuDurationNanos = actualGpuDurationNanos; - mTimestampNanos = timestampNanos; - } - - WorkDuration(@NonNull Parcel in) { - mWorkPeriodStartTimestampNanos = in.readLong(); - mActualTotalDurationNanos = in.readLong(); - mActualCpuDurationNanos = in.readLong(); - mActualGpuDurationNanos = in.readLong(); - mTimestampNanos = in.readLong(); - } - - /** - * Sets the work period start timestamp in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) { - if (workPeriodStartTimestampNanos <= 0) { - throw new IllegalArgumentException( - "the work period start timestamp should be positive."); - } - mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; - } - - /** - * Sets the actual total duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualTotalDurationNanos(long actualTotalDurationNanos) { - if (actualTotalDurationNanos <= 0) { - throw new IllegalArgumentException("the actual total duration should be positive."); - } - mActualTotalDurationNanos = actualTotalDurationNanos; - } - - /** - * Sets the actual CPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualCpuDurationNanos(long actualCpuDurationNanos) { - if (actualCpuDurationNanos <= 0) { - throw new IllegalArgumentException("the actual CPU duration should be positive."); - } - mActualCpuDurationNanos = actualCpuDurationNanos; - } - - /** - * Sets the actual GPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public void setActualGpuDurationNanos(long actualGpuDurationNanos) { - if (actualGpuDurationNanos < 0) { - throw new IllegalArgumentException("the actual GPU duration should be non negative."); - } - mActualGpuDurationNanos = actualGpuDurationNanos; - } - - /** - * Returns the work period start timestamp based in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getWorkPeriodStartTimestampNanos() { - return mWorkPeriodStartTimestampNanos; - } - - /** - * Returns the actual total duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualTotalDurationNanos() { - return mActualTotalDurationNanos; - } - - /** - * Returns the actual CPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualCpuDurationNanos() { - return mActualCpuDurationNanos; - } - - /** - * Returns the actual GPU duration in nanoseconds. - * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. - */ - public long getActualGpuDurationNanos() { - return mActualGpuDurationNanos; - } - - /** - * @hide - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeLong(mWorkPeriodStartTimestampNanos); - dest.writeLong(mActualTotalDurationNanos); - dest.writeLong(mActualCpuDurationNanos); - dest.writeLong(mActualGpuDurationNanos); - dest.writeLong(mTimestampNanos); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof WorkDuration)) { - return false; - } - WorkDuration workDuration = (WorkDuration) obj; - return workDuration.mTimestampNanos == this.mTimestampNanos - && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos - && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos - && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos - && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos; - } - - @Override - public int hashCode() { - return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos, - mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos); - } -} diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index d405d1d0cec6..a78f221fc962 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -61,11 +61,4 @@ flag { namespace: "backstage_power" description: "Guards a new API in PowerManager to check if battery saver is supported or not." bug: "305067031" -} - -flag { - name: "adpf_gpu_report_actual_work_duration" - namespace: "game" - description: "Guards the ADPF GPU APIs." - bug: "284324521" -} +}
\ No newline at end of file diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp index aebe7ea7ee61..95bf49fe501e 100644 --- a/core/jni/android_os_PerformanceHintManager.cpp +++ b/core/jni/android_os_PerformanceHintManager.cpp @@ -16,16 +16,15 @@ #define LOG_TAG "PerfHint-jni" -#include <android/performance_hint.h> +#include "jni.h" + #include <dlfcn.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <utils/Log.h> - #include <vector> #include "core_jni_helpers.h" -#include "jni.h" namespace android { @@ -45,11 +44,6 @@ typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t); typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t); typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const); typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool); -typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*); - -typedef AWorkDuration* (*AWD_create)(); -typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t); -typedef void (*AWD_release)(AWorkDuration*); bool gAPerformanceHintBindingInitialized = false; APH_getManager gAPH_getManagerFn = nullptr; @@ -62,14 +56,6 @@ APH_sendHint gAPH_sendHintFn = nullptr; APH_setThreads gAPH_setThreadsFn = nullptr; APH_getThreadIds gAPH_getThreadIdsFn = nullptr; APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr; -APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr; - -AWD_create gAWD_createFn = nullptr; -AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr; -AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr; -AWD_release gAWD_releaseFn = nullptr; void ensureAPerformanceHintBindingInitialized() { if (gAPerformanceHintBindingInitialized) return; @@ -126,46 +112,9 @@ void ensureAPerformanceHintBindingInitialized() { (APH_setPreferPowerEfficiency)dlsym(handle_, "APerformanceHint_setPreferPowerEfficiency"); LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr, - "Failed to find required symbol " + "Failed to find required symbol" "APerformanceHint_setPreferPowerEfficiency!"); - gAPH_reportActualWorkDuration2Fn = - (APH_reportActualWorkDuration2)dlsym(handle_, - "APerformanceHint_reportActualWorkDuration2"); - LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr, - "Failed to find required symbol " - "APerformanceHint_reportActualWorkDuration2!"); - - gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create"); - LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr, - "Failed to find required symbol AWorkDuration_create!"); - - gAWD_setWorkPeriodStartTimestampNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr, - "Failed to find required symbol " - "AWorkDuration_setWorkPeriodStartTimestampNanos!"); - - gAWD_setActualTotalDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr, - "Failed to find required symbol " - "AWorkDuration_setActualTotalDurationNanos!"); - - gAWD_setActualCpuDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr, - "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!"); - - gAWD_setActualGpuDurationNanosFn = - (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos"); - LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr, - "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!"); - - gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release"); - LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr, - "Failed to find required symbol AWorkDuration_release!"); - gAPerformanceHintBindingInitialized = true; } @@ -289,25 +238,6 @@ static void nativeSetPreferPowerEfficiency(JNIEnv* env, jclass clazz, jlong nati enabled); } -static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, - jlong workPeriodStartTimestampNanos, - jlong actualTotalDurationNanos, - jlong actualCpuDurationNanos, - jlong actualGpuDurationNanos) { - ensureAPerformanceHintBindingInitialized(); - - AWorkDuration* workDuration = gAWD_createFn(); - gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos); - gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos); - gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos); - gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos); - - gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), - workDuration); - - gAWD_releaseFn(workDuration); -} - static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeAcquireManager", "()J", (void*)nativeAcquireManager}, {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos}, @@ -319,7 +249,6 @@ static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds}, {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency}, - {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2}, }; int register_android_os_PerformanceHintManager(JNIEnv* env) { diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 9b4dec4118a1..20ba4270e6fc 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -182,42 +182,4 @@ public class PerformanceHintManagerTest { s.setPreferPowerEfficiency(true); s.setPreferPowerEfficiency(true); } - - @Test - public void testReportActualWorkDurationWithWorkDurationClass() { - Session s = createSession(); - assumeNotNull(s); - s.updateTargetWorkDuration(16); - s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6)); - s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20)); - s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6)); - } - - @Test - public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() { - Session s = createSession(); - assumeNotNull(s); - s.updateTargetWorkDuration(16); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6)); - }); - assertThrows(IllegalArgumentException.class, () -> { - s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1)); - }); - } } diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index 2ec4524e1241..d659ddd75f72 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -402,8 +402,8 @@ public class BaseRecordingCanvas extends Canvas { } @Override - public final void drawDoubleRoundRect(@NonNull RectF outer, float[] outerRadii, - @NonNull RectF inner, float[] innerRadii, @NonNull Paint paint) { + public final void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, + @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) { nDrawDoubleRoundRect(mNativeCanvasWrapper, outer.left, outer.top, outer.right, outer.bottom, outerRadii, inner.left, inner.top, inner.right, inner.bottom, innerRadii, diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 9f2a9ac4798d..fea6c5f95358 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -336,13 +336,6 @@ LIBANDROID { APerformanceHint_closeSession; # introduced=Tiramisu APerformanceHint_setThreads; # introduced=UpsideDownCake APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream - APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream - AWorkDuration_create; # introduced=VanillaIceCream - AWorkDuration_release; # introduced=VanillaIceCream - AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream - AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream - AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream - AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream local: *; }; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c4c81284780e..c25df6e08fd0 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -18,14 +18,12 @@ #include <aidl/android/hardware/power/SessionHint.h> #include <aidl/android/hardware/power/SessionMode.h> -#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> #include <binder/Binder.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> -#include <inttypes.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> @@ -77,13 +75,10 @@ public: int setThreads(const int32_t* threadIds, size_t size); int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); - int reportActualWorkDuration(AWorkDuration* workDuration); private: friend struct APerformanceHintManager; - int reportActualWorkDurationInternal(WorkDuration* workDuration); - sp<IHintManager> mHintManager; sp<IHintSession> mHintSession; // HAL preferred update rate @@ -97,7 +92,8 @@ private: // Last hint reported from sendHint indexed by hint value std::vector<int64_t> mLastHintSentTimestamp; // Cached samples - std::vector<WorkDuration> mActualWorkDurations; + std::vector<int64_t> mActualDurationsNanos; + std::vector<int64_t> mTimestampsNanos; }; static IHintManager* gIHintManagerForTesting = nullptr; @@ -199,7 +195,8 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano * Most of the workload is target_duration dependent, so now clear the cached samples * as they are most likely obsolete. */ - mActualWorkDurations.clear(); + mActualDurationsNanos.clear(); + mTimestampsNanos.clear(); mFirstTargetMetTimestamp = 0; mLastTargetMetTimestamp = 0; return 0; @@ -210,10 +207,43 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); return EINVAL; } + int64_t now = elapsedRealtimeNano(); + mActualDurationsNanos.push_back(actualDurationNanos); + mTimestampsNanos.push_back(now); - WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0); + if (actualDurationNanos >= mTargetDurationNanos) { + // Reset timestamps if we are equal or over the target. + mFirstTargetMetTimestamp = 0; + } else { + // Set mFirstTargetMetTimestamp for first time meeting target. + if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || + (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { + mFirstTargetMetTimestamp = now; + } + /** + * Rate limit the change if the update is over mPreferredRateNanos since first + * meeting target and less than mPreferredRateNanos since last meeting target. + */ + if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && + now - mLastTargetMetTimestamp <= mPreferredRateNanos) { + return 0; + } + mLastTargetMetTimestamp = now; + } - return reportActualWorkDurationInternal(&workDuration); + binder::Status ret = + mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos); + if (!ret.isOk()) { + ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, + ret.exceptionMessage().c_str()); + mFirstTargetMetTimestamp = 0; + mLastTargetMetTimestamp = 0; + return EPIPE; + } + mActualDurationsNanos.clear(); + mTimestampsNanos.clear(); + + return 0; } int APerformanceHintSession::sendHint(SessionHint hint) { @@ -292,67 +322,6 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) { return OK; } -int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) { - WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration); - if (workDuration->workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualTotalDurationNanos <= 0) { - ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualCpuDurationNanos <= 0) { - ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualGpuDurationNanos < 0) { - ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__); - return EINVAL; - } - - return reportActualWorkDurationInternal(workDuration); -} - -int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) { - int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos; - int64_t now = uptimeNanos(); - workDuration->timestampNanos = now; - mActualWorkDurations.push_back(std::move(*workDuration)); - - if (actualTotalDurationNanos >= mTargetDurationNanos) { - // Reset timestamps if we are equal or over the target. - mFirstTargetMetTimestamp = 0; - } else { - // Set mFirstTargetMetTimestamp for first time meeting target. - if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || - (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { - mFirstTargetMetTimestamp = now; - } - /** - * Rate limit the change if the update is over mPreferredRateNanos since first - * meeting target and less than mPreferredRateNanos since last meeting target. - */ - if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && - now - mLastTargetMetTimestamp <= mPreferredRateNanos) { - return 0; - } - mLastTargetMetTimestamp = now; - } - - binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations); - if (!ret.isOk()) { - ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, - ret.exceptionMessage().c_str()); - mFirstTargetMetTimestamp = 0; - mLastTargetMetTimestamp = 0; - return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE; - } - mActualWorkDurations.clear(); - - return 0; -} - // ===================================== C API APerformanceHintManager* APerformanceHint_getManager() { return APerformanceHintManager::getInstance(); @@ -407,64 +376,6 @@ int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, return session->setPreferPowerEfficiency(enabled); } -int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, - AWorkDuration* workDuration) { - if (session == nullptr || workDuration == nullptr) { - ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration); - return EINVAL; - } - return session->reportActualWorkDuration(workDuration); -} - -AWorkDuration* AWorkDuration_create() { - WorkDuration* workDuration = new WorkDuration(); - return static_cast<AWorkDuration*>(workDuration); -} - -void AWorkDuration_release(AWorkDuration* aWorkDuration) { - if (aWorkDuration == nullptr) { - ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__); - } - delete aWorkDuration; -} - -void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration, - int64_t workPeriodStartTimestampNanos) { - if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos = - workPeriodStartTimestampNanos; -} - -void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualTotalDurationNanos) { - if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualTotalDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos; -} - -void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualCpuDurationNanos) { - if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualCpuDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos; -} - -void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration, - int64_t actualGpuDurationNanos) { - if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualGpuDurationNanos); - } - static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos; -} - void APerformanceHint_setIHintManagerForTesting(void* iManager) { delete gHintManagerForTesting; gHintManagerForTesting = nullptr; diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 4553b4919d2d..22d33b139ccf 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -16,7 +16,6 @@ #define LOG_TAG "PerformanceHintNativeTest" -#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> @@ -61,8 +60,6 @@ public: MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override)); MOCK_METHOD(Status, close, (), (override)); MOCK_METHOD(IBinder*, onAsBinder, (), (override)); - MOCK_METHOD(Status, reportActualWorkDuration2, - (const ::std::vector<android::os::WorkDuration>& workDurations), (override)); }; class PerformanceHintTest : public Test { @@ -123,7 +120,6 @@ TEST_F(PerformanceHintTest, TestSession) { std::vector<int64_t> actualDurations; actualDurations.push_back(20); EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1)); - EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1)); result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos); EXPECT_EQ(0, result); @@ -242,125 +238,4 @@ TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) { APerformanceHintSession* session = APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); ASSERT_TRUE(session); -} - -MATCHER_P(WorkDurationEq, expected, "") { - if (arg.size() != expected.size()) { - *result_listener << "WorkDuration vectors are different sizes. Expected: " - << expected.size() << ", Actual: " << arg.size(); - return false; - } - for (int i = 0; i < expected.size(); ++i) { - android::os::WorkDuration expectedWorkDuration = expected[i]; - android::os::WorkDuration actualWorkDuration = arg[i]; - if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) { - *result_listener << "WorkDuration at [" << i << "] is different: " - << "Expected: " << expectedWorkDuration - << ", Actual: " << actualWorkDuration; - return false; - } - } - return true; -} - -TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) { - APerformanceHintManager* manager = createManager(); - - std::vector<int32_t> tids; - tids.push_back(1); - tids.push_back(2); - int64_t targetDuration = 56789L; - - StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>(); - sp<IHintSession> session_sp(iSession); - - EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _)) - .Times(Exactly(1)) - .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status()))); - - APerformanceHintSession* session = - APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); - ASSERT_TRUE(session); - - int64_t targetDurationNanos = 10; - EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1)); - int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); - EXPECT_EQ(0, result); - - usleep(2); // Sleep for longer than preferredUpdateRateNanos. - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(0, result); - } - - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(-1, 20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(22, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, -20, 13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(22, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, -13, 8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(EINVAL, result); - } - { - std::vector<android::os::WorkDuration> actualWorkDurations; - android::os::WorkDuration workDuration(1, 20, 13, -8); - actualWorkDurations.push_back(workDuration); - - EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) - .Times(Exactly(1)); - result = APerformanceHint_reportActualWorkDuration2(session, - static_cast<AWorkDuration*>( - &workDuration)); - EXPECT_EQ(EINVAL, result); - } - - EXPECT_CALL(*iSession, close()).Times(Exactly(1)); - APerformanceHint_closeSession(session); -} - -TEST_F(PerformanceHintTest, TestAWorkDuration) { - AWorkDuration* aWorkDuration = AWorkDuration_create(); - ASSERT_NE(aWorkDuration, nullptr); - - AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1); - AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20); - AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13); - AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8); - AWorkDuration_release(aWorkDuration); -} +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 7eb7dacda255..57af2ba59566 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -19,7 +19,6 @@ package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog import android.app.Dialog import android.content.DialogInterface -import android.content.res.Configuration import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap @@ -54,8 +53,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -68,7 +65,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextOverflow @@ -83,8 +79,8 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf -import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -93,8 +89,8 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.fold.ui.composable.FoldPosture import com.android.systemui.fold.ui.composable.foldPosture +import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -149,10 +145,7 @@ private fun SceneScope.BouncerScene( ) { val backgroundColor = MaterialTheme.colorScheme.surface val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() - val layout = - calculateLayout( - isSideBySideSupported = isSideBySideSupported, - ) + val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) Box(modifier) { Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) { @@ -163,27 +156,27 @@ private fun SceneScope.BouncerScene( val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible when (layout) { - Layout.STANDARD -> + BouncerSceneLayout.STANDARD -> StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, modifier = childModifier, ) - Layout.SIDE_BY_SIDE -> + BouncerSceneLayout.SIDE_BY_SIDE -> SideBySideLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) - Layout.STACKED -> + BouncerSceneLayout.STACKED -> StackedLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) - Layout.SPLIT -> + BouncerSceneLayout.SPLIT -> SplitLayout( viewModel = viewModel, dialogFactory = dialogFactory, @@ -728,58 +721,10 @@ private fun StackedLayout( } } -@Composable -private fun calculateLayout( - isSideBySideSupported: Boolean, -): Layout { - val windowSizeClass = LocalWindowSizeClass.current - val width = windowSizeClass.widthSizeClass - val height = windowSizeClass.heightSizeClass - val isLarge = width > WindowWidthSizeClass.Compact && height > WindowHeightSizeClass.Compact - val isTall = - when (height) { - WindowHeightSizeClass.Expanded -> width < WindowWidthSizeClass.Expanded - WindowHeightSizeClass.Medium -> width < WindowWidthSizeClass.Medium - else -> false - } - val isSquare = - when (width) { - WindowWidthSizeClass.Compact -> height == WindowHeightSizeClass.Compact - WindowWidthSizeClass.Medium -> height == WindowHeightSizeClass.Medium - WindowWidthSizeClass.Expanded -> height == WindowHeightSizeClass.Expanded - else -> false - } - val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE - - return when { - // Small and tall devices (i.e. phone/folded in portrait) or square device not in landscape - // mode (unfolded with hinge along horizontal plane). - (!isLarge && isTall) || (isSquare && !isLandscape) -> Layout.STANDARD - // Small and wide devices (i.e. phone/folded in landscape). - !isLarge -> Layout.SPLIT - // Large and tall devices (i.e. tablet in portrait). - isTall -> Layout.STACKED - // Large and wide/square devices (i.e. tablet in landscape, unfolded). - else -> if (isSideBySideSupported) Layout.SIDE_BY_SIDE else Layout.STANDARD - } -} - interface BouncerSceneDialogFactory { operator fun invoke(): AlertDialog } -/** Enumerates all known adaptive layout configurations. */ -private enum class Layout { - /** The default UI with the bouncer laid out normally. */ - STANDARD, - /** The bouncer is displayed vertically stacked with the user switcher. */ - STACKED, - /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ - SIDE_BY_SIDE, - /** The bouncer is split in two with both sides shown side-by-side. */ - SPLIT, -} - /** Enumerates all supported user-input area visibilities. */ private enum class UserInputAreaVisibility { /** diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt new file mode 100644 index 000000000000..08b7559dcf97 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt @@ -0,0 +1,61 @@ +/* + * 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.bouncer.ui.composable + +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout +import com.android.systemui.bouncer.ui.helper.SizeClass +import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal + +/** + * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If + * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by + * [BouncerSceneLayout.STANDARD]. + */ +@Composable +fun calculateLayout( + isSideBySideSupported: Boolean, +): BouncerSceneLayout { + val windowSizeClass = LocalWindowSizeClass.current + + return calculateLayoutInternal( + width = windowSizeClass.widthSizeClass.toEnum(), + height = windowSizeClass.heightSizeClass.toEnum(), + isSideBySideSupported = isSideBySideSupported, + ) +} + +private fun WindowWidthSizeClass.toEnum(): SizeClass { + return when (this) { + WindowWidthSizeClass.Compact -> SizeClass.COMPACT + WindowWidthSizeClass.Medium -> SizeClass.MEDIUM + WindowWidthSizeClass.Expanded -> SizeClass.EXPANDED + else -> error("Unsupported WindowWidthSizeClass \"$this\"") + } +} + +private fun WindowHeightSizeClass.toEnum(): SizeClass { + return when (this) { + WindowHeightSizeClass.Compact -> SizeClass.COMPACT + WindowHeightSizeClass.Medium -> SizeClass.MEDIUM + WindowHeightSizeClass.Expanded -> SizeClass.EXPANDED + else -> error("Unsupported WindowHeightSizeClass \"$this\"") + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt index 1c993cf6206c..e77ade91a93b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -23,19 +23,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker - -sealed interface FoldPosture { - /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ - data object Folded : FoldPosture - /** A foldable that's halfway open with the hinge held vertically. */ - data object Book : FoldPosture - /** A foldable that's halfway open with the hinge held horizontally. */ - data object Tabletop : FoldPosture - /** A foldable that's fully unfolded / flat. */ - data object FullyUnfolded : FoldPosture -} +import com.android.systemui.fold.ui.helper.FoldPosture +import com.android.systemui.fold.ui.helper.foldPostureInternal /** Returns the [FoldPosture] of the device currently. */ @Composable @@ -48,32 +38,6 @@ fun foldPosture(): State<FoldPosture> { initialValue = FoldPosture.Folded, key1 = layoutInfo, ) { - value = - layoutInfo - ?.displayFeatures - ?.firstNotNullOfOrNull { it as? FoldingFeature } - .let { foldingFeature -> - when (foldingFeature?.state) { - null -> FoldPosture.Folded - FoldingFeature.State.HALF_OPENED -> - foldingFeature.orientation.toHalfwayPosture() - FoldingFeature.State.FLAT -> - if (foldingFeature.isSeparating) { - // Dual screen device. - foldingFeature.orientation.toHalfwayPosture() - } else { - FoldPosture.FullyUnfolded - } - else -> error("Unsupported state \"${foldingFeature.state}\"") - } - } - } -} - -private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { - return when (this) { - FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop - FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book - else -> error("Unsupported orientation \"$this\"") + value = foldPostureInternal(layoutInfo) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt new file mode 100644 index 000000000000..61b205710873 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/fold/ui/helper/FoldPostureTest.kt @@ -0,0 +1,124 @@ +/* + * 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.fold.ui.helper + +import android.graphics.Rect +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowLayoutInfo +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FoldPostureTest : SysuiTestCase() { + + @Test + fun foldPosture_whenNull_returnsFolded() { + assertThat(foldPostureInternal(null)).isEqualTo(FoldPosture.Folded) + } + + @Test + fun foldPosture_whenHalfOpenHorizontally_returnsTabletop() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.HALF_OPENED, + orientation = FoldingFeature.Orientation.HORIZONTAL, + ) + ) + ) + .isEqualTo(FoldPosture.Tabletop) + } + + @Test + fun foldPosture_whenHalfOpenVertically_returnsBook() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.HALF_OPENED, + orientation = FoldingFeature.Orientation.VERTICAL, + ) + ) + ) + .isEqualTo(FoldPosture.Book) + } + + @Test + fun foldPosture_whenFlatAndNotSeparating_returnsFullyUnfolded() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + orientation = FoldingFeature.Orientation.HORIZONTAL, + isSeparating = false, + ) + ) + ) + .isEqualTo(FoldPosture.FullyUnfolded) + } + + @Test + fun foldPosture_whenFlatAndSeparatingHorizontally_returnsTabletop() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + isSeparating = true, + orientation = FoldingFeature.Orientation.HORIZONTAL, + ) + ) + ) + .isEqualTo(FoldPosture.Tabletop) + } + + @Test + fun foldPosture_whenFlatAndSeparatingVertically_returnsBook() { + assertThat( + foldPostureInternal( + createWindowLayoutInfo( + state = FoldingFeature.State.FLAT, + isSeparating = true, + orientation = FoldingFeature.Orientation.VERTICAL, + ) + ) + ) + .isEqualTo(FoldPosture.Book) + } + + private fun createWindowLayoutInfo( + state: FoldingFeature.State, + orientation: FoldingFeature.Orientation = FoldingFeature.Orientation.VERTICAL, + isSeparating: Boolean = false, + occlusionType: FoldingFeature.OcclusionType = FoldingFeature.OcclusionType.NONE, + ): WindowLayoutInfo { + return WindowLayoutInfo( + listOf( + object : FoldingFeature { + override val bounds: Rect = Rect(0, 0, 100, 100) + override val isSeparating: Boolean = isSeparating + override val occlusionType: FoldingFeature.OcclusionType = occlusionType + override val orientation: FoldingFeature.Orientation = orientation + override val state: FoldingFeature.State = state + } + ) + ) + } +} diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index b8f4c0f212c3..7ab44e70e6fe 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -53,6 +53,8 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:focusable="true" + android:importantForAccessibility="no" android:tint="?attr/shadeActive" android:visibility="gone" /> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt new file mode 100644 index 000000000000..5385442092b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt @@ -0,0 +1,66 @@ +/* + * 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.bouncer.ui.helper + +import androidx.annotation.VisibleForTesting + +/** Enumerates all known adaptive layout configurations. */ +enum class BouncerSceneLayout { + /** The default UI with the bouncer laid out normally. */ + STANDARD, + /** The bouncer is displayed vertically stacked with the user switcher. */ + STACKED, + /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ + SIDE_BY_SIDE, + /** The bouncer is split in two with both sides shown side-by-side. */ + SPLIT, +} + +/** Enumerates the supported window size classes. */ +enum class SizeClass { + COMPACT, + MEDIUM, + EXPANDED, +} + +/** + * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow + * for testing that's not dependent on Compose. + */ +@VisibleForTesting +fun calculateLayoutInternal( + width: SizeClass, + height: SizeClass, + isSideBySideSupported: Boolean, +): BouncerSceneLayout { + return when (height) { + SizeClass.COMPACT -> BouncerSceneLayout.SPLIT + SizeClass.MEDIUM -> + when (width) { + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD + SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD + SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + } + SizeClass.EXPANDED -> + when (width) { + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD + SizeClass.MEDIUM -> BouncerSceneLayout.STACKED + SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + } + }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported } + ?: BouncerSceneLayout.STANDARD +} diff --git a/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt new file mode 100644 index 000000000000..bc1cc4f4bc56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fold/ui/helper/FoldPosture.kt @@ -0,0 +1,65 @@ +/* + * 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.fold.ui.helper + +import androidx.annotation.VisibleForTesting +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowLayoutInfo + +sealed interface FoldPosture { + /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ + data object Folded : FoldPosture + /** A foldable that's halfway open with the hinge held vertically. */ + data object Book : FoldPosture + /** A foldable that's halfway open with the hinge held horizontally. */ + data object Tabletop : FoldPosture + /** A foldable that's fully unfolded / flat. */ + data object FullyUnfolded : FoldPosture +} + +/** + * Internal version of `foldPosture` in the System UI Compose library, extracted here to allow for + * testing that's not dependent on Compose. + */ +@VisibleForTesting +fun foldPostureInternal(layoutInfo: WindowLayoutInfo?): FoldPosture { + return layoutInfo + ?.displayFeatures + ?.firstNotNullOfOrNull { it as? FoldingFeature } + .let { foldingFeature -> + when (foldingFeature?.state) { + null -> FoldPosture.Folded + FoldingFeature.State.HALF_OPENED -> foldingFeature.orientation.toHalfwayPosture() + FoldingFeature.State.FLAT -> + if (foldingFeature.isSeparating) { + // Dual screen device. + foldingFeature.orientation.toHalfwayPosture() + } else { + FoldPosture.FullyUnfolded + } + else -> error("Unsupported state \"${foldingFeature.state}\"") + } + } +} + +private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { + return when (this) { + FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop + FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book + else -> error("Unsupported orientation \"$this\"") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index fa18b35b215e..052c0daf0b56 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -12,6 +12,7 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,6 +23,7 @@ import android.view.animation.OvershootInterpolator; import android.widget.Scroller; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -43,6 +45,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private static final int NO_PAGE = -1; private static final int REVEAL_SCROLL_DURATION_MILLIS = 750; + private static final int SINGLE_PAGE_SCROLL_DURATION_MILLIS = 300; private static final float BOUNCE_ANIMATION_TENSION = 1.3f; private static final long BOUNCE_ANIMATION_DURATION = 450L; private static final int TILE_ANIMATION_STAGGER_DELAY = 85; @@ -63,8 +66,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private PageListener mPageListener; private boolean mListening; - private Scroller mScroller; + @VisibleForTesting Scroller mScroller; + /* set of animations used to indicate which tiles were just revealed */ @Nullable private AnimatorSet mBounceAnimatorSet; private float mLastExpansion; @@ -306,6 +310,38 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); mPageIndicator.setLocation(mPageIndicatorPosition); + mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need + // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we + // have a chance to intercept ACTION_UP. + if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) { + scrollByX(getDeltaXForKeyboardScrolling(keyCode), + SINGLE_PAGE_SCROLL_DURATION_MILLIS); + } + return true; + } + return false; + }); + } + + private int getDeltaXForKeyboardScrolling(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) { + return -getWidth(); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + && getCurrentItem() != mPages.size() - 1) { + return getWidth(); + } + return 0; + } + + private void scrollByX(int x, int durationMillis) { + if (x != 0) { + mScroller.startScroll(/* startX= */ getScrollX(), /* startY= */ getScrollY(), + /* dx= */ x, /* dy= */ 0, /* duration= */ durationMillis); + // scroller just sets its state, we need to invalidate view to actually start scrolling + postInvalidateOnAnimation(); + } } @Override @@ -596,9 +632,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { }); setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated. int dx = getWidth() * lastPageNumber; - mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0, - REVEAL_SCROLL_DURATION_MILLIS); - postInvalidateOnAnimation(); + scrollByX(isLayoutRtl() ? -dx : dx, REVEAL_SCROLL_DURATION_MILLIS); } private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index b30bc56aeeb0..bc5090f14d23 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -30,12 +30,11 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; +import com.android.systemui.res.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.haptics.slider.SeekableSliderEventProducer; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.ViewController; @@ -282,7 +281,6 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final VibratorHelper mVibratorHelper; private final SystemClock mSystemClock; private final CoroutineDispatcher mMainDispatcher; - private final ActivityStarter mActivityStarter; @Inject public Factory( @@ -290,14 +288,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV UiEventLogger uiEventLogger, VibratorHelper vibratorHelper, SystemClock clock, - @Main CoroutineDispatcher mainDispatcher, - ActivityStarter activityStarter) { + @Main CoroutineDispatcher mainDispatcher + ) { mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mVibratorHelper = vibratorHelper; mSystemClock = clock; mMainDispatcher = mainDispatcher; - mActivityStarter = activityStarter; } /** @@ -313,8 +310,6 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV int layout = getLayout(); BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context) .inflate(layout, viewRoot, false); - root.setActivityStarter(mActivityStarter); - BrightnessSliderHapticPlugin plugin; if (hapticBrightnessSlider()) { plugin = new BrightnessSliderHapticPluginImpl( diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index 5ecf07f5a264..c88549224183 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -33,7 +33,6 @@ import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; /** @@ -42,7 +41,6 @@ import com.android.systemui.res.R; */ public class BrightnessSliderView extends FrameLayout { - private ActivityStarter mActivityStarter; @NonNull private ToggleSeekBar mSlider; private DispatchTouchEventListener mListener; @@ -59,10 +57,6 @@ public class BrightnessSliderView extends FrameLayout { super(context, attrs); } - public void setActivityStarter(@NonNull ActivityStarter activityStarter) { - mActivityStarter = activityStarter; - } - // Inflated from quick_settings_brightness_dialog @Override protected void onFinishInflate() { @@ -71,7 +65,6 @@ public class BrightnessSliderView extends FrameLayout { mSlider = requireViewById(R.id.slider); mSlider.setAccessibilityLabel(getContentDescription().toString()); - mSlider.setActivityStarter(mActivityStarter); // Finds the progress drawable. Assumes brightness_progress_drawable.xml try { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index 6ec10da28000..a5a0ae70045e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -23,9 +23,8 @@ import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar; -import androidx.annotation.NonNull; - import com.android.settingslib.RestrictedLockUtils; +import com.android.systemui.Dependency; import com.android.systemui.plugins.ActivityStarter; public class ToggleSeekBar extends SeekBar { @@ -33,8 +32,6 @@ public class ToggleSeekBar extends SeekBar { private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null; - private ActivityStarter mActivityStarter; - public ToggleSeekBar(Context context) { super(context); } @@ -52,7 +49,7 @@ public class ToggleSeekBar extends SeekBar { if (mEnforcedAdmin != null) { Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( mContext, mEnforcedAdmin); - mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); return true; } if (!isEnabled()) { @@ -77,8 +74,4 @@ public class ToggleSeekBar extends SeekBar { public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { mEnforcedAdmin = admin; } - - public void setActivityStarter(@NonNull ActivityStarter activityStarter) { - mActivityStarter = activityStarter; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java index de334bbd880c..2338be28d32c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java @@ -85,7 +85,9 @@ public class EmptyShadeView extends StackScrollerDecorView { public void setFooterVisibility(@Visibility int visibility) { mFooterVisibility = visibility; - setSecondaryVisible(visibility == View.VISIBLE, false); + setSecondaryVisible(/* visible = */ visibility == View.VISIBLE, + /* animate = */false, + /* onAnimationEnded = */ null); } public void setFooterText(@StringRes int text) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 19bce8988840..fa2748c1dc77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -23,6 +23,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor @@ -41,6 +42,7 @@ internal constructor( private val groupExpansionManagerImpl: GroupExpansionManagerImpl, private val notificationIconAreaController: NotificationIconAreaController, private val renderListInteractor: RenderNotificationListInteractor, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, ) : Coordinator { override fun attach(pipeline: NotifPipeline) { @@ -50,7 +52,13 @@ internal constructor( fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = traceSection("StackCoordinator.onAfterRenderList") { - controller.setNotifStats(calculateNotifStats(entries)) + val notifStats = calculateNotifStats(entries) + if (FooterViewRefactor.isEnabled) { + activeNotificationsInteractor.setNotifStats(notifStats) + } + // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer + // visibility is handled in the new stack. + controller.setNotifStats(notifStats) if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) { renderListInteractor.setRenderedList(entries) } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt index 12ee54d4977d..5ed82cc1ed5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel @@ -37,6 +38,9 @@ class ActiveNotificationListRepository @Inject constructor() { /** Are any already-seen notifications currently filtered out of the active list? */ val hasFilteredOutSeenNotifications = MutableStateFlow(false) + + /** Stats about the list of notifications attached to the shade */ + val notifStats = MutableStateFlow(NotifStats.empty) } /** Represents the notification list, comprised of groups and individual notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 542f3c4c414a..31893b402e3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -15,6 +15,7 @@ package com.android.systemui.statusbar.notification.domain.interactor +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel @@ -52,4 +53,14 @@ constructor( */ val areAnyNotificationsPresentValue: Boolean get() = repository.activeNotifications.value.renderList.isNotEmpty() + + /** Are there are any notifications that can be cleared by the "Clear all" button? */ + val hasClearableNotifications: Flow<Boolean> = + repository.notifStats + .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs } + .distinctUntilChanged() + + fun setNotifStats(notifStats: NotifStats) { + repository.notifStats.value = notifStats + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 10a43d53353d..3184d5efe5cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.util.DumpUtilsKt; import java.io.PrintWriter; +import java.util.function.Consumer; public class FooterView extends StackScrollerDecorView { private static final String TAG = "FooterView"; @@ -63,9 +64,13 @@ public class FooterView extends StackScrollerDecorView { private String mSeenNotifsFilteredText; private Drawable mSeenNotifsFilteredIcon; + private @StringRes int mClearAllButtonTextId; + private @StringRes int mClearAllButtonDescriptionId; private @StringRes int mMessageStringId; private @DrawableRes int mMessageIconId; + private OnClickListener mClearAllButtonClickListener; + public FooterView(Context context, AttributeSet attrs) { super(context, attrs); } @@ -84,12 +89,18 @@ public class FooterView extends StackScrollerDecorView { return isSecondaryVisible(); } + /** See {@link this#setClearAllButtonVisible(boolean, boolean, Consumer)}. */ + public void setClearAllButtonVisible(boolean visible, boolean animate) { + setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null); + } + /** * Set the visibility of the "Clear all" button to {@code visible}. Animate the change if * {@code animate} is true. */ - public void setClearAllButtonVisible(boolean visible, boolean animate) { - setSecondaryVisible(visible, animate); + public void setClearAllButtonVisible(boolean visible, boolean animate, + Consumer<Boolean> onAnimationEnded) { + setSecondaryVisible(visible, animate, onAnimationEnded); } @Override @@ -106,6 +117,42 @@ public class FooterView extends StackScrollerDecorView { }); } + /** Set the text label for the "Clear all" button. */ + public void setClearAllButtonText(@StringRes int textId) { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; + if (mClearAllButtonTextId == textId) { + return; // nothing changed + } + mClearAllButtonTextId = textId; + updateClearAllButtonText(); + } + + private void updateClearAllButtonText() { + if (mClearAllButtonTextId == 0) { + return; // not initialized yet + } + mClearAllButton.setText(getContext().getString(mClearAllButtonTextId)); + } + + /** Set the accessibility content description for the "Clear all" button. */ + public void setClearAllButtonDescription(@StringRes int contentDescriptionId) { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + return; + } + if (mClearAllButtonDescriptionId == contentDescriptionId) { + return; // nothing changed + } + mClearAllButtonDescriptionId = contentDescriptionId; + updateClearAllButtonDescription(); + } + + private void updateClearAllButtonDescription() { + if (mClearAllButtonDescriptionId == 0) { + return; // not initialized yet + } + mClearAllButton.setContentDescription(getContext().getString(mClearAllButtonDescriptionId)); + } + /** Set the string for a message to be shown instead of the buttons. */ public void setMessageString(@StringRes int messageId) { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; @@ -181,6 +228,10 @@ public class FooterView extends StackScrollerDecorView { /** Set onClickListener for the clear all (end) button. */ public void setClearAllButtonClickListener(OnClickListener listener) { + if (FooterViewRefactor.isEnabled()) { + if (mClearAllButtonClickListener == listener) return; + mClearAllButtonClickListener = listener; + } mClearAllButton.setOnClickListener(listener); } @@ -214,7 +265,28 @@ public class FooterView extends StackScrollerDecorView { mManageButton.setText(mManageNotificationText); mManageButton.setContentDescription(mManageNotificationText); } - if (!FooterViewRefactor.isEnabled()) { + if (FooterViewRefactor.isEnabled()) { + updateClearAllButtonText(); + updateClearAllButtonDescription(); + + updateMessageString(); + updateMessageIcon(); + } else { + // NOTE: Prior to the refactor, `updateResources` set the class properties to the right + // string values. It was always being called together with `updateContent`, which + // deals with actually associating those string values with the correct views + // (buttons or text). + // In the new code, the resource IDs are being set in the view binder (through + // setMessageString and similar setters). The setters themselves now deal with + // updating both the resource IDs and the views where appropriate (as in, calling + // `updateMessageString` when the resource ID changes). This eliminates the need for + // `updateResources`, which will eventually be removed. There are, however, still + // situations in which we want to update the views even if the resource IDs didn't + // change, such as configuration changes. + mClearAllButton.setText(R.string.clear_all_notifications_text); + mClearAllButton.setContentDescription( + mContext.getString(R.string.accessibility_clear_all)); + mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); mSeenNotifsFooterTextView .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); @@ -230,16 +302,8 @@ public class FooterView extends StackScrollerDecorView { protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateColors(); - mClearAllButton.setText(R.string.clear_all_notifications_text); - mClearAllButton.setContentDescription( - mContext.getString(R.string.accessibility_clear_all)); updateResources(); updateContent(); - - if (FooterViewRefactor.isEnabled()) { - updateMessageString(); - updateMessageIcon(); - } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt index 6d8234371b65..0299114e0afc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt @@ -16,10 +16,14 @@ package com.android.systemui.statusbar.notification.footer.ui.viewbinder +import android.view.View import androidx.lifecycle.lifecycleScope import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.stopAnimating +import com.android.systemui.util.ui.value import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.launch @@ -28,9 +32,31 @@ object FooterViewBinder { fun bind( footer: FooterView, viewModel: FooterViewModel, + clearAllNotifications: View.OnClickListener, ): DisposableHandle { + // Listen for changes when the view is attached. return footer.repeatWhenAttached { - // Listen for changes when the view is attached. + lifecycleScope.launch { + viewModel.clearAllButton.collect { button -> + if (button.isVisible.isAnimating) { + footer.setClearAllButtonVisible( + button.isVisible.value, + /* animate = */ true, + ) { _ -> + button.isVisible.stopAnimating() + } + } else { + footer.setClearAllButtonVisible( + button.isVisible.value, + /* animate = */ false, + ) + } + footer.setClearAllButtonText(button.labelId) + footer.setClearAllButtonDescription(button.accessibilityDescriptionId) + footer.setClearAllButtonClickListener(clearAllNotifications) + } + } + lifecycleScope.launch { viewModel.message.collect { message -> footer.setFooterLabelVisible(message.visible) diff --git a/core/java/android/os/WorkDuration.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt index 0f61204d72c4..ea5abeff7042 100644 --- a/core/java/android/os/WorkDuration.aidl +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterButtonViewModel.kt @@ -14,6 +14,13 @@ * limitations under the License. */ -package android.os; +package com.android.systemui.statusbar.notification.footer.ui.viewmodel -parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file +import android.annotation.StringRes +import com.android.systemui.util.ui.AnimatedValue + +data class FooterButtonViewModel( + @StringRes val labelId: Int, + @StringRes val accessibilityDescriptionId: Int, + val isVisible: AnimatedValue<Boolean>, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt index 3d68a7b6f9b9..721bea1086e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt @@ -18,18 +18,51 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.res.R +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.ui.AnimatableEvent +import com.android.systemui.util.ui.toAnimatedValueFlow import dagger.Module import dagger.Provides import java.util.Optional import javax.inject.Provider import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** ViewModel for [FooterView]. */ -class FooterViewModel(seenNotificationsInteractor: SeenNotificationsInteractor) { +class FooterViewModel( + activeNotificationsInteractor: ActiveNotificationsInteractor, + seenNotificationsInteractor: SeenNotificationsInteractor, + shadeInteractor: ShadeInteractor, +) { + val clearAllButton: Flow<FooterButtonViewModel> = + activeNotificationsInteractor.hasClearableNotifications + .sample( + combine( + shadeInteractor.isShadeFullyExpanded, + shadeInteractor.isShadeTouchable, + ::Pair + ) + .onStart { emit(Pair(false, false)) } + ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) -> + val shouldAnimate = isShadeFullyExpanded && animationsEnabled + AnimatableEvent(hasClearableNotifications, shouldAnimate) + } + .toAnimatedValueFlow() + .map { visible -> + FooterButtonViewModel( + labelId = R.string.clear_all_notifications_text, + accessibilityDescriptionId = R.string.accessibility_clear_all, + isVisible = visible, + ) + } + val message: Flow<FooterMessageViewModel> = seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs -> FooterMessageViewModel( @@ -45,10 +78,18 @@ object FooterViewModelModule { @Provides @SysUISingleton fun provideOptional( + activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>, seenNotificationsInteractor: Provider<SeenNotificationsInteractor>, + shadeInteractor: Provider<ShadeInteractor>, ): Optional<FooterViewModel> { return if (FooterViewRefactor.isEnabled) { - Optional.of(FooterViewModel(seenNotificationsInteractor.get())) + Optional.of( + FooterViewModel( + activeNotificationsInteractor.get(), + seenNotificationsInteractor.get(), + shadeInteractor.get() + ) + ) } else { Optional.empty() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 9f2b0a6bfc1f..8e824423973f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -204,6 +204,11 @@ class BubbleNotAllowedSuppressor() : override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble() } +class BubbleAppSuspendedSuppressor : + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "app is suspended") { + override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended +} + class BubbleNoMetadataSuppressor() : VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 2fffd379e6f9..334e08d5a78f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -56,6 +56,14 @@ class NotificationInterruptLogger @Inject constructor( } } + fun logSuspendedAppBubble(entry: NotificationEntry) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + }, { + "No bubble up: notification: app $str1 is suspended" + }) + } + fun logNoBubbleNoMetadata(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 40453800d17f..510086d4892b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -204,6 +204,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } + if (entry.getRanking().isSuspended()) { + mLogger.logSuspendedAppBubble(entry); + return false; + } + if (entry.getBubbleMetadata() == null || (entry.getBubbleMetadata().getShortcutId() == null && entry.getBubbleMetadata().getIntent() == null)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 39a87de89330..73dd5f6e5fb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -159,6 +159,7 @@ constructor( addFilter(PulseLockscreenVisibilityPrivateSuppressor()) addFilter(PulseLowImportanceSuppressor()) addFilter(BubbleNotAllowedSuppressor()) + addFilter(BubbleAppSuspendedSuppressor()) addFilter(BubbleNoMetadataSuppressor()) addFilter(HunGroupAlertBehaviorSuppressor()) addFilter(HunJustLaunchedFsiSuppressor()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index ec90a8d6ad59..162e8af47394 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -54,7 +54,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { mContent = findContentView(); mSecondaryView = findSecondaryView(); setVisible(false /* visible */, false /* animate */); - setSecondaryVisible(false /* visible */, false /* animate */); + setSecondaryVisible(false /* visible */, false /* animate */, null /* onAnimationEnd */); setOutlineProvider(null); } @@ -155,15 +155,23 @@ public abstract class StackScrollerDecorView extends ExpandableView { /** * Set the secondary view of this layout to visible. * - * @param visible should the secondary view be visible - * @param animate should the change be animated + * @param visible True if the contents should be visible. + * @param animate True if we should fade to new visibility. + * @param onAnimationEnded Callback to run after visibility updates, takes a boolean as a + * parameter that represents whether the animation was cancelled. */ - protected void setSecondaryVisible(boolean visible, boolean animate) { + protected void setSecondaryVisible(boolean visible, boolean animate, + Consumer<Boolean> onAnimationEnded) { if (mIsSecondaryVisible != visible) { mSecondaryAnimating = animate; mIsSecondaryVisible = visible; - setViewVisible(mSecondaryView, visible, animate, - (cancelled) -> onSecondaryVisibilityAnimationEnd()); + Consumer<Boolean> onAnimationEndedWrapper = (cancelled) -> { + onContentVisibilityAnimationEnd(); + if (onAnimationEnded != null) { + onAnimationEnded.accept(cancelled); + } + }; + setViewVisible(mSecondaryView, visible, animate, onAnimationEndedWrapper); } if (!mSecondaryAnimating) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 6a34f980dde3..283a5930f930 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4608,13 +4608,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mManageButtonClickListener != null) { mFooterView.setManageButtonClickListener(mManageButtonClickListener); } - mFooterView.setClearAllButtonClickListener(v -> { - if (mFooterClearAllListener != null) { - mFooterClearAllListener.onClearAll(); - } - clearNotifications(ROWS_ALL, true /* closeShade */); - footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); - }); + if (!FooterViewRefactor.isEnabled()) { + mFooterView.setClearAllButtonClickListener(v -> { + if (mFooterClearAllListener != null) { + mFooterClearAllListener.onClearAll(); + } + clearNotifications(ROWS_ALL, true /* closeShade */); + footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); + }); + } if (FooterViewRefactor.isEnabled()) { updateFooter(); } @@ -4687,9 +4689,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } boolean animate = mIsExpanded && mAnimationsEnabled; mFooterView.setVisible(visible, animate); - mFooterView.setClearAllButtonVisible(showDismissView, animate); mFooterView.showHistory(showHistory); if (!FooterViewRefactor.isEnabled()) { + mFooterView.setClearAllButtonVisible(showDismissView, animate); mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); } } @@ -5355,11 +5357,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return viewsToRemove; } + /** Clear all clearable notifications when the user requests it. */ + public void clearAllNotifications() { + clearNotifications(ROWS_ALL, /* closeShade = */ true); + } + /** * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. */ - @VisibleForTesting void clearNotifications(@SelectedRows int selection, boolean closeShade) { // Animate-swipe all dismissable notifications, then animate the shade closed final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); @@ -5659,6 +5665,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void setFooterClearAllListener(FooterClearAllListener listener) { + FooterViewRefactor.assertInLegacyMode(); mFooterClearAllListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index d5ec0c552f83..e6315fd159d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -470,7 +470,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onSnooze(StatusBarNotification sbn, - NotificationSwipeActionHelper.SnoozeOption snoozeOption) { + NotificationSwipeActionHelper.SnoozeOption snoozeOption) { mNotificationsController.setNotificationSnoozed(sbn, snoozeOption); } @@ -592,7 +592,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public boolean updateSwipeProgress(View animView, boolean dismissable, - float swipeProgress) { + float swipeProgress) { // Returning true prevents alpha fading. return false; } @@ -759,8 +759,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setClearAllAnimationListener(this::onAnimationEnd); mView.setClearAllListener((selection) -> mUiEventLogger.log( NotificationPanelEvent.fromSelection(selection))); - mView.setFooterClearAllListener(() -> - mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); + if (!FooterViewRefactor.isEnabled()) { + mView.setFooterClearAllListener(() -> + mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); + } mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { @Override @@ -1090,7 +1092,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setOverScrollAmount(float amount, boolean onTop, boolean animate, - boolean cancelAnimators) { + boolean cancelAnimators) { mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators); } @@ -1408,14 +1410,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Return whether there are any clearable notifications */ public boolean hasActiveClearableNotifications(@SelectedRows int selection) { - // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the clear all - // button in the refactored code + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer + // visibility in the refactored code return hasNotifications(selection, true /* clearable */); } public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { - // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the clear all - // button in the refactored code + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer + // visibility in the refactored code boolean hasAlertingMatchingClearable = isClearable ? mNotifStats.getHasClearableAlertingNotifs() : mNotifStats.getHasNonClearableAlertingNotifs(); @@ -1454,7 +1456,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { public RemoteInputController.Delegate createDelegate() { return new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationEntry entry, - boolean remoteInputActive) { + boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); updateFooter(); @@ -1573,7 +1575,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, - @SelectedRows int selectedRows) { + @SelectedRows int selectedRows) { if (selectedRows == ROWS_ALL) { mNotifCollection.dismissAllNotifications( mLockscreenUserManager.getCurrentUserId()); @@ -1665,7 +1667,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Set rounded rect clipping bounds on this view. */ public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius, - int bottomRadius) { + int bottomRadius) { mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius); } @@ -2021,7 +2023,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private class NotifStackControllerImpl implements NotifStackController { @Override public void setNotifStats(@NonNull NotifStats notifStats) { - // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once clear all visibility + // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility // is handled in the refactored stack. mNotifStats = notifStats; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 54df4abe8b13..4554085c35c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.view.LayoutInflater import androidx.lifecycle.lifecycleScope import com.android.app.tracing.traceSection +import com.android.internal.logging.MetricsLogger +import com.android.internal.logging.nano.MetricsProto import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.reinflateAndBindLatest import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo @@ -46,6 +48,7 @@ class NotificationListViewBinder @Inject constructor( private val viewModel: NotificationListViewModel, + private val metricsLogger: MetricsLogger, private val configuration: ConfigurationState, private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, @@ -100,7 +103,17 @@ constructor( attachToRoot = false, ) { footerView: FooterView -> traceSection("bind FooterView") { - val disposableHandle = FooterViewBinder.bind(footerView, footerViewModel) + val disposableHandle = + FooterViewBinder.bind( + footerView, + footerViewModel, + clearAllNotifications = { + metricsLogger.action( + MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES + ) + parentView.clearAllNotifications() + }, + ) parentView.setFooterView(footerView) return@reinflateAndBindLatest disposableHandle } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt new file mode 100644 index 000000000000..395d7129bee3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt @@ -0,0 +1,253 @@ +/* + * 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.bouncer.ui.helper + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD +import com.google.common.truth.Truth.assertThat +import java.util.Locale +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@SmallTest +@RunWith(Parameterized::class) +class BouncerSceneLayoutTest : SysuiTestCase() { + + data object Phone : + Device( + name = "phone", + width = SizeClass.COMPACT, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + data object Tablet : + Device( + name = "tablet", + width = SizeClass.EXPANDED, + height = SizeClass.MEDIUM, + naturallyHeld = Horizontally, + ) + data object Folded : + Device( + name = "folded", + width = SizeClass.COMPACT, + height = SizeClass.MEDIUM, + naturallyHeld = Vertically, + ) + data object Unfolded : + Device( + name = "unfolded", + width = SizeClass.EXPANDED, + height = SizeClass.MEDIUM, + naturallyHeld = Vertically, + widthWhenUnnaturallyHeld = SizeClass.MEDIUM, + heightWhenUnnaturallyHeld = SizeClass.MEDIUM, + ) + data object TallerFolded : + Device( + name = "taller folded", + width = SizeClass.COMPACT, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + data object TallerUnfolded : + Device( + name = "taller unfolded", + width = SizeClass.EXPANDED, + height = SizeClass.EXPANDED, + naturallyHeld = Vertically, + ) + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun testCases() = + listOf( + Phone to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + Tablet to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = STACKED, + ), + Folded to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + Unfolded to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = STANDARD, + ), + TallerFolded to + Expected( + whenNaturallyHeld = STANDARD, + whenUnnaturallyHeld = SPLIT, + ), + TallerUnfolded to + Expected( + whenNaturallyHeld = SIDE_BY_SIDE, + whenUnnaturallyHeld = SIDE_BY_SIDE, + ), + ) + .flatMap { (device, expected) -> + buildList { + // Holding the device in its natural orientation (vertical or horizontal): + add( + TestCase( + device = device, + held = device.naturallyHeld, + expected = expected.layout(heldNaturally = true), + ) + ) + + if (expected.whenNaturallyHeld == SIDE_BY_SIDE) { + add( + TestCase( + device = device, + held = device.naturallyHeld, + isSideBySideSupported = false, + expected = STANDARD, + ) + ) + } + + // Holding the device the other way: + add( + TestCase( + device = device, + held = device.naturallyHeld.flip(), + expected = expected.layout(heldNaturally = false), + ) + ) + + if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) { + add( + TestCase( + device = device, + held = device.naturallyHeld.flip(), + isSideBySideSupported = false, + expected = STANDARD, + ) + ) + } + } + } + } + + @Parameterized.Parameter @JvmField var testCase: TestCase? = null + + @Test + fun calculateLayout() { + testCase?.let { nonNullTestCase -> + with(nonNullTestCase) { + assertThat( + calculateLayoutInternal( + width = device.width(whenHeld = held), + height = device.height(whenHeld = held), + isSideBySideSupported = isSideBySideSupported, + ) + ) + .isEqualTo(expected) + } + } + } + + data class TestCase( + val device: Device, + val held: Held, + val expected: BouncerSceneLayout, + val isSideBySideSupported: Boolean = true, + ) { + override fun toString(): String { + return buildString { + append(device.name) + append(" width: ${device.width(held).name.lowercase(Locale.US)}") + append(" height: ${device.height(held).name.lowercase(Locale.US)}") + append(" when held $held") + if (!isSideBySideSupported) { + append(" (side-by-side not supported)") + } + } + } + } + + data class Expected( + val whenNaturallyHeld: BouncerSceneLayout, + val whenUnnaturallyHeld: BouncerSceneLayout, + ) { + fun layout(heldNaturally: Boolean): BouncerSceneLayout { + return if (heldNaturally) { + whenNaturallyHeld + } else { + whenUnnaturallyHeld + } + } + } + + sealed class Device( + val name: String, + private val width: SizeClass, + private val height: SizeClass, + val naturallyHeld: Held, + private val widthWhenUnnaturallyHeld: SizeClass = height, + private val heightWhenUnnaturallyHeld: SizeClass = width, + ) { + fun width(whenHeld: Held): SizeClass { + return if (isHeldNaturally(whenHeld)) { + width + } else { + widthWhenUnnaturallyHeld + } + } + + fun height(whenHeld: Held): SizeClass { + return if (isHeldNaturally(whenHeld)) { + height + } else { + heightWhenUnnaturallyHeld + } + } + + private fun isHeldNaturally(whenHeld: Held): Boolean { + return whenHeld == naturallyHeld + } + } + + sealed class Held { + abstract fun flip(): Held + } + data object Vertically : Held() { + override fun flip(): Held { + return Horizontally + } + } + data object Horizontally : Held() { + override fun flip(): Held { + return Vertically + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt new file mode 100644 index 000000000000..db9e548e74c8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt @@ -0,0 +1,86 @@ +package com.android.systemui.qs + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.view.KeyEvent +import android.view.View +import android.widget.Scroller +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class PagedTileLayoutTest : SysuiTestCase() { + + @Mock private lateinit var pageIndicator: PageIndicator + @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener> + + private lateinit var pageTileLayout: TestPagedTileLayout + private lateinit var scroller: Scroller + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + pageTileLayout = TestPagedTileLayout(mContext) + pageTileLayout.setPageIndicator(pageIndicator) + verify(pageIndicator).setOnKeyListener(captor.capture()) + setViewWidth(pageTileLayout, width = PAGE_WIDTH) + scroller = pageTileLayout.mScroller + } + + private fun setViewWidth(view: View, width: Int) { + view.left = 0 + view.right = width + } + + @Test + fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() { + pageTileLayout.currentPageIndex = 0 + + sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT) + + assertThat(scroller.isFinished).isFalse() // aka we're scrolling + assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH) + } + + @Test + fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() { + pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page + + sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT) + + assertThat(scroller.isFinished).isFalse() // aka we're scrolling + assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH) + } + + private fun sendUpEvent(keyCode: Int) { + val event = KeyEvent(KeyEvent.ACTION_UP, keyCode) + captor.value.onKey(pageIndicator, keyCode, event) + } + + /** + * Custom PagedTileLayout to easy mock "currentItem" i.e. currently visible page. Setting this + * up otherwise would require setting adapter etc + */ + class TestPagedTileLayout(context: Context) : PagedTileLayout(context, null) { + + var currentPageIndex: Int = 0 + + override fun getCurrentItem(): Int { + return currentPageIndex + } + } + + companion object { + const val PAGE_WIDTH = 200 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 428574bb15f8..fa5fad06b671 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfte import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING @@ -57,6 +58,7 @@ class StackCoordinatorTest : SysuiTestCase() { @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor + @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor @Mock private lateinit var stackController: NotifStackController @Mock private lateinit var section: NotifSection @@ -75,6 +77,7 @@ class StackCoordinatorTest : SysuiTestCase() { groupExpansionManagerImpl, notificationIconAreaController, renderListInteractor, + activeNotificationsInteractor, ) coordinator.attach(pipeline) afterRenderListListener = withArgCaptor { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt index b24cafdfdc84..4ab3cd49b297 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt @@ -72,4 +72,58 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() { assertThat(areAnyNotificationsPresent).isFalse() assertThat(underTest.areAnyNotificationsPresentValue).isFalse() } + + @Test + fun testHasClearableNotifications_whenHasClearableAlertingNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasClearableSilentNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasNoClearableNotifs() = + testComponent.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index a64ac674a91c..22c5bae93489 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -114,9 +114,46 @@ public class FooterViewTest extends SysuiTestCase { } @Test + public void testSetClearAllButtonText_resourceOnlyFetchedOnce() { + int resId = R.string.clear_all_notifications_text; + mView.setClearAllButtonText(resId); + verify(mSpyContext).getString(eq(resId)); + + clearInvocations(mSpyContext); + + assertThat(((TextView) mView.findViewById(R.id.dismiss_text)) + .getText().toString()).contains("Clear all"); + + // Set it a few more times, it shouldn't lead to the resource being fetched again + mView.setClearAllButtonText(resId); + mView.setClearAllButtonText(resId); + + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test + public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() { + int resId = R.string.accessibility_clear_all; + mView.setClearAllButtonDescription(resId); + verify(mSpyContext).getString(eq(resId)); + + clearInvocations(mSpyContext); + + assertThat(((TextView) mView.findViewById(R.id.dismiss_text)) + .getContentDescription().toString()).contains("Clear all notifications"); + + // Set it a few more times, it shouldn't lead to the resource being fetched again + mView.setClearAllButtonDescription(resId); + mView.setClearAllButtonDescription(resId); + + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test public void testSetMessageString_resourceOnlyFetchedOnce() { - mView.setMessageString(R.string.unlock_to_see_notif_text); - verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text)); + int resId = R.string.unlock_to_see_notif_text; + mView.setMessageString(resId); + verify(mSpyContext).getString(eq(resId)); clearInvocations(mSpyContext); @@ -124,22 +161,23 @@ public class FooterViewTest extends SysuiTestCase { .getText().toString()).contains("Unlock"); // Set it a few more times, it shouldn't lead to the resource being fetched again - mView.setMessageString(R.string.unlock_to_see_notif_text); - mView.setMessageString(R.string.unlock_to_see_notif_text); + mView.setMessageString(resId); + mView.setMessageString(resId); verify(mSpyContext, never()).getString(anyInt()); } @Test public void testSetMessageIcon_resourceOnlyFetchedOnce() { - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); - verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed)); + int resId = R.drawable.ic_friction_lock_closed; + mView.setMessageIcon(resId); + verify(mSpyContext).getDrawable(eq(resId)); clearInvocations(mSpyContext); // Set it a few more times, it shouldn't lead to the resource being fetched again - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); - mView.setMessageIcon(R.drawable.ic_friction_lock_closed); + mView.setMessageIcon(resId); + mView.setMessageIcon(resId); verify(mSpyContext, never()).getDrawable(anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 57a7c3c7e2bf..94dcf7a18514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -18,37 +18,222 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysUITestComponent +import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.TestMocksModule +import com.android.systemui.collectLastValue +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.runCurrent +import com.android.systemui.runTest +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.ui.isAnimating +import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest +import dagger.BindsInstance +import dagger.Component +import java.util.Optional +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest class FooterViewModelTest : SysuiTestCase() { - private val repository = ActiveNotificationListRepository() - private val interactor = SeenNotificationsInteractor(repository) - private val underTest = FooterViewModel(interactor) + private lateinit var footerViewModel: FooterViewModel - @Test - fun testMessageVisible_whenFilteredNotifications() = runTest { - val message by collectLastValue(underTest.message) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + ActivatableNotificationViewModelModule::class, + FooterViewModelModule::class, + HeadlessSystemUserModeModule::class, + ] + ) + interface TestComponent : SysUITestComponent<Optional<FooterViewModel>> { + val activeNotificationListRepository: ActiveNotificationListRepository + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val shadeRepository: FakeShadeRepository + val powerRepository: FakePowerRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() - repository.hasFilteredOutSeenNotifications.value = true + private val testComponent: TestComponent = + DaggerFooterViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ) + ) - assertThat(message?.visible).isTrue() + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) + + // The underTest in the component is Optional, because that matches the provider we + // currently have for the footer view model. + footerViewModel = testComponent.underTest.get() } @Test - fun testMessageVisible_whenNoFilteredNotifications() = runTest { - val message by collectLastValue(underTest.message) + fun testMessageVisible_whenFilteredNotifications() = + testComponent.runTest { + val message by collectLastValue(footerViewModel.message) - repository.hasFilteredOutSeenNotifications.value = false + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true - assertThat(message?.visible).isFalse() - } + assertThat(message?.visible).isTrue() + } + + @Test + fun testMessageVisible_whenNoFilteredNotifications() = + testComponent.runTest { + val message by collectLastValue(footerViewModel.message) + + activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false + + assertThat(message?.visible).isFalse() + } + + @Test + fun testClearAllButtonVisible_whenHasClearableNotifs() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(button?.isVisible?.value).isTrue() + } + + @Test + fun testClearAllButtonVisible_whenHasNoClearableNotifs() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(button?.isVisible?.value).isFalse() + } + + @Test + fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + runCurrent() + + // WHEN shade is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setLegacyShadeExpansion(1f) + // AND QS not expanded + shadeRepository.setQsExpansion(0f) + // AND device is awake + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // AND there are clearable notifications + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + // THEN button visibility should animate + assertThat(button?.isVisible?.isAnimating).isTrue() + } + + @Test + fun testClearAllButtonAnimating_whenShadeNotExpanded() = + testComponent.runTest { + val button by collectLastValue(footerViewModel.clearAllButton) + runCurrent() + + // WHEN shade is collapsed + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setLegacyShadeExpansion(0f) + // AND QS not expanded + shadeRepository.setQsExpansion(0f) + // AND device is awake + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // AND there are clearable notifications + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + // THEN button visibility should not animate + assertThat(button?.isVisible?.isAnimating).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 3e331a6cff2a..0a9bac91004a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -999,11 +999,25 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse(); } + @Test + public void shouldNotBubbleUp_suspended() { + assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createSuspendedBubble())) + .isFalse(); + } + + private NotificationEntry createSuspendedBubble() { + return createBubble(null, null, true); + } + private NotificationEntry createBubble() { - return createBubble(null, null); + return createBubble(null, null, false); } private NotificationEntry createBubble(String groupKey, Integer groupAlert) { + return createBubble(groupKey, groupAlert, false); + } + + private NotificationEntry createBubble(String groupKey, Integer groupAlert, Boolean suspended) { Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder( PendingIntent.getActivity(mContext, 0, new Intent().setPackage(mContext.getPackageName()), @@ -1031,6 +1045,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { .setNotification(n) .setImportance(IMPORTANCE_HIGH) .setCanBubble(true) + .setSuspended(suspended) .build(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index a3b7e8c8ca0a..7babff5e0b2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -601,6 +601,13 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testShouldNotBubble_bubbleAppSuspended() { + ensureBubbleState() + assertShouldNotBubble(buildBubbleEntry { packageSuspended = true }) + assertNoEventsLogged() + } + + @Test fun testShouldNotFsi_noFullScreenIntent() { forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 9d8f979e883c..f3b2ef34ad6d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -552,7 +552,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mAsync = true; } else if (opt.equals("--splashscreen-show-icon")) { mShowSplashScreen = true; - } else if (opt.equals("--dismiss-keyguard-if-insecure")) { + } else if (opt.equals("--dismiss-keyguard-if-insecure") + || opt.equals("--dismiss-keyguard")) { mDismissKeyguardIfInsecure = true; } else { return false; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index a158b18d91b4..c24d6a054236 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -527,12 +527,27 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public boolean canHandleVolumeKey() { if (isPlaybackTypeLocal()) { + if (DEBUG) { + Log.d(TAG, "Local MediaSessionRecord can handle volume key"); + } return true; } if (mVolumeControlType == VOLUME_CONTROL_FIXED) { + if (DEBUG) { + Log.d( + TAG, + "Local MediaSessionRecord with FIXED volume control can't handle volume" + + " key"); + } return false; } if (mVolumeAdjustmentForRemoteGroupSessions) { + if (DEBUG) { + Log.d( + TAG, + "Volume adjustment for remote group sessions allowed so MediaSessionRecord" + + " can handle volume key"); + } return true; } // See b/228021646 for details. @@ -540,7 +555,18 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName); boolean foundNonSystemSession = false; boolean remoteSessionAllowVolumeAdjustment = true; + if (DEBUG) { + Log.d( + TAG, + "Found " + + sessions.size() + + " routing sessions for package name " + + mPackageName); + } for (RoutingSessionInfo session : sessions) { + if (DEBUG) { + Log.d(TAG, "Found routingSessionInfo: " + session); + } if (!session.isSystemSession()) { foundNonSystemSession = true; if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) { @@ -549,9 +575,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } } if (!foundNonSystemSession) { - Log.d(TAG, "Package " + mPackageName - + " has a remote media session but no associated routing session"); + if (DEBUG) { + Log.d( + TAG, + "Package " + + mPackageName + + " has a remote media session but no associated routing session"); + } } + return foundNonSystemSession && remoteSessionAllowVolumeAdjustment; } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index ee3b74653b75..dd39fb02573e 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -32,8 +32,6 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; -import android.os.WorkDuration; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseIntArray; @@ -197,9 +195,6 @@ public final class HintManagerService extends SystemService { private static native void nativeSetMode(long halPtr, int mode, boolean enabled); - private static native void nativeReportActualWorkDuration(long halPtr, - WorkDuration[] workDurations); - /** Wrapper for HintManager.nativeInit */ public void halInit() { nativeInit(); @@ -257,10 +252,6 @@ public final class HintManagerService extends SystemService { nativeSetMode(halPtr, mode, enabled); } - /** Wrapper for HintManager.nativeReportActualWorkDuration */ - public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) { - nativeReportActualWorkDuration(halPtr, workDurations); - } } @VisibleForTesting @@ -633,52 +624,6 @@ public final class HintManagerService extends SystemService { } } - @Override - public void reportActualWorkDuration2(WorkDuration[] workDurations) { - synchronized (this) { - if (mHalSessionPtr == 0 || !mUpdateAllowed) { - return; - } - Preconditions.checkArgument(workDurations.length != 0, "the count" - + " of work durations shouldn't be 0."); - for (WorkDuration workDuration : workDurations) { - validateWorkDuration(workDuration); - } - mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations); - } - } - - void validateWorkDuration(WorkDuration workDuration) { - if (DEBUG) { - Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", " - + workDuration.getWorkPeriodStartTimestampNanos() + ", " - + workDuration.getActualTotalDurationNanos() + ", " - + workDuration.getActualCpuDurationNanos() + ", " - + workDuration.getActualGpuDurationNanos() + ")"); - } - if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple( - "Work period start timestamp (%d) should be greater than 0", - workDuration.getWorkPeriodStartTimestampNanos())); - } - if (workDuration.getActualTotalDurationNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual total duration (%d) should be greater than 0", - workDuration.getActualTotalDurationNanos())); - } - if (workDuration.getActualCpuDurationNanos() <= 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0", - workDuration.getActualCpuDurationNanos())); - } - if (workDuration.getActualGpuDurationNanos() < 0) { - throw new IllegalArgumentException( - TextUtils.formatSimple("Actual GPU duration (%d) should be non negative", - workDuration.getActualGpuDurationNanos())); - } - } - private void onProcStateChanged(boolean updateAllowed) { updateHintAllowed(updateAllowed); } diff --git a/services/core/java/com/android/server/timedetector/TEST_MAPPING b/services/core/java/com/android/server/timedetector/TEST_MAPPING index 5c37680af745..17d327e94d4d 100644 --- a/services/core/java/com/android/server/timedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timedetector/TEST_MAPPING @@ -7,10 +7,7 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] - } - ], - // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. - "postsubmit": [ + }, { "name": "FrameworksTimeServicesTests" } diff --git a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING index 63dd7b42f23b..358618a71cbc 100644 --- a/services/core/java/com/android/server/timezonedetector/TEST_MAPPING +++ b/services/core/java/com/android/server/timezonedetector/TEST_MAPPING @@ -7,15 +7,15 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "FrameworksTimeServicesTests" } ], // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows. "postsubmit": [ { "name": "CtsLocationTimeZoneManagerHostTest" - }, - { - "name": "FrameworksTimeServicesTests" } ] } diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index ccd9bd0a50ca..7edf445d7604 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -20,7 +20,6 @@ #include <aidl/android/hardware/power/IPower.h> #include <android-base/stringprintf.h> -#include <inttypes.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <powermanager/PowerHalController.h> @@ -39,15 +38,6 @@ using android::base::StringPrintf; namespace android { -static struct { - jclass clazz{}; - jfieldID workPeriodStartTimestampNanos{}; - jfieldID actualTotalDurationNanos{}; - jfieldID actualCpuDurationNanos{}; - jfieldID actualGpuDurationNanos{}; - jfieldID timestampNanos{}; -} gWorkDurationInfo; - static power::PowerHalController gPowerHalController; static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap; static std::mutex gSessionMapLock; @@ -190,26 +180,6 @@ static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, ji setMode(session_ptr, static_cast<SessionMode>(mode), enabled); } -static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, - jobjectArray jWorkDurations) { - int size = env->GetArrayLength(jWorkDurations); - std::vector<WorkDuration> workDurations(size); - for (int i = 0; i < size; i++) { - jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); - workDurations[i].workPeriodStartTimestampNanos = - env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos); - workDurations[i].durationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos); - workDurations[i].cpuDurationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos); - workDurations[i].gpuDurationNanos = - env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos); - workDurations[i].timeStampNanos = - env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos); - } - reportActualWorkDuration(session_ptr, workDurations); -} - // ---------------------------------------------------------------------------- static const JNINativeMethod sHintManagerServiceMethods[] = { /* name, signature, funcPtr */ @@ -224,23 +194,9 @@ static const JNINativeMethod sHintManagerServiceMethods[] = { {"nativeSendHint", "(JI)V", (void*)nativeSendHint}, {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode}, - {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V", - (void*)nativeReportActualWorkDuration2}, }; int register_android_server_HintManagerService(JNIEnv* env) { - gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration"); - gWorkDurationInfo.workPeriodStartTimestampNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J"); - gWorkDurationInfo.actualTotalDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J"); - gWorkDurationInfo.actualCpuDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J"); - gWorkDurationInfo.actualGpuDurationNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J"); - gWorkDurationInfo.timestampNanos = - env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J"); - return jniRegisterNativeMethods(env, "com/android/server/power/hint/" "HintManagerService$NativeWrapper", diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 37485275dac7..d09aa89179b8 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -44,7 +44,6 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; -import android.os.WorkDuration; import android.util.Log; import com.android.server.FgThread; @@ -90,11 +89,6 @@ public class HintManagerServiceTest { private static final long[] DURATIONS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; - private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] { - new WorkDuration(1L, 11L, 8L, 4L, 1L), - new WorkDuration(2L, 13L, 8L, 6L, 2L), - new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L), - }; @Mock private Context mContext; @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @@ -599,55 +593,4 @@ public class HintManagerServiceTest { } a.close(); } - - @Test - public void testReportActualWorkDuration2() throws Exception { - HintManagerService service = createService(); - IBinder token = new Binder(); - - AppHintSession a = (AppHintSession) service.getBinderServiceInstance() - .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); - - a.updateTargetWorkDuration(100L); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); - verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), - eq(WORK_DURATIONS_THREE)); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)}); - }); - - assertThrows(IllegalArgumentException.class, () -> { - a.reportActualWorkDuration2( - new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)}); - }); - - reset(mNativeWrapperMock); - // Set session to background, then the duration would not be updated. - service.mUidObserver.onUidStateChanged( - a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); - - // Using CountDownLatch to ensure above onUidStateChanged() job was digested. - final CountDownLatch latch = new CountDownLatch(1); - FgThread.getHandler().post(() -> { - latch.countDown(); - }); - latch.await(); - - assertFalse(service.mUidObserver.isUidForeground(a.mUid)); - a.reportActualWorkDuration2(WORK_DURATIONS_THREE); - verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); - } } |