diff options
352 files changed, 8358 insertions, 3886 deletions
diff --git a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java new file mode 100644 index 000000000000..cf94e9e0d384 --- /dev/null +++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_UP; +import static android.view.MotionEvent.TOOL_TYPE_FINGER; +import static android.view.MotionEvent.TOOL_TYPE_STYLUS; + + +import android.app.Instrumentation; +import android.content.Context; +import android.graphics.Rect; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Benchmark tests for {@link HandwritingInitiator} + * + * Build/Install/Run: + * atest CorePerfTests:android.view.HandwritingInitiatorPerfTest + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class HandwritingInitiatorPerfTest { + private Context mContext; + private HandwritingInitiator mHandwritingInitiator; + private int mTouchSlop; + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Before + public void setup() { + final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = mInstrumentation.getTargetContext(); + ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext); + mTouchSlop = viewConfiguration.getScaledTouchSlop(); + InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class); + mHandwritingInitiator = new HandwritingInitiator(viewConfiguration, inputMethodManager); + } + + @Test + public void onTouchEvent_actionDown_toolTypeStylus() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final MotionEvent downEvent = + createMotionEvent(ACTION_DOWN, TOOL_TYPE_STYLUS, 10, 10, 0); + final MotionEvent upEvent = + createMotionEvent(ACTION_UP, TOOL_TYPE_STYLUS, 11, 11, 1); + + while (state.keepRunning()) { + mHandwritingInitiator.onTouchEvent(downEvent); + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(upEvent); + state.resumeTiming(); + } + } + + @Test + public void onTouchEvent_actionUp_toolTypeStylus() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final MotionEvent downEvent = + createMotionEvent(ACTION_DOWN, TOOL_TYPE_STYLUS, 10, 10, 0); + final MotionEvent upEvent = + createMotionEvent(ACTION_UP, TOOL_TYPE_STYLUS, 11, 11, 1); + + while (state.keepRunning()) { + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(downEvent); + state.resumeTiming(); + mHandwritingInitiator.onTouchEvent(upEvent); + } + } + + @Test + public void onTouchEvent_actionMove_toolTypeStylus() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final int initX = 10; + final int initY = 10; + final MotionEvent downEvent = + createMotionEvent(ACTION_DOWN, TOOL_TYPE_STYLUS, initX, initY, 0); + + final int x = initX + mTouchSlop; + final int y = initY + mTouchSlop; + final MotionEvent moveEvent = + createMotionEvent(ACTION_MOVE, TOOL_TYPE_STYLUS, x, y, 1); + final MotionEvent upEvent = + createMotionEvent(ACTION_UP, TOOL_TYPE_STYLUS, x, y, 1); + + while (state.keepRunning()) { + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(downEvent); + state.resumeTiming(); + + mHandwritingInitiator.onTouchEvent(moveEvent); + + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(upEvent); + state.resumeTiming(); + } + } + + @Test + public void onTouchEvent_actionDown_toolTypeFinger() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final MotionEvent downEvent = + createMotionEvent(ACTION_DOWN, TOOL_TYPE_FINGER, 10, 10, 0); + final MotionEvent upEvent = + createMotionEvent(ACTION_UP, TOOL_TYPE_FINGER, 11, 11, 1); + + while (state.keepRunning()) { + mHandwritingInitiator.onTouchEvent(downEvent); + mHandwritingInitiator.onTouchEvent(upEvent); + } + } + + @Test + public void onTouchEvent_actionUp_toolTypeFinger() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final MotionEvent downEvent = + createMotionEvent(ACTION_DOWN, TOOL_TYPE_FINGER, 10, 10, 0); + final MotionEvent upEvent = + createMotionEvent(ACTION_UP, TOOL_TYPE_FINGER, 11, 11, 1); + + while (state.keepRunning()) { + mHandwritingInitiator.onTouchEvent(downEvent); + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(upEvent); + state.resumeTiming(); + } + } + + @Test + public void onTouchEvent_actionMove_toolTypeFinger() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final int initX = 10; + final int initY = 10; + final MotionEvent downEvent = + createMotionEvent(ACTION_DOWN, TOOL_TYPE_FINGER, initX, initY, 0); + + final int x = initX + mTouchSlop; + final int y = initY + mTouchSlop; + final MotionEvent moveEvent = + createMotionEvent(ACTION_MOVE, TOOL_TYPE_FINGER, x, y, 1); + final MotionEvent upEvent = + createMotionEvent(ACTION_UP, TOOL_TYPE_FINGER, x, y, 1); + + while (state.keepRunning()) { + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(downEvent); + state.resumeTiming(); + + mHandwritingInitiator.onTouchEvent(moveEvent); + + state.pauseTiming(); + mHandwritingInitiator.onTouchEvent(upEvent); + state.resumeTiming(); + } + } + + @Test + public void onInputConnectionCreated() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final View view = new View(mContext); + final EditorInfo editorInfo = new EditorInfo(); + while (state.keepRunning()) { + mHandwritingInitiator.onInputConnectionCreated(view, editorInfo); + state.pauseTiming(); + mHandwritingInitiator.onInputConnectionClosed(view); + state.resumeTiming(); + } + } + + @Test + public void onInputConnectionClosed() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final View view = new View(mContext); + final EditorInfo editorInfo = new EditorInfo(); + while (state.keepRunning()) { + state.pauseTiming(); + mHandwritingInitiator.onInputConnectionCreated(view, editorInfo); + state.resumeTiming(); + mHandwritingInitiator.onInputConnectionClosed(view); + } + } + + @Test + public void updateEditorBoundary() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final Rect rect = new Rect(0, 0, 100, 100); + while (state.keepRunning()) { + mHandwritingInitiator.updateEditorBound(rect); + } + } + + private MotionEvent createMotionEvent(int action, int toolType, int x, int y, long eventTime) { + MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); + properties[0].toolType = toolType; + + MotionEvent.PointerCoords[] coords = MotionEvent.PointerCoords.createArray(1); + coords[0].x = x; + coords[0].y = y; + + return MotionEvent.obtain(0 /* downTime */, eventTime /* eventTime */, action, 1, + properties, coords, 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, + 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, + InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */); + } +} diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 05a0661914dc..1f4a64f5c7f1 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -81,18 +81,18 @@ static constexpr const char* PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE = "/product static constexpr const char* OEM_USERSPACE_REBOOT_ANIMATION_FILE = "/oem/media/userspace-reboot.zip"; static constexpr const char* SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE = "/system/media/userspace-reboot.zip"; -static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; -static const char SYSTEM_TIME_DIR_NAME[] = "time"; -static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time"; +static const char BOOTANIM_DATA_DIR_PATH[] = "/data/bootanim"; +static const char BOOTANIM_TIME_DIR_NAME[] = "time"; +static const char BOOTANIM_TIME_DIR_PATH[] = "/data/bootanim/time"; static const char CLOCK_FONT_ASSET[] = "images/clock_font.png"; static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png"; static const char PROGRESS_FONT_ASSET[] = "images/progress_font.png"; static const char PROGRESS_FONT_ZIP_NAME[] = "progress_font.png"; static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change"; -static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change"; +static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/bootanim/time/last_time_change"; static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate"; -static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate"; -static const char TIME_FORMAT_12_HOUR_FLAG_FILE_PATH[] = "/data/system/time/time_format_12_hour"; +static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/bootanim/time/time_is_accurate"; +static const char TIME_FORMAT_12_HOUR_FLAG_FILE_PATH[] = "/data/bootanim/time/time_format_12_hour"; // Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00. static const long long ACCURATE_TIME_EPOCH = 946684800000; static constexpr char FONT_BEGIN_CHAR = ' '; @@ -1741,7 +1741,7 @@ bool BootAnimation::updateIsTimeAccurate() { } BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false), - mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {} + mInotifyFd(-1), mBootAnimWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {} BootAnimation::TimeCheckThread::~TimeCheckThread() { // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD. @@ -1784,7 +1784,7 @@ bool BootAnimation::TimeCheckThread::doThreadLoop() { const struct inotify_event *event; for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; - if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) { + if (event->wd == mBootAnimWd && strcmp(BOOTANIM_TIME_DIR_NAME, event->name) == 0) { addTimeDirWatch(); } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0 || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) { @@ -1796,12 +1796,12 @@ bool BootAnimation::TimeCheckThread::doThreadLoop() { } void BootAnimation::TimeCheckThread::addTimeDirWatch() { - mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH, + mTimeWd = inotify_add_watch(mInotifyFd, BOOTANIM_TIME_DIR_PATH, IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB); if (mTimeWd > 0) { // No need to watch for the time directory to be created if it already exists - inotify_rm_watch(mInotifyFd, mSystemWd); - mSystemWd = -1; + inotify_rm_watch(mInotifyFd, mBootAnimWd); + mBootAnimWd = -1; } } @@ -1812,11 +1812,11 @@ status_t BootAnimation::TimeCheckThread::readyToRun() { return NO_INIT; } - mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB); - if (mSystemWd < 0) { + mBootAnimWd = inotify_add_watch(mInotifyFd, BOOTANIM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB); + if (mBootAnimWd < 0) { close(mInotifyFd); mInotifyFd = -1; - SLOGE("Could not add watch for %s: %s", SYSTEM_DATA_DIR_PATH, strerror(errno)); + SLOGE("Could not add watch for %s: %s", BOOTANIM_DATA_DIR_PATH, strerror(errno)); return NO_INIT; } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 7a597da533ee..4c378cbc48bd 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -161,7 +161,7 @@ private: void addTimeDirWatch(); int mInotifyFd; - int mSystemWd; + int mBootAnimWd; int mTimeWd; BootAnimation* mBootAnimation; }; diff --git a/core/api/current.txt b/core/api/current.txt index b983617445e5..edf69449f4e9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -2258,6 +2258,7 @@ package android { field public static final int TextAppearance = 16973886; // 0x103003e field public static final int TextAppearance_DeviceDefault = 16974253; // 0x10301ad field public static final int TextAppearance_DeviceDefault_DialogWindowTitle = 16974264; // 0x10301b8 + field public static final int TextAppearance_DeviceDefault_Headline; field public static final int TextAppearance_DeviceDefault_Inverse = 16974254; // 0x10301ae field public static final int TextAppearance_DeviceDefault_Large = 16974255; // 0x10301af field public static final int TextAppearance_DeviceDefault_Large_Inverse = 16974256; // 0x10301b0 @@ -3130,11 +3131,13 @@ package android.accessibilityservice { method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, @Nullable android.os.Handler); method public float getCenterX(); method public float getCenterY(); + method @NonNull public android.graphics.Region getCurrentMagnificationRegion(); method @Nullable public android.accessibilityservice.MagnificationConfig getMagnificationConfig(); method @NonNull public android.graphics.Region getMagnificationRegion(); method public float getScale(); method public boolean removeListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener); method public boolean reset(boolean); + method public boolean resetCurrentMagnification(boolean); method public boolean setCenter(float, float, boolean); method public boolean setMagnificationConfig(@NonNull android.accessibilityservice.MagnificationConfig, boolean); method public boolean setScale(float, boolean); @@ -3142,6 +3145,7 @@ package android.accessibilityservice { public static interface AccessibilityService.MagnificationController.OnMagnificationChangedListener { method public void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, float, float, float); + method public default void onMagnificationChanged(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController, @NonNull android.graphics.Region, @NonNull android.accessibilityservice.MagnificationConfig); } public static final class AccessibilityService.ScreenshotResult { @@ -9158,6 +9162,7 @@ package android.bluetooth { field public static final int AUDIO = 2097152; // 0x200000 field public static final int CAPTURE = 524288; // 0x80000 field public static final int INFORMATION = 8388608; // 0x800000 + field public static final int LE_AUDIO = 16384; // 0x4000 field public static final int LIMITED_DISCOVERABILITY = 8192; // 0x2000 field public static final int NETWORKING = 131072; // 0x20000 field public static final int OBJECT_TRANSFER = 1048576; // 0x100000 @@ -15972,6 +15977,8 @@ package android.graphics { method public String getFontFeatureSettings(); method public float getFontMetrics(android.graphics.Paint.FontMetrics); method public android.graphics.Paint.FontMetrics getFontMetrics(); + method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); + method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt); method public android.graphics.Paint.FontMetricsInt getFontMetricsInt(); method public float getFontSpacing(); @@ -34292,6 +34299,7 @@ package android.provider { field public static final int PRESENTATION_ALLOWED = 1; // 0x1 field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 + field public static final int PRESENTATION_UNAVAILABLE = 5; // 0x5 field public static final int PRESENTATION_UNKNOWN = 3; // 0x3 field public static final String PRIORITY = "priority"; field public static final int PRIORITY_NORMAL = 0; // 0x0 @@ -41065,6 +41073,7 @@ package android.telecom { field public static final int PRESENTATION_ALLOWED = 1; // 0x1 field public static final int PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int PRESENTATION_RESTRICTED = 2; // 0x2 + field public static final int PRESENTATION_UNAVAILABLE = 5; // 0x5 field public static final int PRESENTATION_UNKNOWN = 3; // 0x3 field public static final int PRIORITY_NORMAL = 0; // 0x0 field public static final int PRIORITY_URGENT = 1; // 0x1 @@ -41695,6 +41704,7 @@ package android.telephony { field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array"; field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array"; field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int"; + field public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.enable_support_for_eap_aka_fast_reauth_bool"; field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array"; field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int"; field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int"; @@ -44646,6 +44656,7 @@ package android.text { public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback { ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); + ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public void ellipsized(int, int); method public int getBottomPadding(); method public int getEllipsisCount(int); @@ -44660,9 +44671,12 @@ package android.text { method public int getTopPadding(); method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint); method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics); + method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); + method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean); + method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @NonNull android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean); method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int); } @@ -44871,6 +44885,7 @@ package android.text { method public abstract int getTopPadding(); method public final int getWidth(); method public final void increaseWidthTo(int); + method public boolean isFallbackLineSpacingEnabled(); method public boolean isRtlCharAt(int); method protected final boolean isSpanned(); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 30154c8f4a73..e26d3c419039 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9530,7 +9530,7 @@ package android.os.storage { public final class StorageVolume implements android.os.Parcelable { method @NonNull public String getId(); - method public boolean isStub(); + method public boolean isExternallyManaged(); } } @@ -11865,11 +11865,18 @@ package android.telephony { method public int getCallDuration(); method public int getCodecType(); method public int getDownlinkCallQualityLevel(); + method public long getMaxPlayoutDelayMillis(); method public int getMaxRelativeJitter(); + method public long getMinPlayoutDelayMillis(); + method public int getNumDroppedRtpPackets(); + method public int getNumNoDataFrames(); + method public int getNumRtpDuplicatePackets(); method public int getNumRtpPacketsNotReceived(); method public int getNumRtpPacketsReceived(); method public int getNumRtpPacketsTransmitted(); method public int getNumRtpPacketsTransmittedLost(); + method public int getNumRtpSidPacketsRx(); + method public int getNumVoiceFrames(); method public int getUplinkCallQualityLevel(); method public boolean isIncomingSilenceDetectedAtCallSetup(); method public boolean isOutgoingSilenceDetectedAtCallSetup(); @@ -11884,6 +11891,32 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR; } + public static final class CallQuality.Builder { + ctor public CallQuality.Builder(); + method @NonNull public android.telephony.CallQuality build(); + method @NonNull public android.telephony.CallQuality.Builder setAverageRelativeJitter(int); + method @NonNull public android.telephony.CallQuality.Builder setAverageRoundTripTime(int); + method @NonNull public android.telephony.CallQuality.Builder setCallDuration(int); + method @NonNull public android.telephony.CallQuality.Builder setCodecType(int); + method @NonNull public android.telephony.CallQuality.Builder setDownlinkCallQualityLevel(int); + method @NonNull public android.telephony.CallQuality.Builder setIncomingSilenceDetectedAtCallSetup(boolean); + method @NonNull public android.telephony.CallQuality.Builder setMaxPlayoutDelayMillis(long); + method @NonNull public android.telephony.CallQuality.Builder setMaxRelativeJitter(int); + method @NonNull public android.telephony.CallQuality.Builder setMinPlayoutDelayMillis(long); + method @NonNull public android.telephony.CallQuality.Builder setNumDroppedRtpPackets(int); + method @NonNull public android.telephony.CallQuality.Builder setNumNoDataFrames(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpDuplicatePackets(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsNotReceived(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsReceived(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsTransmitted(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpPacketsTransmittedLost(int); + method @NonNull public android.telephony.CallQuality.Builder setNumRtpSidPacketsRx(int); + method @NonNull public android.telephony.CallQuality.Builder setNumVoiceFrames(int); + method @NonNull public android.telephony.CallQuality.Builder setOutgoingSilenceDetectedAtCallSetup(boolean); + method @NonNull public android.telephony.CallQuality.Builder setRtpInactivityDetected(boolean); + method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int); + } + public class CarrierConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName(); method @NonNull public static android.os.PersistableBundle getDefaultConfig(); @@ -13785,6 +13818,7 @@ package android.telephony.ims { field public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2; // 0x2 field public static final int OIR_PRESENTATION_PAYPHONE = 4; // 0x4 field public static final int OIR_PRESENTATION_RESTRICTED = 1; // 0x1 + field public static final int OIR_PRESENTATION_UNAVAILABLE = 5; // 0x5 field public static final int OIR_PRESENTATION_UNKNOWN = 3; // 0x3 field public static final int PRIORITY_NORMAL = 0; // 0x0 field public static final int PRIORITY_URGENT = 1; // 0x1 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8724b5363985..0710781a3155 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1679,6 +1679,10 @@ package android.os { method public void removeSyncBarrier(int); } + public final class NewUserResponse { + ctor public NewUserResponse(@Nullable android.os.UserHandle, int); + } + public final class PackageTagsList implements android.os.Parcelable { method public boolean contains(@NonNull String, @Nullable String); method public boolean contains(@NonNull android.os.PackageTagsList); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 9a5586747d0e..479e6bf594ba 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -588,7 +588,7 @@ public abstract class AccessibilityService extends Service { boolean onKeyEvent(KeyEvent event); /** Magnification changed callbacks for different displays */ void onMagnificationChanged(int displayId, @NonNull Region region, - float scale, float centerX, float centerY); + MagnificationConfig config); /** Callbacks for receiving motion events. */ void onMotionEvent(MotionEvent event); /** Callback for tuch state changes. */ @@ -1183,14 +1183,14 @@ public abstract class AccessibilityService extends Service { } } - private void onMagnificationChanged(int displayId, @NonNull Region region, float scale, - float centerX, float centerY) { + private void onMagnificationChanged(int displayId, @NonNull Region region, + MagnificationConfig config) { MagnificationController controller; synchronized (mLock) { controller = mMagnificationControllers.get(displayId); } if (controller != null) { - controller.dispatchMagnificationChanged(region, scale, centerX, centerY); + controller.dispatchMagnificationChanged(region, config); } } @@ -1328,8 +1328,8 @@ public abstract class AccessibilityService extends Service { * Dispatches magnification changes to any registered listeners. This * should be called on the service's main thread. */ - void dispatchMagnificationChanged(final @NonNull Region region, final float scale, - final float centerX, final float centerY) { + void dispatchMagnificationChanged(final @NonNull Region region, + final MagnificationConfig config) { final ArrayMap<OnMagnificationChangedListener, Handler> entries; synchronized (mLock) { if (mListeners == null || mListeners.isEmpty()) { @@ -1348,16 +1348,13 @@ public abstract class AccessibilityService extends Service { final OnMagnificationChangedListener listener = entries.keyAt(i); final Handler handler = entries.valueAt(i); if (handler != null) { - handler.post(new Runnable() { - @Override - public void run() { - listener.onMagnificationChanged(MagnificationController.this, - region, scale, centerX, centerY); - } + handler.post(() -> { + listener.onMagnificationChanged(MagnificationController.this, + region, config); }); } else { // We're already on the main thread, just run the listener. - listener.onMagnificationChanged(this, region, scale, centerX, centerY); + listener.onMagnificationChanged(this, region, config); } } } @@ -1503,6 +1500,12 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will * return an empty region. + * </p> + * <p> + * <strong>Note:</strong> This legacy API gets the magnification region of full-screen + * magnification. To get the magnification region of the current controlling magnifier, + * use {@link #getCurrentMagnificationRegion()} instead. + * </p> * * @return the region of the screen currently active for magnification, or an empty region * if magnification is not active. @@ -1524,6 +1527,45 @@ public abstract class AccessibilityService extends Service { } /** + * Returns the region of the screen currently active for magnification if the + * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}. + * Returns the region of screen projected on the magnification window if the + * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}. + * + * <p> + * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}, + * the returned region will be empty if the magnification is + * not active. And the magnification is active if magnification gestures are enabled + * or if a service is running that can control magnification. + * </p><p> + * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}, + * the returned region will be empty if the magnification is not activated. + * </p><p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will + * return an empty region. + * </p> + * + * @return the magnification region of the currently controlling magnification + */ + @NonNull + public Region getCurrentMagnificationRegion() { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance(mService).getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.getCurrentMagnificationRegion(mDisplayId); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to obtain the current magnified region", re); + re.rethrowFromSystemServer(); + } + } + return Region.obtain(); + } + + /** * Resets magnification scale and center to their default (e.g. no * magnification) values. * <p> @@ -1531,6 +1573,11 @@ public abstract class AccessibilityService extends Service { * {@link AccessibilityService#onServiceConnected()} has not yet been * called) or the service has been disconnected, this method will have * no effect and return {@code false}. + * <p> + * <strong>Note:</strong> This legacy API reset full-screen magnification. + * To reset the current controlling magnifier, use + * {@link #resetCurrentMagnification(boolean)} ()} instead. + * </p> * * @param animate {@code true} to animate from the current scale and * center or {@code false} to reset the scale and center @@ -1553,6 +1600,36 @@ public abstract class AccessibilityService extends Service { } /** + * Resets magnification scale and center of the controlling magnification + * to their default (e.g. no magnification) values. + * <p> + * <strong>Note:</strong> If the service is not yet connected (e.g. + * {@link AccessibilityService#onServiceConnected()} has not yet been + * called) or the service has been disconnected, this method will have + * no effect and return {@code false}. + * </p> + * + * @param animate {@code true} to animate from the current scale and + * center or {@code false} to reset the scale and center + * immediately + * @return {@code true} on success, {@code false} on failure + */ + public boolean resetCurrentMagnification(boolean animate) { + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance(mService).getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.resetCurrentMagnification(mDisplayId, animate); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to reset", re); + re.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Sets the {@link MagnificationConfig}. The service controls the magnification by * setting the config. * <p> @@ -1665,6 +1742,10 @@ public abstract class AccessibilityService extends Service { public interface OnMagnificationChangedListener { /** * Called when the magnified region, scale, or center changes. + * <p> + * <strong>Note:</strong> This legacy callback notifies only full-screen + * magnification change. + * </p> * * @param controller the magnification controller * @param region the magnification region @@ -1676,6 +1757,38 @@ public abstract class AccessibilityService extends Service { */ void onMagnificationChanged(@NonNull MagnificationController controller, @NonNull Region region, float scale, float centerX, float centerY); + + /** + * Called when the magnified region, mode, scale, or center changes of + * all magnification modes. + * <p> + * <strong>Note:</strong> This method can be overridden to listen to the + * magnification changes of all magnification modes then the legacy callback + * would not receive the notifications. + * Skipping calling super when overriding this method results in + * {@link #onMagnificationChanged(MagnificationController, Region, float, float, float)} + * not getting called. + * </p> + * + * @param controller the magnification controller + * @param region the magnification region + * If the config mode is + * {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}, + * it is the region of the screen currently active for magnification. + * that is the same region as {@link #getMagnificationRegion()}. + * If the config mode is + * {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}, + * it is the region of screen projected on the magnification window. + * @param config The magnification config. That has the controlling magnification + * mode, the new scale and the new screen-relative center position + */ + default void onMagnificationChanged(@NonNull MagnificationController controller, + @NonNull Region region, @NonNull MagnificationConfig config) { + if (config.getMode() == MAGNIFICATION_MODE_FULLSCREEN) { + onMagnificationChanged(controller, region, + config.getScale(), config.getCenterX(), config.getCenterY()); + } + } } } @@ -2449,9 +2562,8 @@ public abstract class AccessibilityService extends Service { @Override public void onMagnificationChanged(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { - AccessibilityService.this.onMagnificationChanged(displayId, region, scale, - centerX, centerY); + MagnificationConfig config) { + AccessibilityService.this.onMagnificationChanged(displayId, region, config); } @Override @@ -2575,12 +2687,10 @@ public abstract class AccessibilityService extends Service { /** Magnification changed callbacks for different displays */ public void onMagnificationChanged(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + MagnificationConfig config) { final SomeArgs args = SomeArgs.obtain(); args.arg1 = region; - args.arg2 = scale; - args.arg3 = centerX; - args.arg4 = centerY; + args.arg2 = config; args.argi1 = displayId; final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args); @@ -2739,13 +2849,10 @@ public abstract class AccessibilityService extends Service { if (mConnectionId != AccessibilityInteractionClient.NO_ID) { final SomeArgs args = (SomeArgs) message.obj; final Region region = (Region) args.arg1; - final float scale = (float) args.arg2; - final float centerX = (float) args.arg3; - final float centerY = (float) args.arg4; + final MagnificationConfig config = (MagnificationConfig) args.arg2; final int displayId = args.argi1; args.recycle(); - mCallback.onMagnificationChanged(displayId, region, scale, - centerX, centerY); + mCallback.onMagnificationChanged(displayId, region, config); } return; } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 651c50f475c6..375383d5d858 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -21,6 +21,7 @@ import android.graphics.Region; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityWindowInfo; import android.accessibilityservice.AccessibilityGestureEvent; +import android.accessibilityservice.MagnificationConfig; import android.view.KeyEvent; import android.view.MotionEvent; @@ -43,7 +44,7 @@ import android.view.MotionEvent; void onKeyEvent(in KeyEvent event, int sequence); - void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY); + void onMagnificationChanged(int displayId, in Region region, in MagnificationConfig config); void onMotionEvent(in MotionEvent event); diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 7d76bbf35081..2cc15b40106b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -88,8 +88,12 @@ interface IAccessibilityServiceConnection { Region getMagnificationRegion(int displayId); + Region getCurrentMagnificationRegion(int displayId); + boolean resetMagnification(int displayId, boolean animate); + boolean resetCurrentMagnification(int displayId, boolean animate); + boolean setMagnificationConfig(int displayId, in MagnificationConfig config, boolean animate); void setMagnificationCallbackEnabled(int displayId, boolean enabled); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 2bbf280277ff..fa9de6e27282 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -384,7 +384,7 @@ public class AccountManager { new PropertyInvalidatedCache<UserIdPackage, Account[]>( CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) { @Override - protected Account[] recompute(UserIdPackage userAndPackage) { + public Account[] recompute(UserIdPackage userAndPackage) { try { return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName); } catch (RemoteException e) { @@ -392,11 +392,11 @@ public class AccountManager { } } @Override - protected boolean bypass(UserIdPackage query) { + public boolean bypass(UserIdPackage query) { return query.userId < 0; } @Override - protected boolean debugCompareQueryResults(Account[] l, Account[] r) { + public boolean resultEquals(Account[] l, Account[] r) { if (l == r) { return true; } else if (l == null || r == null) { @@ -455,7 +455,7 @@ public class AccountManager { new PropertyInvalidatedCache<AccountKeyData, String>(CACHE_USER_DATA_SIZE, CACHE_KEY_USER_DATA_PROPERTY) { @Override - protected String recompute(AccountKeyData accountKeyData) { + public String recompute(AccountKeyData accountKeyData) { Account account = accountKeyData.account; String key = accountKeyData.key; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1778ea4a32e2..b2c9439d5760 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4645,12 +4645,14 @@ public final class ActivityThread extends ClientTransactionHandler } private void handleDumpGfxInfo(DumpComponentInfo info) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { ThreadedRenderer.handleDumpGfxInfo(info.fd.getFileDescriptor(), info.args); } catch (Exception e) { Log.w(TAG, "Caught exception from dumpGfxInfo()", e); } finally { IoUtils.closeQuietly(info.fd); + StrictMode.setThreadPolicy(oldPolicy); } } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 44fb5db02f5d..49c75c49b2d7 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -802,7 +802,7 @@ public class ApplicationPackageManager extends PackageManager { new PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>( 256, "cache_key.has_system_feature") { @Override - protected Boolean recompute(HasSystemFeatureQuery query) { + public Boolean recompute(HasSystemFeatureQuery query) { try { return ActivityThread.currentActivityThread().getPackageManager(). hasSystemFeature(query.name, query.version); @@ -1098,7 +1098,7 @@ public class ApplicationPackageManager extends PackageManager { new PropertyInvalidatedCache<Integer, GetPackagesForUidResult>( 32, CACHE_KEY_PACKAGES_FOR_UID_PROPERTY) { @Override - protected GetPackagesForUidResult recompute(Integer uid) { + public GetPackagesForUidResult recompute(Integer uid) { try { return new GetPackagesForUidResult( ActivityThread.currentActivityThread(). diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c895636ddc82..f3e9f105500e 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1796,7 +1796,6 @@ class ContextImpl extends Context { && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) { flags = flags | Context.RECEIVER_EXPORTED; } - final Intent intent = ActivityManager.getService().registerReceiverWithFeature( mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId, @@ -1811,9 +1810,6 @@ class ContextImpl extends Context { return intent; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } catch (WtfException e) { - Log.wtf(TAG, e.getMessage()); - return null; } } diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index ef4d7b1f42e2..978160c1c243 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -505,13 +505,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * block. If this function returns null, the result of the cache query is null. There is no * "negative cache" in the query: we don't cache null results at all. */ - protected abstract Result recompute(Query query); + public abstract Result recompute(Query query); /** * Return true if the query should bypass the cache. The default behavior is to * always use the cache but the method can be overridden for a specific class. */ - protected boolean bypass(Query query) { + public boolean bypass(Query query) { return false; } @@ -519,7 +519,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Determines if a pair of responses are considered equal. Used to determine whether * a cache is inadvertently returning stale results when VERIFY is set to true. */ - protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) { + protected boolean resultEquals(Result cachedResult, Result fetchedResult) { // If a service crashes and returns a null result, the cached value remains valid. if (fetchedResult != null) { return Objects.equals(cachedResult, fetchedResult); @@ -990,11 +990,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } - protected Result maybeCheckConsistency(Query query, Result proposedResult) { + private Result maybeCheckConsistency(Query query, Result proposedResult) { if (VERIFY) { Result resultToCompare = recompute(query); boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce); - if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) { + if (!nonceChanged && !resultEquals(proposedResult, resultToCompare)) { Log.e(TAG, TextUtils.formatSimple( "cache %s inconsistent for %s is %s should be %s", cacheName(), queryToString(query), diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 58ded716cf40..00903a880834 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -22,6 +22,7 @@ import android.accessibilityservice.AccessibilityService.IAccessibilityServiceCl import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.accessibilityservice.MagnificationConfig; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1581,7 +1582,7 @@ public final class UiAutomation { @Override public void onMagnificationChanged(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + MagnificationConfig config) { /* do nothing */ } diff --git a/core/java/android/app/WtfException.java b/core/java/android/app/WtfException.java deleted file mode 100644 index ba8dbefad160..000000000000 --- a/core/java/android/app/WtfException.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2021 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.app; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Exception meant to be thrown instead of calling Log.wtf() such that server side code can - * throw this exception, and it will carry across the binder to do client side logging. - * {@hide} - */ -public final class WtfException extends RuntimeException implements Parcelable { - public static final @android.annotation.NonNull - Creator<WtfException> CREATOR = new Creator<WtfException>() { - @Override - public WtfException createFromParcel(Parcel source) { - return new WtfException(source.readString8()); - } - - @Override - public WtfException[] newArray(int size) { - return new WtfException[size]; - } - }; - - public WtfException(@android.annotation.NonNull String message) { - super(message); - } - - /** {@hide} */ - public static Throwable readFromParcel(Parcel in) { - final String msg = in.readString8(); - return new WtfException(msg); - } - - /** {@hide} */ - public static void writeToParcel(Parcel out, Throwable t) { - out.writeString8(t.getMessage()); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) { - dest.writeString8(getMessage()); - } -} - diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java index 3d0bf91bf8d7..acd404b7d475 100644 --- a/core/java/android/app/compat/ChangeIdStateCache.java +++ b/core/java/android/app/compat/ChangeIdStateCache.java @@ -84,7 +84,7 @@ public final class ChangeIdStateCache } @Override - protected Boolean recompute(ChangeIdStateQuery query) { + public Boolean recompute(ChangeIdStateQuery query) { final long token = Binder.clearCallingIdentity(); try { if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 9a0f02e8b2b6..856a8e1f8199 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1062,7 +1062,7 @@ public final class BluetoothAdapter { 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { @Override @SuppressLint("AndroidFrameworkRequiresPermission") - protected Integer recompute(Void query) { + public Integer recompute(Void query) { try { return mService.getState(); } catch (RemoteException e) { @@ -2085,7 +2085,7 @@ public final class BluetoothAdapter { 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { @Override @SuppressLint("AndroidFrameworkRequiresPermission") - protected Boolean recompute(Void query) { + public Boolean recompute(Void query) { try { mServiceLock.readLock().lock(); if (mService != null) { @@ -2540,7 +2540,7 @@ public final class BluetoothAdapter { */ @Override @SuppressLint("AndroidFrameworkRequiresPermission") - protected Integer recompute(Void query) { + public Integer recompute(Void query) { try { return mService.getAdapterConnectionState(); } catch (RemoteException e) { @@ -2605,7 +2605,7 @@ public final class BluetoothAdapter { 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { @Override @SuppressLint("AndroidFrameworkRequiresPermission") - protected Integer recompute(Integer query) { + public Integer recompute(Integer query) { try { mServiceLock.readLock().lock(); if (mService != null) { @@ -3064,9 +3064,6 @@ public final class BluetoothAdapter { BluetoothCsipSetCoordinator csipSetCoordinator = new BluetoothCsipSetCoordinator(context, listener, this); return true; - } else if (profile == BluetoothProfile.LE_CALL_CONTROL) { - BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener); - return true; } else { return false; } @@ -3169,10 +3166,6 @@ public final class BluetoothAdapter { (BluetoothCsipSetCoordinator) proxy; csipSetCoordinator.close(); break; - case BluetoothProfile.LE_CALL_CONTROL: - BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy; - tbs.close(); - break; } } diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index 69525b543478..a3c45d0276ca 100755 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -120,6 +120,7 @@ public final class BluetoothClass implements Parcelable { private static final int BITMASK = 0xFFE000; public static final int LIMITED_DISCOVERABILITY = 0x002000; + public static final int LE_AUDIO = 0x004000; public static final int POSITIONING = 0x010000; public static final int NETWORKING = 0x020000; public static final int RENDER = 0x040000; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 93f026860856..fc99942cb784 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1604,7 +1604,7 @@ public final class BluetoothDevice implements Parcelable, Attributable { 8, BLUETOOTH_BONDING_CACHE_PROPERTY) { @Override @SuppressLint("AndroidFrameworkRequiresPermission") - protected Integer recompute(BluetoothDevice query) { + public Integer recompute(BluetoothDevice query) { try { return sService.getBondState(query, mAttributionSource); } catch (RemoteException e) { diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java b/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java new file mode 100644 index 000000000000..b866cce22470 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java @@ -0,0 +1,140 @@ +/* + * Copyright 2021 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.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.bluetooth.le.ScanResult; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class provides a set of callbacks that are invoked when scanning for Broadcast Sources is + * offloaded to a Broadcast Assistant. + * + * <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast + * Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. This + * is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT server that is + * part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on the Broadcast + * Assistant. + * + * <p>Once a GATT connection is established between the BASS client and the BASS server, the + * Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast + * Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the + * established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the + * Assistant about changes such as addition and removal of Broadcast Sources. + * + * @hide + */ +public abstract class BluetoothLeBroadcastAssistantCallback { + + /** + * Broadcast Audio Scan Service (BASS) codes returned by a BASS Server + * + * @hide + */ + @IntDef( + prefix = "BASS_STATUS_", + value = { + BASS_STATUS_SUCCESS, + BASS_STATUS_FAILURE, + BASS_STATUS_INVALID_GATT_HANDLE, + BASS_STATUS_TXN_TIMEOUT, + BASS_STATUS_INVALID_SOURCE_ID, + BASS_STATUS_COLOCATED_SRC_UNAVAILABLE, + BASS_STATUS_INVALID_SOURCE_SELECTED, + BASS_STATUS_SOURCE_UNAVAILABLE, + BASS_STATUS_DUPLICATE_ADDITION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BassStatus {} + + public static final int BASS_STATUS_SUCCESS = 0x00; + public static final int BASS_STATUS_FAILURE = 0x01; + public static final int BASS_STATUS_INVALID_GATT_HANDLE = 0x02; + public static final int BASS_STATUS_TXN_TIMEOUT = 0x03; + + public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04; + public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05; + public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06; + public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07; + public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08; + public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09; + public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10; + + /** + * Callback invoked when a new LE Audio Broadcast Source is found. + * + * @param result {@link ScanResult} scan result representing a Broadcast Source + */ + public void onBluetoothLeBroadcastSourceFound(@NonNull ScanResult result) {} + + /** + * Callback invoked when the Broadcast Assistant synchronizes with Periodic Advertisements (PAs) + * of an LE Audio Broadcast Source. + * + * @param source the selected Broadcast Source + */ + public void onBluetoothLeBroadcastSourceSelected( + @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} + + /** + * Callback invoked when the Broadcast Assistant loses synchronization with an LE Audio + * Broadcast Source. + * + * @param source the Broadcast Source with which synchronization was lost + */ + public void onBluetoothLeBroadcastSourceLost( + @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} + + /** + * Callback invoked when a new LE Audio Broadcast Source has been successfully added to the Scan + * Delegator (within a Broadcast Sink, for example). + * + * @param sink Scan Delegator device on which a new Broadcast Source has been added + * @param source the added Broadcast Source + */ + public void onBluetoothLeBroadcastSourceAdded( + @NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastSourceInfo source, + @BassStatus int status) {} + + /** + * Callback invoked when an existing LE Audio Broadcast Source within a remote Scan Delegator + * has been updated. + * + * @param sink Scan Delegator device on which a Broadcast Source has been updated + * @param source the updated Broadcast Source + */ + public void onBluetoothLeBroadcastSourceUpdated( + @NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastSourceInfo source, + @BassStatus int status) {} + + /** + * Callback invoked when an LE Audio Broadcast Source has been successfully removed from the + * Scan Delegator (within a Broadcast Sink, for example). + * + * @param sink Scan Delegator device from which a Broadcast Source has been removed + * @param source the removed Broadcast Source + */ + public void onBluetoothLeBroadcastSourceRemoved( + @NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastSourceInfo source, + @BassStatus int status) {} +} diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java deleted file mode 100644 index fb7789db25c7..000000000000 --- a/core/java/android/bluetooth/BluetoothLeCall.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.ParcelUuid; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; -import java.util.UUID; - -/** - * Representation of Call - * - * @hide - */ -public final class BluetoothLeCall implements Parcelable { - - /** @hide */ - @IntDef(prefix = "STATE_", value = { - STATE_INCOMING, - STATE_DIALING, - STATE_ALERTING, - STATE_ACTIVE, - STATE_LOCALLY_HELD, - STATE_REMOTELY_HELD, - STATE_LOCALLY_AND_REMOTELY_HELD - }) - @Retention(RetentionPolicy.SOURCE) - public @interface State { - } - - /** - * A remote party is calling (incoming call). - * - * @hide - */ - public static final int STATE_INCOMING = 0x00; - - /** - * The process to call the remote party has started but the remote party is not - * being alerted (outgoing call). - * - * @hide - */ - public static final int STATE_DIALING = 0x01; - - /** - * A remote party is being alerted (outgoing call). - * - * @hide - */ - public static final int STATE_ALERTING = 0x02; - - /** - * The call is in an active conversation. - * - * @hide - */ - public static final int STATE_ACTIVE = 0x03; - - /** - * The call is connected but held locally. “Locally Held” implies that either - * the server or the client can affect the state. - * - * @hide - */ - public static final int STATE_LOCALLY_HELD = 0x04; - - /** - * The call is connected but held remotely. “Remotely Held” means that the state - * is controlled by the remote party of a call. - * - * @hide - */ - public static final int STATE_REMOTELY_HELD = 0x05; - - /** - * The call is connected but held both locally and remotely. - * - * @hide - */ - public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06; - - /** - * Whether the call direction is outgoing. - * - * @hide - */ - public static final int FLAG_OUTGOING_CALL = 0x00000001; - - /** - * Whether the call URI and Friendly Name are withheld by server. - * - * @hide - */ - public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002; - - /** - * Whether the call URI and Friendly Name are withheld by network. - * - * @hide - */ - public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004; - - /** Unique UUID that identifies this call */ - private UUID mUuid; - - /** Remote Caller URI */ - private String mUri; - - /** Caller friendly name */ - private String mFriendlyName; - - /** Call state */ - private @State int mState; - - /** Call flags */ - private int mCallFlags; - - /** @hide */ - public BluetoothLeCall(@NonNull BluetoothLeCall that) { - mUuid = new UUID(that.getUuid().getMostSignificantBits(), - that.getUuid().getLeastSignificantBits()); - mUri = that.mUri; - mFriendlyName = that.mFriendlyName; - mState = that.mState; - mCallFlags = that.mCallFlags; - } - - /** @hide */ - public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName, - @State int state, int callFlags) { - mUuid = uuid; - mUri = uri; - mFriendlyName = friendlyName; - mState = state; - mCallFlags = callFlags; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - BluetoothLeCall that = (BluetoothLeCall) o; - return mUuid.equals(that.mUuid) && mUri.equals(that.mUri) - && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState - && mCallFlags == that.mCallFlags; - } - - @Override - public int hashCode() { - return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags); - } - - /** - * Returns a string representation of this BluetoothLeCall. - * - * <p> - * Currently this is the UUID. - * - * @return string representation of this BluetoothLeCall - */ - @Override - public String toString() { - return mUuid.toString(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeString(mUri); - out.writeString(mFriendlyName); - out.writeInt(mState); - out.writeInt(mCallFlags); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR = - new Parcelable.Creator<BluetoothLeCall>() { - public BluetoothLeCall createFromParcel(Parcel in) { - return new BluetoothLeCall(in); - } - - public BluetoothLeCall[] newArray(int size) { - return new BluetoothLeCall[size]; - } - }; - - private BluetoothLeCall(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); - mUri = in.readString(); - mFriendlyName = in.readString(); - mState = in.readInt(); - mCallFlags = in.readInt(); - } - - /** - * Returns an UUID of this BluetoothLeCall. - * - * <p> - * An UUID is unique identifier of a BluetoothLeCall. - * - * @return UUID of this BluetoothLeCall - * @hide - */ - public @NonNull UUID getUuid() { - return mUuid; - } - - /** - * Returns a URI of the remote party of this BluetoothLeCall. - * - * @return string representation of this BluetoothLeCall - * @hide - */ - public @NonNull String getUri() { - return mUri; - } - - /** - * Returns a friendly name of the call. - * - * @return friendly name representation of this BluetoothLeCall - * @hide - */ - public @NonNull String getFriendlyName() { - return mFriendlyName; - } - - /** - * Returns the call state. - * - * @return the state of this BluetoothLeCall - * @hide - */ - public @State int getState() { - return mState; - } - - /** - * Returns the call flags. - * - * @return call flags - * @hide - */ - public int getCallFlags() { - return mCallFlags; - } - - /** - * Whether the call direction is incoming. - * - * @return true if incoming call, false otherwise - * @hide - */ - public boolean isIncomingCall() { - return (mCallFlags & FLAG_OUTGOING_CALL) == 0; - } -} diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java deleted file mode 100644 index 5283e0804252..000000000000 --- a/core/java/android/bluetooth/BluetoothLeCallControl.java +++ /dev/null @@ -1,911 +0,0 @@ -/* - * Copyright 2019 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import android.Manifest; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.content.ComponentName; -import android.content.Context; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; -import android.annotation.SuppressLint; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Executor; - -/** - * This class provides the APIs to control the Call Control profile. - * - * <p> - * This class provides Bluetooth Telephone Bearer Service functionality, - * allowing applications to expose a GATT Service based interface to control the - * state of the calls by remote devices such as LE audio devices. - * - * <p> - * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the - * BluetoothLeCallControl proxy object. - * - * @hide - */ -public final class BluetoothLeCallControl implements BluetoothProfile { - private static final String TAG = "BluetoothLeCallControl"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** @hide */ - @IntDef(prefix = "RESULT_", value = { - RESULT_SUCCESS, - RESULT_ERROR_UNKNOWN_CALL_ID, - RESULT_ERROR_INVALID_URI, - RESULT_ERROR_APPLICATION - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Result { - } - - /** - * Opcode write was successful. - * - * @hide - */ - public static final int RESULT_SUCCESS = 0; - - /** - * Unknown call Id has been used in the operation. - * - * @hide - */ - public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; - - /** - * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. - * - * @hide - */ - public static final int RESULT_ERROR_INVALID_URI = 2; - - /** - * Application internal error. - * - * @hide - */ - public static final int RESULT_ERROR_APPLICATION = 3; - - /** @hide */ - @IntDef(prefix = "TERMINATION_REASON_", value = { - TERMINATION_REASON_INVALID_URI, - TERMINATION_REASON_FAIL, - TERMINATION_REASON_REMOTE_HANGUP, - TERMINATION_REASON_SERVER_HANGUP, - TERMINATION_REASON_LINE_BUSY, - TERMINATION_REASON_NETWORK_CONGESTION, - TERMINATION_REASON_CLIENT_HANGUP, - TERMINATION_REASON_NO_SERVICE, - TERMINATION_REASON_NO_ANSWER - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TerminationReason { - } - - /** - * Remote Caller ID value used to place a call was formed improperly. - * - * @hide - */ - public static final int TERMINATION_REASON_INVALID_URI = 0x00; - - /** - * Call fail. - * - * @hide - */ - public static final int TERMINATION_REASON_FAIL = 0x01; - - /** - * Remote party ended call. - * - * @hide - */ - public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02; - - /** - * Call ended from the server. - * - * @hide - */ - public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03; - - /** - * Line busy. - * - * @hide - */ - public static final int TERMINATION_REASON_LINE_BUSY = 0x04; - - /** - * Network congestion. - * - * @hide - */ - public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05; - - /** - * Client terminated. - * - * @hide - */ - public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06; - - /** - * No service. - * - * @hide - */ - public static final int TERMINATION_REASON_NO_SERVICE = 0x07; - - /** - * No answer. - * - * @hide - */ - public static final int TERMINATION_REASON_NO_ANSWER = 0x08; - - /* - * Flag indicating support for hold/unhold call feature. - * - * @hide - */ - public static final int CAPABILITY_HOLD_CALL = 0x00000001; - - /** - * Flag indicating support for joining calls feature. - * - * @hide - */ - public static final int CAPABILITY_JOIN_CALLS = 0x00000002; - - private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102; - private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103; - - private static final int REG_TIMEOUT = 10000; - - /** - * The template class is used to call callback functions on events from the TBS - * server. Callback functions are wrapped in this class and registered to the - * Android system during app registration. - * - * @hide - */ - public abstract static class Callback { - - private static final String TAG = "BluetoothLeCallControl.Callback"; - - /** - * Called when a remote client requested to accept the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to be accepted - * @hide - */ - public abstract void onAcceptCall(int requestId, @NonNull UUID callId); - - /** - * A remote client has requested to terminate the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to terminate - * @hide - */ - public abstract void onTerminateCall(int requestId, @NonNull UUID callId); - - /** - * A remote client has requested to hold the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to be put on hold - * @hide - */ - public void onHoldCall(int requestId, @NonNull UUID callId) { - Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); - } - - /** - * A remote client has requested to unhold the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to unhold - * @hide - */ - public void onUnholdCall(int requestId, @NonNull UUID callId) { - Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); - } - - /** - * A remote client has requested to place a call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The Id to be assigned for the new call - * @param uri The caller URI requested - * @hide - */ - public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); - - /** - * A remote client has requested to join the calls. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callIds The call Id list requested to join - * @hide - */ - public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { - Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); - } - } - - private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { - - private final Executor mExecutor; - private final Callback mCallback; - - CallbackWrapper(Executor executor, Callback callback) { - mExecutor = executor; - mCallback = callback; - } - - @Override - public void onBearerRegistered(int ccid) { - synchronized (mServerIfLock) { - if (mCallback != null) { - mCcid = ccid; - mServerIfLock.notifyAll(); - } else { - // registration timeout - Log.e(TAG, "onBearerRegistered: mCallback is null"); - } - } - } - - @Override - public void onAcceptCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onTerminateCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onHoldCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onUnholdCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { - List<UUID> uuids = new ArrayList<>(); - for (ParcelUuid parcelUuid : parcelUuids) { - uuids.add(parcelUuid.getUuid()); - } - - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - }; - - private Context mContext; - private ServiceListener mServiceListener; - private volatile IBluetoothLeCallControl mService; - private BluetoothAdapter mAdapter; - private int mCcid = 0; - private String mToken; - private Callback mCallback = null; - private Object mServerIfLock = new Object(); - - private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = - new IBluetoothStateChangeCallback.Stub() { - public void onBluetoothStateChange(boolean up) { - if (DBG) - Log.d(TAG, "onBluetoothStateChange: up=" + up); - if (!up) { - doUnbind(); - } else { - doBind(); - } - } - }; - - /** - * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth - * telephone bearer service. - */ - /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) { - mContext = context; - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mServiceListener = listener; - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - doBind(); - } - - private boolean doBind() { - synchronized (mConnection) { - if (mService == null) { - if (VDBG) - Log.d(TAG, "Binding service..."); - try { - return mAdapter.getBluetoothManager(). - bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, - mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to bind TelephoneBearerService", e); - } - } - } - return false; - } - - private void doUnbind() { - synchronized (mConnection) { - if (mService != null) { - if (VDBG) - Log.d(TAG, "Unbinding service..."); - try { - mAdapter.getBluetoothManager(). - unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, - mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to unbind TelephoneBearerService", e); - } finally { - mService = null; - } - } - } - } - - /* package */ void close() { - if (VDBG) - log("close()"); - unregisterBearer(); - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - Log.e(TAG, "", re); - } - } - mServiceListener = null; - doUnbind(); - } - - private IBluetoothLeCallControl getService() { - return mService; - } - - /** - * Not supported - * - * @throws UnsupportedOperationException - */ - @Override - public int getConnectionState(@Nullable BluetoothDevice device) { - throw new UnsupportedOperationException("not supported"); - } - - /** - * Not supported - * - * @throws UnsupportedOperationException - */ - @Override - public @NonNull List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException("not supported"); - } - - /** - * Not supported - * - * @throws UnsupportedOperationException - */ - @Override - public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( - @NonNull int[] states) { - throw new UnsupportedOperationException("not supported"); - } - - /** - * Register Telephone Bearer exposing the interface that allows remote devices - * to track and control the call states. - * - * <p> - * This is an asynchronous call. The callback is used to notify success or - * failure if the function returns true. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * <!-- The UCI is a String identifier of the telephone bearer as defined at - * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers - * (login required). --> - * - * <!-- The examples of common URI schemes can be found in - * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> - * - * <!-- The Technology is an integer value. The possible values are defined at - * https://www.bluetooth.com/specifications/assigned-numbers (login required). - * --> - * - * @param uci Bearer Unique Client Identifier - * @param uriSchemes URI Schemes supported list - * @param capabilities bearer capabilities - * @param provider Network provider name - * @param technology Network technology - * @param executor {@link Executor} object on which callback will be - * executed. The Executor object is required. - * @param callback {@link Callback} object to which callback messages will - * be sent. The Callback object is required. - * @return true on success, false otherwise - * @hide - */ - @SuppressLint("ExecutorRegistration") - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean registerBearer(@Nullable String uci, - @NonNull List<String> uriSchemes, int capabilities, - @NonNull String provider, int technology, - @NonNull Executor executor, @NonNull Callback callback) { - if (DBG) { - Log.d(TAG, "registerBearer"); - } - if (callback == null) { - throw new IllegalArgumentException("null parameter: " + callback); - } - if (mCcid != 0) { - return false; - } - - mToken = uci; - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - synchronized (mServerIfLock) { - if (mCallback != null) { - Log.e(TAG, "Bearer can be opened only once"); - return false; - } - - mCallback = callback; - try { - CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); - service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities, - provider, technology); - } catch (RemoteException e) { - Log.e(TAG, "", e); - mCallback = null; - return false; - } - - try { - mServerIfLock.wait(REG_TIMEOUT); - } catch (InterruptedException e) { - Log.e(TAG, "" + e); - mCallback = null; - } - - if (mCcid == 0) { - mCallback = null; - return false; - } - - return true; - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - - return false; - } - - /** - * Unregister Telephone Bearer Service and destroy all the associated data. - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void unregisterBearer() { - if (DBG) { - Log.d(TAG, "unregisterBearer"); - } - if (mCcid == 0) { - return; - } - - int ccid = mCcid; - mCcid = 0; - mCallback = null; - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.unregisterBearer(mToken); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Get the Content Control ID (CCID) value. - * - * @return ccid Content Control ID value - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public int getContentControlId() { - return mCcid; - } - - /** - * Notify about the newly added call. - * - * <p> - * This shall be called as early as possible after the call has been added. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param call Newly added call - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onCallAdded(@NonNull BluetoothLeCall call) { - if (DBG) { - Log.d(TAG, "onCallAdded: call=" + call); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.callAdded(mCcid, call); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Notify about the removed call. - * - * <p> - * This shall be called as early as possible after the call has been removed. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param callId The Id of a call that has been removed - * @param reason Call termination reason - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { - if (DBG) { - Log.d(TAG, "callRemoved: callId=" + callId); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.callRemoved(mCcid, new ParcelUuid(callId), reason); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Notify the call state change - * - * <p> - * This shall be called as early as possible after the state of the call has - * changed. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param callId The call Id that state has been changed - * @param state Call state - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { - if (DBG) { - Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.callStateChanged(mCcid, new ParcelUuid(callId), state); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Provide the current calls list - * - * <p> - * This function must be invoked after registration if application has any - * calls. - * - * @param calls current calls list - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.currentCallsList(mCcid, calls); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - } - - /** - * Provide the network current status - * - * <p> - * This function must be invoked on change of network state. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * <!-- The Technology is an integer value. The possible values are defined at - * https://www.bluetooth.com/specifications/assigned-numbers (login required). - * --> - * - * @param provider Network provider name - * @param technology Network technology - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void networkStateChanged(@NonNull String provider, int technology) { - if (DBG) { - Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.networkStateChanged(mCcid, provider, technology); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Send a response to a call control request to a remote device. - * - * <p> - * This function must be invoked in when a request is received by one of these - * callback methods: - * - * <ul> - * <li>{@link Callback#onAcceptCall} - * <li>{@link Callback#onTerminateCall} - * <li>{@link Callback#onHoldCall} - * <li>{@link Callback#onUnholdCall} - * <li>{@link Callback#onPlaceCall} - * <li>{@link Callback#onJoinCalls} - * </ul> - * - * @param requestId The ID of the request that was received with the callback - * @param result The result of the request to be sent to the remote devices - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void requestResult(int requestId, @Result int result) { - if (DBG) { - Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.requestResult(mCcid, requestId, result); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - private static boolean isValidDevice(@Nullable BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private final IBluetoothProfileServiceConnection mConnection = - new IBluetoothProfileServiceConnection.Stub() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) { - Log.d(TAG, "Proxy object connected"); - } - mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service)); - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED)); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (DBG) { - Log.d(TAG, "Proxy object disconnected"); - } - doUnbind(); - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED)); - } - }; - - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_TBS_SERVICE_CONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL, - BluetoothLeCallControl.this); - } - break; - } - case MESSAGE_TBS_SERVICE_DISCONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL); - } - break; - } - } - } - }; -} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index d0f74e985729..e047e5d81a9d 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -240,19 +240,12 @@ public interface BluetoothProfile { int LE_AUDIO_BROADCAST = 26; /** - * @hide - * Telephone Bearer Service from Call Control Profile - * - */ - int LE_CALL_CONTROL = 27; - - /** * Max profile ID. This value should be updated whenever a new profile is added to match * the largest value assigned to a profile. * * @hide */ - int MAX_PROFILE_ID = 27; + int MAX_PROFILE_ID = 26; /** * Default priority for devices that we try to auto-connect to and diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 01d231c51751..7b9d37e7136d 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2670,6 +2670,9 @@ public abstract class ContentResolver implements ContentInterface { * {@link ContentObserver#onChange(boolean, Collection, int, UserHandle)} should be * overwritten to get the corresponding {@link UserHandle} for that notification. * + * <p> If you don't hold the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} + * permission, you can register the {@link ContentObserver} only for current user. + * * @param uri The URI to watch for changes. This can be a specific row URI, * or a base URI for a whole class of content. * @param notifyForDescendants When false, the observer will be notified diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 338dfd62f316..819cbb0f347d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -10187,16 +10187,15 @@ public abstract class PackageManager { 16, PermissionManager.CACHE_KEY_PACKAGE_INFO, "getApplicationInfo") { @Override - protected ApplicationInfo recompute(ApplicationInfoQuery query) { + public ApplicationInfo recompute(ApplicationInfoQuery query) { return getApplicationInfoAsUserUncached( query.packageName, query.flags, query.userId); } @Override - protected ApplicationInfo maybeCheckConsistency( - ApplicationInfoQuery query, ApplicationInfo proposedResult) { + public boolean resultEquals(ApplicationInfo cached, ApplicationInfo fetched) { // Implementing this debug check for ApplicationInfo would require a // complicated deep comparison, so just bypass it for now. - return proposedResult; + return true; } }; @@ -10289,16 +10288,15 @@ public abstract class PackageManager { 32, PermissionManager.CACHE_KEY_PACKAGE_INFO, "getPackageInfo") { @Override - protected PackageInfo recompute(PackageInfoQuery query) { + public PackageInfo recompute(PackageInfoQuery query) { return getPackageInfoAsUserUncached( query.packageName, query.flags, query.userId); } @Override - protected PackageInfo maybeCheckConsistency( - PackageInfoQuery query, PackageInfo proposedResult) { + public boolean resultEquals(PackageInfo cached, PackageInfo fetched) { // Implementing this debug check for PackageInfo would require a // complicated deep comparison, so just bypass it for now. - return proposedResult; + return true; } }; diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index dbd3d5c4a7e7..23cae4c04467 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -1152,7 +1152,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, */ @Nullable private ArrayMap<String, String> buildAppClassNamesByProcess() { - if (processes == null) { + if (ArrayUtils.size(processes) == 0) { return null; } final ArrayMap<String, String> ret = new ArrayMap<>(4); diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java index fd1a33161740..6c83057fdf29 100644 --- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java +++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java @@ -78,7 +78,7 @@ public class SurfaceUtils { public static boolean isSurfaceForHwVideoEncoder(Surface surface) { checkNotNull(surface); long usageFlags = nativeDetectSurfaceUsageFlags(surface); - long disallowedFlags = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | USAGE_HW_COMPOSER + long disallowedFlags = USAGE_HW_COMPOSER | USAGE_RENDERSCRIPT | HardwareBuffer.USAGE_CPU_READ_OFTEN; long allowedFlags = HardwareBuffer.USAGE_VIDEO_ENCODE; boolean videoEncoderConsumer = ((usageFlags & disallowedFlags) == 0 diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 00374644d72c..de5c9adbc599 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1206,6 +1206,17 @@ public final class DisplayManager { } /** + * Returns whether the specified display supports DISPLAY_DECORATION. + * + * @param displayId The display to query support. + * + * @hide + */ + public boolean getDisplayDecorationSupport(int displayId) { + return mGlobal.getDisplayDecorationSupport(displayId); + } + + /** * Returns the user preference for "Match content frame rate". * <p> * Never: Even if the app requests it, the device will never try to match its output to the diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index e73116556758..bf6e6651741c 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -128,7 +128,7 @@ public final class DisplayManagerGlobal { 8, // size of display cache CACHE_KEY_DISPLAY_INFO_PROPERTY) { @Override - protected DisplayInfo recompute(Integer id) { + public DisplayInfo recompute(Integer id) { try { return mDm.getDisplayInfo(id); } catch (RemoteException ex) { @@ -812,6 +812,21 @@ public final class DisplayManagerGlobal { } /** + * Report whether the display supports DISPLAY_DECORATION. + * + * @param displayId The display whose support is being queried. + * + * @hide + */ + public boolean getDisplayDecorationSupport(int displayId) { + try { + return mDm.getDisplayDecorationSupport(displayId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * Gets the brightness of the display. * * @param displayId The display from which to get the brightness diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 82b31d48d5fe..d38d388ca8a3 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -180,4 +180,7 @@ interface IDisplayManager { // Returns the refresh rate switching type. int getRefreshRateSwitchingType(); + + // Query for DISPLAY_DECORATION support. + boolean getDisplayDecorationSupport(int displayId); } diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 56f81423db4e..b97055976e3e 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -306,22 +306,21 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan throw new IllegalArgumentException("Must supply an enrollment callback"); } - if (cancel != null) { - if (cancel.isCanceled()) { - Slog.w(TAG, "enrollment already canceled"); - return; - } else { - cancel.setOnCancelListener(new OnEnrollCancelListener()); - } + if (cancel != null && cancel.isCanceled()) { + Slog.w(TAG, "enrollment already canceled"); + return; } if (mService != null) { try { mEnrollmentCallback = callback; Trace.beginSection("FaceManager#enroll"); - mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver, - mContext.getOpPackageName(), disabledFeatures, previewSurface, - debugConsent); + final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken, + mServiceReceiver, mContext.getOpPackageName(), disabledFeatures, + previewSurface, debugConsent); + if (cancel != null) { + cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); + } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or @@ -359,21 +358,20 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan throw new IllegalArgumentException("Must supply an enrollment callback"); } - if (cancel != null) { - if (cancel.isCanceled()) { - Slog.w(TAG, "enrollRemotely is already canceled."); - return; - } else { - cancel.setOnCancelListener(new OnEnrollCancelListener()); - } + if (cancel != null && cancel.isCanceled()) { + Slog.w(TAG, "enrollRemotely is already canceled."); + return; } if (mService != null) { try { mEnrollmentCallback = callback; Trace.beginSection("FaceManager#enrollRemotely"); - mService.enrollRemotely(userId, mToken, hardwareAuthToken, mServiceReceiver, - mContext.getOpPackageName(), disabledFeatures); + final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken, + mServiceReceiver, mContext.getOpPackageName(), disabledFeatures); + if (cancel != null) { + cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId)); + } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enrollRemotely: ", e); // Though this may not be a hardware issue, it will cause apps to give up or @@ -713,10 +711,10 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } - private void cancelEnrollment() { + private void cancelEnrollment(long requestId) { if (mService != null) { try { - mService.cancelEnrollment(mToken); + mService.cancelEnrollment(mToken, requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1100,9 +1098,16 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } private class OnEnrollCancelListener implements OnCancelListener { + private final long mAuthRequestId; + + private OnEnrollCancelListener(long id) { + mAuthRequestId = id; + } + @Override public void onCancel() { - cancelEnrollment(); + Slog.d(TAG, "Cancel face enrollment requested for: " + mAuthRequestId); + cancelEnrollment(mAuthRequestId); } } diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl index e9198246dee3..989b001ca8bf 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -76,15 +76,16 @@ interface IFaceService { void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId); // Start face enrollment - void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, - String opPackageName, in int [] disabledFeatures, in Surface previewSurface, boolean debugConsent); + long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, + String opPackageName, in int [] disabledFeatures, + in Surface previewSurface, boolean debugConsent); // Start remote face enrollment - void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, + long enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver, String opPackageName, in int [] disabledFeatures); // Cancel enrollment in progress - void cancelEnrollment(IBinder token); + void cancelEnrollment(IBinder token, long requestId); // Removes the specified face enrollment for the specified userId. void remove(IBinder token, int faceId, int userId, IFaceServiceReceiver receiver, diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index fe04e5d35784..acf9427b1241 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -183,9 +183,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } private class OnEnrollCancelListener implements OnCancelListener { + private final long mAuthRequestId; + + private OnEnrollCancelListener(long id) { + mAuthRequestId = id; + } + @Override public void onCancel() { - cancelEnrollment(); + Slog.d(TAG, "Cancel fingerprint enrollment requested for: " + mAuthRequestId); + cancelEnrollment(mAuthRequestId); } } @@ -646,20 +653,19 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing throw new IllegalArgumentException("Must supply an enrollment callback"); } - if (cancel != null) { - if (cancel.isCanceled()) { - Slog.w(TAG, "enrollment already canceled"); - return; - } else { - cancel.setOnCancelListener(new OnEnrollCancelListener()); - } + if (cancel != null && cancel.isCanceled()) { + Slog.w(TAG, "enrollment already canceled"); + return; } if (mService != null) { try { mEnrollmentCallback = callback; - mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver, - mContext.getOpPackageName(), enrollReason); + final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId, + mServiceReceiver, mContext.getOpPackageName(), enrollReason); + if (cancel != null) { + cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId)); + } } catch (RemoteException e) { Slog.w(TAG, "Remote exception in enroll: ", e); // Though this may not be a hardware issue, it will cause apps to give up or try @@ -1302,9 +1308,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return allSensors.isEmpty() ? null : allSensors.get(0); } - private void cancelEnrollment() { + private void cancelEnrollment(long requestId) { if (mService != null) try { - mService.cancelEnrollment(mToken); + mService.cancelEnrollment(mToken, requestId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index ba1dc6da62a6..cbff8b11a72a 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -84,11 +84,11 @@ interface IFingerprintService { void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId); // Start fingerprint enrollment - void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, + long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver, String opPackageName, int enrollReason); // Cancel enrollment in progress - void cancelEnrollment(IBinder token); + void cancelEnrollment(IBinder token, long requestId); // Any errors resulting from this call will be returned to the listener void remove(IBinder token, int fingerId, int userId, IFingerprintServiceReceiver receiver, diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 6253fb90323a..ef349a96ee17 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -215,14 +215,6 @@ public final class InputManager { public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; /** - * Check whether apps are using FLAG_SLIPPERY for their windows. We expect that this flag is - * only used by the system components. If so, we can lock it down. - * @hide - */ - @ChangeId - public static final long BLOCK_FLAG_SLIPPERY = android.os.IInputConstants.BLOCK_FLAG_SLIPPERY; - - /** * Input Event Injection Synchronization Mode: None. * Never blocks. Injection is asynchronous and is assumed always to be successful. * @hide diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java index b3f734524078..1ac3f0a6d7d1 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkTemplate.java @@ -39,7 +39,7 @@ import java.util.Set; // TODO: Add documents /** @hide */ -public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority { +public final class VcnCellUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate { private static final String ALLOWED_NETWORK_PLMN_IDS_KEY = "mAllowedNetworkPlmnIds"; @NonNull private final Set<String> mAllowedNetworkPlmnIds; private static final String ALLOWED_SPECIFIC_CARRIER_IDS_KEY = "mAllowedSpecificCarrierIds"; @@ -51,7 +51,7 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork private static final String REQUIRE_OPPORTUNISTIC_KEY = "mRequireOpportunistic"; private final boolean mRequireOpportunistic; - private VcnCellUnderlyingNetworkPriority( + private VcnCellUnderlyingNetworkTemplate( int networkQuality, boolean allowMetered, Set<String> allowedNetworkPlmnIds, @@ -92,7 +92,7 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PROTECTED) - public static VcnCellUnderlyingNetworkPriority fromPersistableBundle( + public static VcnCellUnderlyingNetworkTemplate fromPersistableBundle( @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); @@ -117,7 +117,7 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork final boolean allowRoaming = in.getBoolean(ALLOW_ROAMING_KEY); final boolean requireOpportunistic = in.getBoolean(REQUIRE_OPPORTUNISTIC_KEY); - return new VcnCellUnderlyingNetworkPriority( + return new VcnCellUnderlyingNetworkTemplate( networkQuality, allowMetered, allowedNetworkPlmnIds, @@ -190,11 +190,11 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork return false; } - if (!(other instanceof VcnCellUnderlyingNetworkPriority)) { + if (!(other instanceof VcnCellUnderlyingNetworkTemplate)) { return false; } - final VcnCellUnderlyingNetworkPriority rhs = (VcnCellUnderlyingNetworkPriority) other; + final VcnCellUnderlyingNetworkTemplate rhs = (VcnCellUnderlyingNetworkTemplate) other; return Objects.equals(mAllowedNetworkPlmnIds, rhs.mAllowedNetworkPlmnIds) && Objects.equals(mAllowedSpecificCarrierIds, rhs.mAllowedSpecificCarrierIds) && mAllowRoaming == rhs.mAllowRoaming @@ -211,7 +211,7 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork } /** This class is used to incrementally build WifiNetworkPriority objects. */ - public static final class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> { + public static final class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> { @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>(); @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>(); @@ -280,10 +280,10 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork return this; } - /** Build the VcnCellUnderlyingNetworkPriority. */ + /** Build the VcnCellUnderlyingNetworkTemplate. */ @NonNull - public VcnCellUnderlyingNetworkPriority build() { - return new VcnCellUnderlyingNetworkPriority( + public VcnCellUnderlyingNetworkTemplate build() { + return new VcnCellUnderlyingNetworkTemplate( mNetworkQuality, mAllowMetered, mAllowedNetworkPlmnIds, diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 55d3ecd2c92f..d07c24a6529c 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -16,7 +16,7 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static com.android.internal.annotations.VisibleForTesting.Visibility; @@ -162,12 +162,12 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @VisibleForTesting(visibility = Visibility.PRIVATE) - public static final LinkedHashSet<VcnUnderlyingNetworkPriority> + public static final LinkedHashSet<VcnUnderlyingNetworkTemplate> DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>(); static { DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( - new VcnCellUnderlyingNetworkPriority.Builder() + new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .setAllowRoaming(true /* allowRoaming */) @@ -175,13 +175,13 @@ public final class VcnGatewayConnectionConfig { .build()); DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( - new VcnWifiUnderlyingNetworkPriority.Builder() + new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .build()); DEFAULT_UNDERLYING_NETWORK_PRIORITIES.add( - new VcnCellUnderlyingNetworkPriority.Builder() + new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .setAllowRoaming(true /* allowRoaming */) @@ -202,7 +202,7 @@ public final class VcnGatewayConnectionConfig { @VisibleForTesting(visibility = Visibility.PRIVATE) public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities"; - @NonNull private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities; + @NonNull private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities; private static final String MAX_MTU_KEY = "mMaxMtu"; private final int mMaxMtu; @@ -215,7 +215,7 @@ public final class VcnGatewayConnectionConfig { @NonNull String gatewayConnectionName, @NonNull IkeTunnelConnectionParams tunnelConnectionParams, @NonNull Set<Integer> exposedCapabilities, - @NonNull LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities, + @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, @NonNull long[] retryIntervalsMs, @IntRange(from = MIN_MTU_V6) int maxMtu) { mGatewayConnectionName = gatewayConnectionName; @@ -265,7 +265,7 @@ public final class VcnGatewayConnectionConfig { new LinkedHashSet<>( PersistableBundleUtils.toList( networkPrioritiesBundle, - VcnUnderlyingNetworkPriority::fromPersistableBundle)); + VcnUnderlyingNetworkTemplate::fromPersistableBundle)); } mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY); @@ -368,14 +368,14 @@ public final class VcnGatewayConnectionConfig { } /** - * Retrieve the configured VcnUnderlyingNetworkPriority list, or a default list if it is not + * Retrieve the configured VcnUnderlyingNetworkTemplate list, or a default list if it is not * configured. * - * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkPriority>) + * @see Builder#setVcnUnderlyingNetworkPriorities(LinkedHashSet<VcnUnderlyingNetworkTemplate>) * @hide */ @NonNull - public LinkedHashSet<VcnUnderlyingNetworkPriority> getVcnUnderlyingNetworkPriorities() { + public LinkedHashSet<VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities() { return new LinkedHashSet<>(mUnderlyingNetworkPriorities); } @@ -418,7 +418,7 @@ public final class VcnGatewayConnectionConfig { final PersistableBundle networkPrioritiesBundle = PersistableBundleUtils.fromList( new ArrayList<>(mUnderlyingNetworkPriorities), - VcnUnderlyingNetworkPriority::toPersistableBundle); + VcnUnderlyingNetworkTemplate::toPersistableBundle); result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName); result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle); @@ -465,7 +465,7 @@ public final class VcnGatewayConnectionConfig { @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet(); @NonNull - private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities = + private final LinkedHashSet<VcnUnderlyingNetworkTemplate> mUnderlyingNetworkPriorities = new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES); @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; @@ -539,7 +539,7 @@ public final class VcnGatewayConnectionConfig { } /** - * Set the VcnUnderlyingNetworkPriority list. + * Set the VcnUnderlyingNetworkTemplate list. * * @param underlyingNetworkPriorities a list of unique VcnUnderlyingNetworkPriorities that * are ordered from most to least preferred, or an empty list to use the default @@ -550,7 +550,7 @@ public final class VcnGatewayConnectionConfig { /** @hide */ @NonNull public Builder setVcnUnderlyingNetworkPriorities( - @NonNull LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities) { + @NonNull LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities) { Objects.requireNonNull( mUnderlyingNetworkPriorities, "underlyingNetworkPriorities is null"); diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java index 551f75772b9a..d306d5cb6826 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkTemplate.java @@ -33,7 +33,7 @@ import java.util.Objects; // TODO: Add documents /** @hide */ -public abstract class VcnUnderlyingNetworkPriority { +public abstract class VcnUnderlyingNetworkTemplate { /** @hide */ protected static final int NETWORK_PRIORITY_TYPE_WIFI = 1; /** @hide */ @@ -68,7 +68,7 @@ public abstract class VcnUnderlyingNetworkPriority { private final boolean mAllowMetered; /** @hide */ - protected VcnUnderlyingNetworkPriority( + protected VcnUnderlyingNetworkTemplate( int networkPriorityType, int networkQuality, boolean allowMetered) { mNetworkPriorityType = networkPriorityType; mNetworkQuality = networkQuality; @@ -89,16 +89,16 @@ public abstract class VcnUnderlyingNetworkPriority { /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PROTECTED) - public static VcnUnderlyingNetworkPriority fromPersistableBundle( + public static VcnUnderlyingNetworkTemplate fromPersistableBundle( @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); final int networkPriorityType = in.getInt(NETWORK_PRIORITY_TYPE_KEY); switch (networkPriorityType) { case NETWORK_PRIORITY_TYPE_WIFI: - return VcnWifiUnderlyingNetworkPriority.fromPersistableBundle(in); + return VcnWifiUnderlyingNetworkTemplate.fromPersistableBundle(in); case NETWORK_PRIORITY_TYPE_CELL: - return VcnCellUnderlyingNetworkPriority.fromPersistableBundle(in); + return VcnCellUnderlyingNetworkTemplate.fromPersistableBundle(in); default: throw new IllegalArgumentException( "Invalid networkPriorityType:" + networkPriorityType); @@ -124,11 +124,11 @@ public abstract class VcnUnderlyingNetworkPriority { @Override public boolean equals(@Nullable Object other) { - if (!(other instanceof VcnUnderlyingNetworkPriority)) { + if (!(other instanceof VcnUnderlyingNetworkTemplate)) { return false; } - final VcnUnderlyingNetworkPriority rhs = (VcnUnderlyingNetworkPriority) other; + final VcnUnderlyingNetworkTemplate rhs = (VcnUnderlyingNetworkTemplate) other; return mNetworkPriorityType == rhs.mNetworkPriorityType && mNetworkQuality == rhs.mNetworkQuality && mAllowMetered == rhs.mAllowMetered; @@ -168,7 +168,7 @@ public abstract class VcnUnderlyingNetworkPriority { } /** - * This class is used to incrementally build VcnUnderlyingNetworkPriority objects. + * This class is used to incrementally build VcnUnderlyingNetworkTemplate objects. * * @param <T> The subclass to be built. */ diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java index 85eb100779a2..6bbb2bfecda4 100644 --- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplate.java @@ -28,11 +28,11 @@ import java.util.Objects; // TODO: Add documents /** @hide */ -public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetworkPriority { +public final class VcnWifiUnderlyingNetworkTemplate extends VcnUnderlyingNetworkTemplate { private static final String SSID_KEY = "mSsid"; @Nullable private final String mSsid; - private VcnWifiUnderlyingNetworkPriority( + private VcnWifiUnderlyingNetworkTemplate( int networkQuality, boolean allowMetered, String ssid) { super(NETWORK_PRIORITY_TYPE_WIFI, networkQuality, allowMetered); mSsid = ssid; @@ -43,14 +43,14 @@ public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetwork /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PROTECTED) - public static VcnWifiUnderlyingNetworkPriority fromPersistableBundle( + public static VcnWifiUnderlyingNetworkTemplate fromPersistableBundle( @NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); final int networkQuality = in.getInt(NETWORK_QUALITY_KEY); final boolean allowMetered = in.getBoolean(ALLOW_METERED_KEY); final String ssid = in.getString(SSID_KEY); - return new VcnWifiUnderlyingNetworkPriority(networkQuality, allowMetered, ssid); + return new VcnWifiUnderlyingNetworkTemplate(networkQuality, allowMetered, ssid); } /** @hide */ @@ -74,11 +74,11 @@ public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetwork return false; } - if (!(other instanceof VcnWifiUnderlyingNetworkPriority)) { + if (!(other instanceof VcnWifiUnderlyingNetworkTemplate)) { return false; } - final VcnWifiUnderlyingNetworkPriority rhs = (VcnWifiUnderlyingNetworkPriority) other; + final VcnWifiUnderlyingNetworkTemplate rhs = (VcnWifiUnderlyingNetworkTemplate) other; return mSsid.equals(rhs.mSsid); } @@ -94,8 +94,8 @@ public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetwork return mSsid; } - /** This class is used to incrementally build VcnWifiUnderlyingNetworkPriority objects. */ - public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> { + /** This class is used to incrementally build VcnWifiUnderlyingNetworkTemplate objects. */ + public static class Builder extends VcnUnderlyingNetworkTemplate.Builder<Builder> { @Nullable private String mSsid; /** Construct a Builder object. */ @@ -112,10 +112,10 @@ public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetwork return this; } - /** Build the VcnWifiUnderlyingNetworkPriority. */ + /** Build the VcnWifiUnderlyingNetworkTemplate. */ @NonNull - public VcnWifiUnderlyingNetworkPriority build() { - return new VcnWifiUnderlyingNetworkPriority(mNetworkQuality, mAllowMetered, mSsid); + public VcnWifiUnderlyingNetworkTemplate build() { + return new VcnWifiUnderlyingNetworkTemplate(mNetworkQuality, mAllowMetered, mSsid); } /** @hide */ diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fa209cc02cd1..520730fa7352 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -3374,6 +3374,11 @@ public abstract class BatteryStats implements Parcelable { public abstract Map<String, ? extends Timer> getKernelWakelockStats(); /** + * Returns aggregated wake lock stats. + */ + public abstract WakeLockStats getWakeLockStats(); + + /** * Returns Timers tracking the total time of each Resource Power Manager state and voter. */ public abstract Map<String, ? extends Timer> getRpmStats(); diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 7f526c13a75a..6339435e539c 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -157,6 +157,7 @@ public final class BatteryStatsManager { @Retention(RetentionPolicy.SOURCE) public @interface WifiSupplState {} + private final IBatteryStats mBatteryStats; /** @hide */ @@ -352,6 +353,21 @@ public final class BatteryStatsManager { } /** + * Retrieves accumulate wake lock stats. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BATTERY_STATS) + @NonNull + public WakeLockStats getWakeLockStats() { + try { + return mBatteryStats.getWakeLockStats(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Indicates an app acquiring full wifi lock. * * @param ws worksource (to be used for battery blaming). diff --git a/core/java/android/os/NewUserResponse.java b/core/java/android/os/NewUserResponse.java index 3869559dc3c6..c2aef8e62078 100644 --- a/core/java/android/os/NewUserResponse.java +++ b/core/java/android/os/NewUserResponse.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; /** * Contains the response of the call {@link UserManager#createUser(NewUserRequest)}. @@ -29,7 +30,11 @@ public final class NewUserResponse { private final @Nullable UserHandle mUser; private final @UserManager.UserOperationResult int mOperationResult; - NewUserResponse(@Nullable UserHandle user, + /** + * @hide + */ + @TestApi + public NewUserResponse(@Nullable UserHandle user, @UserManager.UserOperationResult int operationResult) { mUser = user; mOperationResult = operationResult; diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 74fffd0ae10d..92d652df35d9 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1011,7 +1011,7 @@ public final class PowerManager { new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES, CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY) { @Override - protected Boolean recompute(Void query) { + public Boolean recompute(Void query) { try { return mService.isPowerSaveMode(); } catch (RemoteException e) { @@ -1024,7 +1024,7 @@ public final class PowerManager { new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES, CACHE_KEY_IS_INTERACTIVE_PROPERTY) { @Override - protected Boolean recompute(Void query) { + public Boolean recompute(Void query) { try { return mService.isInteractive(); } catch (RemoteException e) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index b3639e419a56..b4f3fa0ae80c 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2754,7 +2754,7 @@ public class UserManager { new PropertyInvalidatedCache<Integer, Boolean>( 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) { @Override - protected Boolean recompute(Integer query) { + public Boolean recompute(Integer query) { try { return mService.isUserUnlocked(query); } catch (RemoteException re) { @@ -2762,7 +2762,7 @@ public class UserManager { } } @Override - protected boolean bypass(Integer query) { + public boolean bypass(Integer query) { return query < 0; } }; @@ -2772,7 +2772,7 @@ public class UserManager { new PropertyInvalidatedCache<Integer, Boolean>( 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) { @Override - protected Boolean recompute(Integer query) { + public Boolean recompute(Integer query) { try { return mService.isUserUnlockingOrUnlocked(query); } catch (RemoteException re) { @@ -2780,7 +2780,7 @@ public class UserManager { } } @Override - protected boolean bypass(Integer query) { + public boolean bypass(Integer query) { return query < 0; } }; diff --git a/core/java/android/os/WakeLockStats.aidl b/core/java/android/os/WakeLockStats.aidl new file mode 100644 index 000000000000..be08d782264f --- /dev/null +++ b/core/java/android/os/WakeLockStats.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 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; + +/** {@hide} */ +parcelable WakeLockStats; diff --git a/core/java/android/os/WakeLockStats.java b/core/java/android/os/WakeLockStats.java new file mode 100644 index 000000000000..05a7313d1600 --- /dev/null +++ b/core/java/android/os/WakeLockStats.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Snapshot of wake lock stats. + * @hide + */ +public final class WakeLockStats implements Parcelable { + + /** @hide */ + public static class WakeLock { + public final int uid; + @NonNull + public final String name; + public final int timesAcquired; + public final long totalTimeHeldMs; + + /** + * Time in milliseconds that the lock has been held or 0 if not currently holding the lock + */ + public final long timeHeldMs; + + public WakeLock(int uid, @NonNull String name, int timesAcquired, long totalTimeHeldMs, + long timeHeldMs) { + this.uid = uid; + this.name = name; + this.timesAcquired = timesAcquired; + this.totalTimeHeldMs = totalTimeHeldMs; + this.timeHeldMs = timeHeldMs; + } + + private WakeLock(Parcel in) { + uid = in.readInt(); + name = in.readString(); + timesAcquired = in.readInt(); + totalTimeHeldMs = in.readLong(); + timeHeldMs = in.readLong(); + } + + private void writeToParcel(Parcel out) { + out.writeInt(uid); + out.writeString(name); + out.writeInt(timesAcquired); + out.writeLong(totalTimeHeldMs); + out.writeLong(timeHeldMs); + } + + @Override + public String toString() { + return "WakeLock{" + + "uid=" + uid + + ", name='" + name + '\'' + + ", timesAcquired=" + timesAcquired + + ", totalTimeHeldMs=" + totalTimeHeldMs + + ", timeHeldMs=" + timeHeldMs + + '}'; + } + } + + private final List<WakeLock> mWakeLocks; + + /** @hide **/ + public WakeLockStats(@NonNull List<WakeLock> wakeLocks) { + mWakeLocks = wakeLocks; + } + + @NonNull + public List<WakeLock> getWakeLocks() { + return mWakeLocks; + } + + private WakeLockStats(Parcel in) { + final int size = in.readInt(); + mWakeLocks = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + mWakeLocks.add(new WakeLock(in)); + } + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + final int size = mWakeLocks.size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + WakeLock stats = mWakeLocks.get(i); + stats.writeToParcel(out); + } + } + + @NonNull + public static final Creator<WakeLockStats> CREATOR = + new Creator<WakeLockStats>() { + public WakeLockStats createFromParcel(Parcel in) { + return new WakeLockStats(in); + } + + public WakeLockStats[] newArray(int size) { + return new WakeLockStats[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "WakeLockStats " + mWakeLocks; + } +} diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 19e04e69f52e..8ee52c21e869 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -99,7 +99,7 @@ public final class StorageVolume implements Parcelable { @UnsupportedAppUsage private final boolean mRemovable; private final boolean mEmulated; - private final boolean mStub; + private final boolean mExternallyManaged; private final boolean mAllowMassStorage; private final long mMaxFileSize; private final UserHandle mOwner; @@ -138,7 +138,7 @@ public final class StorageVolume implements Parcelable { /** {@hide} */ public StorageVolume(String id, File path, File internalPath, String description, - boolean primary, boolean removable, boolean emulated, boolean stub, + boolean primary, boolean removable, boolean emulated, boolean externallyManaged, boolean allowMassStorage, long maxFileSize, UserHandle owner, UUID uuid, String fsUuid, String state) { mId = Preconditions.checkNotNull(id); @@ -148,7 +148,7 @@ public final class StorageVolume implements Parcelable { mPrimary = primary; mRemovable = removable; mEmulated = emulated; - mStub = stub; + mExternallyManaged = externallyManaged; mAllowMassStorage = allowMassStorage; mMaxFileSize = maxFileSize; mOwner = Preconditions.checkNotNull(owner); @@ -165,7 +165,7 @@ public final class StorageVolume implements Parcelable { mPrimary = in.readInt() != 0; mRemovable = in.readInt() != 0; mEmulated = in.readInt() != 0; - mStub = in.readInt() != 0; + mExternallyManaged = in.readInt() != 0; mAllowMassStorage = in.readInt() != 0; mMaxFileSize = in.readLong(); mOwner = in.readParcelable(null, android.os.UserHandle.class); @@ -275,13 +275,13 @@ public final class StorageVolume implements Parcelable { } /** - * Returns true if the volume is a stub volume (a volume managed from outside Android). + * Returns true if the volume is managed from outside Android. * * @hide */ @SystemApi - public boolean isStub() { - return mStub; + public boolean isExternallyManaged() { + return mExternallyManaged; } /** @@ -520,7 +520,7 @@ public final class StorageVolume implements Parcelable { pw.printPair("mPrimary", mPrimary); pw.printPair("mRemovable", mRemovable); pw.printPair("mEmulated", mEmulated); - pw.printPair("mStub", mStub); + pw.printPair("mExternallyManaged", mExternallyManaged); pw.printPair("mAllowMassStorage", mAllowMassStorage); pw.printPair("mMaxFileSize", mMaxFileSize); pw.printPair("mOwner", mOwner); @@ -555,7 +555,7 @@ public final class StorageVolume implements Parcelable { parcel.writeInt(mPrimary ? 1 : 0); parcel.writeInt(mRemovable ? 1 : 0); parcel.writeInt(mEmulated ? 1 : 0); - parcel.writeInt(mStub ? 1 : 0); + parcel.writeInt(mExternallyManaged ? 1 : 0); parcel.writeInt(mAllowMassStorage ? 1 : 0); parcel.writeLong(mMaxFileSize); parcel.writeParcelable(mOwner, flags); @@ -637,7 +637,7 @@ public final class StorageVolume implements Parcelable { mPrimary, mRemovable, mEmulated, - /* stub= */ false, + /* externallyManaged= */ false, /* allowMassStorage= */ false, /* maxFileSize= */ 0, mOwner, diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index ebd143c33990..84b6b5eabda1 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -404,7 +404,7 @@ public class VolumeInfo implements Parcelable { final boolean removable; final boolean emulated; - final boolean stub = type == TYPE_STUB; + final boolean externallyManaged = type == TYPE_STUB; final boolean allowMassStorage = false; final String envState = reportUnmounted ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state); @@ -460,8 +460,8 @@ public class VolumeInfo implements Parcelable { } return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable, - emulated, stub, allowMassStorage, maxFileSize, new UserHandle(userId), uuid, - derivedFsUuid, envState); + emulated, externallyManaged, allowMassStorage, maxFileSize, new UserHandle(userId), + uuid, derivedFsUuid, envState); } @UnsupportedAppUsage diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java index eac09aab46cd..1e5cdd79f539 100644 --- a/core/java/android/os/storage/VolumeRecord.java +++ b/core/java/android/os/storage/VolumeRecord.java @@ -105,7 +105,7 @@ public class VolumeRecord implements Parcelable { final boolean primary = false; final boolean removable = true; final boolean emulated = false; - final boolean stub = false; + final boolean externallyManaged = false; final boolean allowMassStorage = false; final long maxFileSize = 0; final UserHandle user = new UserHandle(UserHandle.USER_NULL); @@ -117,8 +117,8 @@ public class VolumeRecord implements Parcelable { } return new StorageVolume(id, userPath, internalPath, description, primary, removable, - emulated, stub, allowMassStorage, maxFileSize, user, null /* uuid */, fsUuid, - envState); + emulated, externallyManaged, allowMassStorage, maxFileSize, user, null /* uuid */, + fsUuid, envState); } public void dump(IndentingPrintWriter pw) { diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 61e48c587e1b..3ea50e98879b 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1447,7 +1447,7 @@ public final class PermissionManager { new PropertyInvalidatedCache<PermissionQuery, Integer>( 2048, CACHE_KEY_PACKAGE_INFO, "checkPermission") { @Override - protected Integer recompute(PermissionQuery query) { + public Integer recompute(PermissionQuery query) { return checkPermissionUncached(query.permission, query.pid, query.uid); } }; @@ -1530,12 +1530,12 @@ public final class PermissionManager { new PropertyInvalidatedCache<PackageNamePermissionQuery, Integer>( 16, CACHE_KEY_PACKAGE_INFO, "checkPackageNamePermission") { @Override - protected Integer recompute(PackageNamePermissionQuery query) { + public Integer recompute(PackageNamePermissionQuery query) { return checkPackageNamePermissionUncached( query.permName, query.pkgName, query.userId); } @Override - protected boolean bypass(PackageNamePermissionQuery query) { + public boolean bypass(PackageNamePermissionQuery query) { return query.userId < 0; } }; diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java index dd2ea81d747b..5d00b29eb3c8 100644 --- a/core/java/android/provider/BlockedNumberContract.java +++ b/core/java/android/provider/BlockedNumberContract.java @@ -231,7 +231,7 @@ public class BlockedNumberContract { prefix = { "STATUS_" }, value = {STATUS_NOT_BLOCKED, STATUS_BLOCKED_IN_LIST, STATUS_BLOCKED_RESTRICTED, STATUS_BLOCKED_UNKNOWN_NUMBER, STATUS_BLOCKED_PAYPHONE, - STATUS_BLOCKED_NOT_IN_CONTACTS}) + STATUS_BLOCKED_NOT_IN_CONTACTS, STATUS_BLOCKED_UNAVAILABLE}) public @interface BlockStatus {} /** @@ -277,6 +277,13 @@ public class BlockedNumberContract { public static final int STATUS_BLOCKED_NOT_IN_CONTACTS = 5; /** + * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked + * because it is from a number not available. + * @hide + */ + public static final int STATUS_BLOCKED_UNAVAILABLE = 6; + + /** * Integer reason indicating whether a call was blocked, and if so why. * @hide */ @@ -441,6 +448,9 @@ public class BlockedNumberContract { /* Preference key for whether should show an emergency call notification. */ public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION = "show_emergency_call_notification"; + /* Preference key of block unavailable calls setting. */ + public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE = + "block_unavailable_calls_setting"; /** * Notifies the provider that emergency services were contacted by the user. @@ -547,6 +557,7 @@ public class BlockedNumberContract { * {@link #ENHANCED_SETTING_KEY_BLOCK_PRIVATE} * {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE} * {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN} + * {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE} * {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING} * @return {@code true} if the setting is enabled. {@code false} otherwise. */ @@ -574,6 +585,7 @@ public class BlockedNumberContract { * {@link #ENHANCED_SETTING_KEY_BLOCK_PRIVATE} * {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE} * {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN} + * {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE} * {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING} * @param value the enabled statue of the setting to set. */ @@ -603,6 +615,8 @@ public class BlockedNumberContract { return "blocked - payphone"; case STATUS_BLOCKED_NOT_IN_CONTACTS: return "blocked - not in contacts"; + case STATUS_BLOCKED_UNAVAILABLE: + return "blocked - unavailable"; } return "unknown"; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 0cc5bfda75e8..8dca7abaca8e 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -910,6 +910,7 @@ public class CallLog { * <li>{@link #PRESENTATION_RESTRICTED}</li> * <li>{@link #PRESENTATION_UNKNOWN}</li> * <li>{@link #PRESENTATION_PAYPHONE}</li> + * <li>{@link #PRESENTATION_UNAVAILABLE}</li> * </ul> * </p> * @@ -925,6 +926,8 @@ public class CallLog { public static final int PRESENTATION_UNKNOWN = 3; /** Number is a pay phone. */ public static final int PRESENTATION_PAYPHONE = 4; + /** Number is unavailable. */ + public static final int PRESENTATION_UNAVAILABLE = 5; /** * The ISO 3166-1 two letters country code of the country where the @@ -2028,6 +2031,10 @@ public class CallLog { return presentation; } + if (presentation == TelecomManager.PRESENTATION_UNAVAILABLE) { + return PRESENTATION_UNAVAILABLE; + } + if (TextUtils.isEmpty(number) || presentation == TelecomManager.PRESENTATION_UNKNOWN) { return PRESENTATION_UNKNOWN; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 55ffdab9d094..44476795774e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10248,6 +10248,13 @@ public final class Settings { public static final int ACCESSIBILITY_MAGNIFICATION_MODE_ALL = 0x3; /** + * Whether the following typing focus feature for magnification is enabled. + * @hide + */ + public static final String ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED = + "accessibility_magnification_follow_typing_enabled"; + + /** * Controls magnification capability. Accessibility magnification is capable of at least one * of the magnification modes. * diff --git a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java index 6411314f7542..50efbac76f48 100644 --- a/core/java/android/service/contentsuggestions/ContentSuggestionsService.java +++ b/core/java/android/service/contentsuggestions/ContentSuggestionsService.java @@ -80,6 +80,7 @@ public abstract class ContentSuggestionsService extends Service { colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]); } wrappedBuffer = Bitmap.wrapHardwareBuffer(contextImage, colorSpace); + contextImage.close(); } } diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 3ee1a9000188..4ee02f01f330 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -16,6 +16,9 @@ package android.text; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas; import android.graphics.Paint; @@ -85,6 +88,37 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback } /** + * Utility function to construct a BoringLayout instance. + * + * The spacing multiplier and additional amount spacing are not used by BoringLayout. + * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will + * return 0.0. + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested width + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerWidth} is used instead + */ + public static @NonNull BoringLayout make( + @NonNull CharSequence source, @NonNull TextPaint paint, + @IntRange(from = 0) int outerWidth, + @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, + boolean includePad, @NonNull TextUtils.TruncateAt ellipsize, + @IntRange(from = 0) int ellipsizedWidth, boolean useFallbackLineSpacing) { + return new BoringLayout(source, paint, outerWidth, align, 1f, 0f, metrics, includePad, + ellipsize, ellipsizedWidth, useFallbackLineSpacing); + } + + /** * Returns a BoringLayout for the specified text, potentially reusing * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. @@ -109,7 +143,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedStart = 0; mEllipsizedCount = 0; - init(source, paint, align, metrics, includePad, true); + init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); return this; } @@ -118,12 +152,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * this one if it is already suitable. The caller must make sure that * no one is still using this Layout. * + * The spacing multiplier and additional amount spacing are not used by BoringLayout. + * {@link Layout#getSpacingMultiplier()} will return 1.0 and {@link Layout#getSpacingAdd()} will + * return 0.0. + * * @param source the text to render * @param paint the default paint for the layout * @param outerWidth the wrapping width for the text * @param align whether to left, right, or center the text - * @param spacingMult this value is no longer used by BoringLayout - * @param spacingAdd this value is no longer used by BoringLayout * @param metrics {@code #Metrics} instance that contains information about FontMetrics and * line width * @param includePad set whether to include extra space beyond font ascent and descent which is @@ -134,13 +170,15 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is * not used, {@code outerwidth} is used instead */ - public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, - Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, - boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + public @NonNull BoringLayout replaceOrMake(@NonNull CharSequence source, + @NonNull TextPaint paint, @IntRange(from = 0) int outerWidth, + @NonNull Alignment align, @NonNull BoringLayout.Metrics metrics, boolean includePad, + @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + boolean useFallbackLineSpacing) { boolean trust; if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) { - replaceWith(source, paint, outerWidth, align, spacingMult, spacingAdd); + replaceWith(source, paint, outerWidth, align, 1f, 0f); mEllipsizedWidth = outerWidth; mEllipsizedStart = 0; @@ -148,17 +186,46 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = true; } else { replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth, ellipsize, true, this), - paint, outerWidth, align, spacingMult, spacingAdd); + paint, outerWidth, align, 1f, 0f); mEllipsizedWidth = ellipsizedWidth; trust = false; } - init(getText(), paint, align, metrics, includePad, trust); + init(getText(), paint, align, metrics, includePad, trust, + useFallbackLineSpacing); return this; } /** + * Returns a BoringLayout for the specified text, potentially reusing + * this one if it is already suitable. The caller must make sure that + * no one is still using this Layout. + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param spacingMult this value is no longer used by BoringLayout + * @param spacingAdd this value is no longer used by BoringLayout + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested width + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerwidth} is used instead + */ + public BoringLayout replaceOrMake(CharSequence source, TextPaint paint, int outerWidth, + Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, + boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + return replaceOrMake(source, paint, outerWidth, align, metrics, + includePad, ellipsize, ellipsizedWidth, false /* useFallbackLineSpacing */); + } + + /** * @param source the text to render * @param paint the default paint for the layout * @param outerwidth the wrapping width for the text @@ -178,7 +245,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback mEllipsizedStart = 0; mEllipsizedCount = 0; - init(source, paint, align, metrics, includePad, true); + init(source, paint, align, metrics, includePad, true, false /* useFallbackLineSpacing */); } /** @@ -202,6 +269,34 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + this(source, paint, outerWidth, align, spacingMult, spacingAdd, metrics, includePad, + ellipsize, ellipsizedWidth, false /* fallbackLineSpacing */); + } + + /** + * + * @param source the text to render + * @param paint the default paint for the layout + * @param outerWidth the wrapping width for the text + * @param align whether to left, right, or center the text + * @param spacingMult this value is no longer used by BoringLayout + * @param spacingAdd this value is no longer used by BoringLayout + * @param metrics {@code #Metrics} instance that contains information about FontMetrics and + * line width + * @param includePad set whether to include extra space beyond font ascent and descent which is + * needed to avoid clipping in some scripts + * @param ellipsize whether to ellipsize the text if width of the text is longer than the + * requested {@code outerwidth} + * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is + * {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is + * not used, {@code outerwidth} is used instead + */ + public BoringLayout( + @NonNull CharSequence source, @NonNull TextPaint paint, + @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult, + float spacingAdd, @NonNull BoringLayout.Metrics metrics, boolean includePad, + @NonNull TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth, + boolean useFallbackLineSpacing) { /* * It is silly to have to call super() and then replaceWith(), * but we can't use "this" for the callback until the call to @@ -224,11 +319,12 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback trust = false; } - init(getText(), paint, align, metrics, includePad, trust); + init(getText(), paint, align, metrics, includePad, trust, useFallbackLineSpacing); } /* package */ void init(CharSequence source, TextPaint paint, Alignment align, - BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth) { + BoringLayout.Metrics metrics, boolean includePad, boolean trustWidth, + boolean useFallbackLineSpacing) { int spacing; if (source instanceof String && align == Layout.Alignment.ALIGN_NORMAL) { @@ -260,7 +356,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextLine line = TextLine.obtain(); line.set(paint, source, 0, source.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, - mEllipsizedStart, mEllipsizedStart + mEllipsizedCount); + mEllipsizedStart, mEllipsizedStart + mEllipsizedCount, useFallbackLineSpacing); mMax = (int) Math.ceil(line.metrics(null)); TextLine.recycle(line); } @@ -336,6 +432,24 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback @UnsupportedAppUsage public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { + return isBoring(text, paint, textDir, false /* useFallbackLineSpacing */, metrics); + } + + /** + * Returns null if not boring; the width, ascent, and descent in the + * provided Metrics object (or a new one if the provided one was null) + * if boring. + * + * @param text a text to be calculated text layout. + * @param paint a paint object used for styling. + * @param textDir a text direction. + * @param useFallbackLineSpacing true if use fallback line spacing, otherwise false. + * @param metrics the out metrics. + * @return metrics on success. null if text cannot be rendered by BoringLayout. + */ + public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing, + @Nullable Metrics metrics) { final int textLength = text.length(); if (hasAnyInterestingChars(text, textLength)) { return null; // There are some interesting characters. Not boring. @@ -362,7 +476,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null, 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */, - 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */); + 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */, + useFallbackLineSpacing); fm.width = (int) Math.ceil(line.metrics(fm)); TextLine.recycle(line); @@ -450,6 +565,11 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback return mEllipsizedWidth; } + @Override + public boolean isFallbackLineSpacingEnabled() { + return mUseFallbackLineSpacing; + } + // Override draw so it will be faster. @Override public void draw(Canvas c, Path highlight, Paint highlightpaint, @@ -471,6 +591,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback private String mDirect; private Paint mPaint; + private boolean mUseFallbackLineSpacing; /* package */ int mBottom, mDesc; // for Direct private int mTopPadding, mBottomPadding; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index da3e9b6d509c..95adb7765f1e 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -591,7 +591,8 @@ public abstract class Layout { } else { tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops, getEllipsisStart(lineNum), - getEllipsisStart(lineNum) + getEllipsisCount(lineNum)); + getEllipsisStart(lineNum) + getEllipsisCount(lineNum), + isFallbackLineSpacingEnabled()); if (justify) { tl.justify(right - left - indentWidth); } @@ -960,6 +961,15 @@ public abstract class Layout { } /** + * Return true if the fallback line space is enabled in this Layout. + * + * @return true if the fallback line space is enabled. Otherwise returns false. + */ + public boolean isFallbackLineSpacingEnabled() { + return false; + } + + /** * Returns true if the character at offset and the preceding character * are at different run levels (and thus there's a split caret). * @param offset the offset @@ -1231,7 +1241,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); float wid = tl.measure(offset - start, trailing, null); TextLine.recycle(tl); @@ -1271,7 +1282,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); boolean[] trailings = primaryIsTrailingPreviousAllLineOffsets(line); if (!primary) { for (int offset = 0; offset < trailings.length; ++offset) { @@ -1456,7 +1468,8 @@ public abstract class Layout { paint.setStartHyphenEdit(getStartHyphenEdit(line)); paint.setEndHyphenEdit(getEndHyphenEdit(line)); tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { tl.justify(getJustifyWidth(line)); } @@ -1486,7 +1499,8 @@ public abstract class Layout { paint.setStartHyphenEdit(getStartHyphenEdit(line)); paint.setEndHyphenEdit(getEndHyphenEdit(line)); tl.set(paint, mText, start, end, dir, directions, hasTabs, tabStops, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); if (isJustificationRequired(line)) { tl.justify(getJustifyWidth(line)); } @@ -1572,7 +1586,8 @@ public abstract class Layout { // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here. tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs, false, null, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); final HorizontalMeasurementProvider horizontal = new HorizontalMeasurementProvider(line, primary); @@ -1828,7 +1843,8 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); // XXX: we don't care about tabs tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null, - getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line)); + getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line), + isFallbackLineSpacingEnabled()); caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft); TextLine.recycle(tl); return caret; @@ -2202,7 +2218,8 @@ public abstract class Layout { } } tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops, - 0 /* ellipsisStart */, 0 /* ellipsisEnd */); + 0 /* ellipsisStart */, 0 /* ellipsisEnd */, + false /* use fallback line spacing. unused */); return margin + Math.abs(tl.metrics(null)); } finally { TextLine.recycle(tl); diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 4789231b0404..b1bc7667da16 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -612,7 +612,6 @@ public class StaticLayout extends Layout { TextPaint paint = b.mPaint; int outerWidth = b.mWidth; TextDirectionHeuristic textDir = b.mTextDir; - final boolean fallbackLineSpacing = b.mFallbackLineSpacing; float spacingmult = b.mSpacingMult; float spacingadd = b.mSpacingAdd; float ellipsizedWidth = b.mEllipsizedWidth; @@ -630,6 +629,7 @@ public class StaticLayout extends Layout { mLineCount = 0; mEllipsized = false; mMaxLineHeight = mMaximumVisibleLineCount < 1 ? 0 : DEFAULT_MAX_LINE_HEIGHT; + mFallbackLineSpacing = b.mFallbackLineSpacing; int v = 0; boolean needMultiply = (spacingmult != 1 || spacingadd != 0); @@ -867,17 +867,17 @@ public class StaticLayout extends Layout { boolean moreChars = (endPos < bufEnd); - final int ascent = fallbackLineSpacing + final int ascent = mFallbackLineSpacing ? Math.min(fmAscent, Math.round(ascents[breakIndex])) : fmAscent; - final int descent = fallbackLineSpacing + final int descent = mFallbackLineSpacing ? Math.max(fmDescent, Math.round(descents[breakIndex])) : fmDescent; // The fallback ascent/descent may be larger than top/bottom of the default font // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected // clipping. - if (fallbackLineSpacing) { + if (mFallbackLineSpacing) { if (ascent < fmTop) { fmTop = ascent; } @@ -1381,6 +1381,11 @@ public class StaticLayout extends Layout { return mEllipsizedWidth; } + @Override + public boolean isFallbackLineSpacingEnabled() { + return mFallbackLineSpacing; + } + /** * Return the total height of this layout. * @@ -1407,6 +1412,7 @@ public class StaticLayout extends Layout { @UnsupportedAppUsage private int mColumns; private int mEllipsizedWidth; + private boolean mFallbackLineSpacing; /** * Keeps track if ellipsize is applied to the text. diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 1a7ec7f99c95..2b396612cf3c 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -71,6 +71,8 @@ public class TextLine { private Spanned mSpanned; private PrecomputedText mComputed; + private boolean mUseFallbackExtent = false; + // The start and end of a potentially existing ellipsis on this text line. // We use them to filter out replacement and metric affecting spans on ellipsized away chars. private int mEllipsisStart; @@ -141,6 +143,7 @@ public class TextLine { tl.mTabs = null; tl.mChars = null; tl.mComputed = null; + tl.mUseFallbackExtent = false; tl.mMetricAffectingSpanSpanSet.recycle(); tl.mCharacterStyleSpanSet.recycle(); @@ -171,17 +174,20 @@ public class TextLine { * @param ellipsisStart the start of the ellipsis relative to the line * @param ellipsisEnd the end of the ellipsis relative to the line. When there * is no ellipsis, this should be equal to ellipsisStart. + * @param useFallbackLineSpacing true for enabling fallback line spacing. false for disabling + * fallback line spacing. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops, - int ellipsisStart, int ellipsisEnd) { + int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) { mPaint = paint; mText = text; mStart = start; mLen = limit - start; mDir = dir; mDirections = directions; + mUseFallbackExtent = useFallbackLineSpacing; if (mDirections == null) { throw new IllegalArgumentException("Directions cannot be null"); } @@ -845,6 +851,31 @@ public class TextLine { previousLeading); } + private void expandMetricsFromPaint(TextPaint wp, int start, int end, + int contextStart, int contextEnd, boolean runIsRtl, FontMetricsInt fmi) { + + final int previousTop = fmi.top; + final int previousAscent = fmi.ascent; + final int previousDescent = fmi.descent; + final int previousBottom = fmi.bottom; + final int previousLeading = fmi.leading; + + if (mCharsValid) { + int count = end - start; + int contextCount = contextEnd - contextStart; + wp.getFontMetricsInt(mChars, start, count, contextStart, contextCount, runIsRtl, + fmi); + } else { + int delta = mStart; + wp.getFontMetricsInt(mText, delta + start, delta + end, + delta + contextStart, delta + contextEnd, runIsRtl, fmi); + } + + updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom, + previousLeading); + } + + static void updateMetrics(FontMetricsInt fmi, int previousTop, int previousAscent, int previousDescent, int previousBottom, int previousLeading) { fmi.top = Math.min(fmi.top, previousTop); @@ -949,6 +980,10 @@ public class TextLine { shapeTextRun(consumer, wp, start, end, contextStart, contextEnd, runIsRtl, leftX); } + if (mUseFallbackExtent && fmi != null) { + expandMetricsFromPaint(wp, start, end, contextStart, contextEnd, runIsRtl, fmi); + } + if (c != null) { if (wp.bgColor != 0) { int previousColor = wp.getColor(); diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java index 02fd7b4470f0..a1d6cc8e283a 100644 --- a/core/java/android/text/TextShaper.java +++ b/core/java/android/text/TextShaper.java @@ -222,7 +222,8 @@ public class TextShaper { mp.getDirections(0, count), false /* tabstop is not supported */, null, - -1, -1 // ellipsis is not supported. + -1, -1, // ellipsis is not supported. + false /* fallback line spacing is not used */ ); tl.shape(consumer); } finally { diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java index af76cb914b42..1ed12f74ba2c 100644 --- a/core/java/android/view/BatchedInputEventReceiver.java +++ b/core/java/android/view/BatchedInputEventReceiver.java @@ -66,7 +66,12 @@ public class BatchedInputEventReceiver extends InputEventReceiver { * @hide */ public void setBatchingEnabled(boolean batchingEnabled) { + if (mBatchingEnabled == batchingEnabled) { + return; + } + mBatchingEnabled = batchingEnabled; + mHandler.removeCallbacks(mConsumeBatchedInputEvents); if (!batchingEnabled) { unscheduleBatchedInput(); mHandler.post(mConsumeBatchedInputEvents); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index ab33feae7c56..b7f9be70f7ce 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -231,6 +231,7 @@ public final class SurfaceControl implements Parcelable { float shadowRadius); private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor, @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius); + private static native boolean nativeGetDisplayDecorationSupport(IBinder displayToken); private static native void nativeSetFrameRate(long transactionObj, long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); @@ -2651,6 +2652,20 @@ public final class SurfaceControl implements Parcelable { } /** + * Returns whether a display supports DISPLAY_DECORATION. + * + * @param displayToken + * The token for the display. + * + * @return Whether the display supports DISPLAY_DECORATION. + * + * @hide + */ + public static boolean getDisplayDecorationSupport(IBinder displayToken) { + return nativeGetDisplayDecorationSupport(displayToken); + } + + /** * Adds a callback to be informed about SF's jank classification for a specific surface. * @hide */ diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index d0a5835f8203..49ece5ff1e6a 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -972,9 +972,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); if (mViewVisibility) { - mTmpTransaction.show(mSurfaceControl); + geometryTransaction.show(mSurfaceControl); } else { - mTmpTransaction.hide(mSurfaceControl); + geometryTransaction.hide(mSurfaceControl); } if (mSurfacePackage != null) { diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl index 1cb6825e426e..722546eb06e4 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl @@ -67,4 +67,13 @@ import android.graphics.Rect; */ void onAccessibilityActionPerformed(int displayId); + /** + * Called when the user is performing dragging gesture. It is started after the offset + * between the down location and the move event location exceed + * {@link ViewConfiguration#getScaledTouchSlop()}. + * + * @param displayId The logical display id. + */ + void onDrag(int displayId); + } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 60ce65153890..6284bc2f3513 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1073,7 +1073,7 @@ public class Editor { com.android.internal.R.dimen.textview_error_popup_default_width); final StaticLayout l = StaticLayout.Builder.obtain(text, 0, text.length(), tv.getPaint(), defaultWidthInPixels) - .setUseLineSpacingFromFallbacks(tv.mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(tv.isFallbackLineSpacingForStaticLayout()) .build(); float max = 0; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 014340197393..7327214c5389 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -48,6 +48,9 @@ import android.annotation.XmlRes; import android.app.Activity; import android.app.PendingIntent; import android.app.assist.AssistStructure; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -453,6 +456,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; + /** + * This change ID enables the fallback text line spacing (line height) for BoringLayout. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + public static final long BORINGLAYOUT_FALLBACK_LINESPACING = 210923482L; // buganizer id + + /** + * This change ID enables the fallback text line spacing (line height) for StaticLayout. + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P) + public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id + // System wide time for last cut, copy or text changed action. static long sLastCutCopyOrTextChangedTime; @@ -766,8 +785,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. private final boolean mUseInternationalizedInput; - // True if fallback fonts that end up getting used should be allowed to affect line spacing. - /* package */ boolean mUseFallbackLineSpacing; + + // Fallback fonts that end up getting used should be allowed to affect line spacing. + private static final int FALLBACK_LINE_SPACING_NONE = 0; + private static final int FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY = 1; + private static final int FALLBACK_LINE_SPACING_ALL = 2; + + private int mUseFallbackLineSpacing; // True if the view text can be padded for compat reasons, when the view is translated. private final boolean mUseTextPaddingForUiTranslation; @@ -1479,7 +1503,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; - mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; + if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_ALL; + } else if (CompatChanges.isChangeEnabled(STATICLAYOUT_FALLBACK_LINESPACING)) { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; + } else { + mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE; + } // TODO(b/179693024): Use a ChangeId instead. mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; @@ -4541,8 +4571,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_fallbackLineSpacing */ public void setFallbackLineSpacing(boolean enabled) { - if (mUseFallbackLineSpacing != enabled) { - mUseFallbackLineSpacing = enabled; + int fallbackStrategy; + if (enabled) { + if (CompatChanges.isChangeEnabled(BORINGLAYOUT_FALLBACK_LINESPACING)) { + fallbackStrategy = FALLBACK_LINE_SPACING_ALL; + } else { + fallbackStrategy = FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; + } + } else { + fallbackStrategy = FALLBACK_LINE_SPACING_NONE; + } + if (mUseFallbackLineSpacing != fallbackStrategy) { + mUseFallbackLineSpacing = fallbackStrategy; if (mLayout != null) { nullLayouts(); requestLayout(); @@ -4560,7 +4600,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @InspectableProperty public boolean isFallbackLineSpacing() { - return mUseFallbackLineSpacing; + return mUseFallbackLineSpacing != FALLBACK_LINE_SPACING_NONE; + } + + private boolean isFallbackLineSpacingForBoringLayout() { + return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL; + } + + // Package privte for accessing from Editor.java + /* package */ boolean isFallbackLineSpacingForStaticLayout() { + return mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_ALL + || mUseFallbackLineSpacing == FALLBACK_LINE_SPACING_STATIC_LAYOUT_ONLY; } /** @@ -9148,7 +9198,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (hintBoring == UNKNOWN_BORING) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, - mHintBoring); + isFallbackLineSpacingForBoringLayout(), mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -9190,7 +9240,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9250,7 +9300,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9259,7 +9309,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener result = builder.build(); } else { if (boring == UNKNOWN_BORING) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mBoring); if (boring != null) { mBoring = boring; } @@ -9303,7 +9354,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -9430,7 +9481,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mBoring); if (boring != null) { mBoring = boring; } @@ -9463,7 +9515,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (hintDes < 0) { - hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); + hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, + isFallbackLineSpacingForBoringLayout(), mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -9667,7 +9720,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) - .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .setUseLineSpacingFromFallbacks(isFallbackLineSpacingForStaticLayout()) .setBreakStrategy(getBreakStrategy()) .setHyphenationFrequency(getHyphenationFrequency()) .setJustificationMode(getJustificationMode()) diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 1d13b73fc186..587876df0df6 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -22,6 +22,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.ParcelFileDescriptor; +import android.os.WakeLockStats; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.WifiActivityEnergyInfo; @@ -157,6 +158,10 @@ interface IBatteryStats { /** {@hide} */ GpsBatteryStats getGpsBatteryStats(); + /** {@hide} */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)") + WakeLockStats getWakeLockStats(); + HealthStatsParceler takeUidSnapshot(int uid); HealthStatsParceler[] takeUidSnapshots(in int[] uid); diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 0d4ad382222f..a33b2f18819e 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -41,6 +41,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; @@ -181,6 +183,8 @@ public class InteractionJankMonitor { public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; public static final int CUJ_SCREEN_OFF = 40; public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; + public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; + public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; private static final int NO_STATSD_LOGGING = -1; @@ -231,6 +235,8 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION, }; private static volatile InteractionJankMonitor sInstance; @@ -293,6 +299,8 @@ public class InteractionJankMonitor { CUJ_SPLASHSCREEN_EXIT_ANIM, CUJ_SCREEN_OFF, CUJ_SCREEN_OFF_SHOW_AOD, + CUJ_ONE_HANDED_ENTER_TRANSITION, + CUJ_ONE_HANDED_EXIT_TRANSITION, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -712,6 +720,10 @@ public class InteractionJankMonitor { return "SCREEN_OFF"; case CUJ_SCREEN_OFF_SHOW_AOD: return "SCREEN_OFF_SHOW_AOD"; + case CUJ_ONE_HANDED_ENTER_TRANSITION: + return "ONE_HANDED_ENTER_TRANSITION"; + case CUJ_ONE_HANDED_EXIT_TRANSITION: + return "ONE_HANDED_EXIT_TRANSITION"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 209c64a02324..21f719c74aa8 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.WakeLockStats; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.os.connectivity.CellularBatteryStats; @@ -1143,6 +1144,37 @@ public class BatteryStatsImpl extends BatteryStats { return mKernelWakelockStats; } + @Override + public WakeLockStats getWakeLockStats() { + final long realtimeMs = mClock.elapsedRealtime(); + final long realtimeUs = realtimeMs * 1000; + List<WakeLockStats.WakeLock> uidWakeLockStats = new ArrayList<>(); + for (int i = mUidStats.size() - 1; i >= 0; i--) { + final Uid uid = mUidStats.valueAt(i); + final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = + uid.mWakelockStats.getMap(); + for (int j = wakelockStats.size() - 1; j >= 0; j--) { + final String name = wakelockStats.keyAt(j); + final Uid.Wakelock wakelock = (Uid.Wakelock) wakelockStats.valueAt(j); + final DualTimer timer = wakelock.mTimerPartial; + if (timer != null) { + final long totalTimeLockHeldMs = + timer.getTotalTimeLocked(realtimeUs, STATS_SINCE_CHARGED) / 1000; + if (totalTimeLockHeldMs != 0) { + uidWakeLockStats.add( + new WakeLockStats.WakeLock(uid.getUid(), name, + timer.getCountLocked(STATS_SINCE_CHARGED), + totalTimeLockHeldMs, + timer.isRunningLocked() + ? timer.getCurrentDurationMsLocked(realtimeMs) + : 0)); + } + } + } + } + return new WakeLockStats(uidWakeLockStats); + } + String mLastWakeupReason = null; long mLastWakeupUptimeMs = 0; private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>(); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index f3cdf827984f..a5cf7ce1d37c 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -207,8 +207,10 @@ oneway interface IStatusBar * * @param displayId the ID of the display to notify. * @param types the internal insets types of the bars are about to show transiently. + * @param isGestureOnSystemBar whether the gesture to show the transient bar was a gesture on + * one of the bars itself. */ - void showTransient(int displayId, in int[] types); + void showTransient(int displayId, in int[] types, boolean isGestureOnSystemBar); /** * Notifies System UI to abort the transient state of system bars, which prevents the bars being diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index 04e4819f6ead..d3c3917cd791 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -178,7 +178,7 @@ public class ScreenshotHelper { public ScreenshotHelper(Context context) { mContext = context; IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED); - mContext.registerReceiver(mBroadcastReceiver, filter); + mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); } /** diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS index 554e27890476..1c2d19d94871 100644 --- a/core/java/com/android/server/OWNERS +++ b/core/java/com/android/server/OWNERS @@ -1 +1 @@ -per-file SystemConfig.java = toddke@google.com,patb@google.com +per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 67d0c52960e0..dd5af0435acc 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1768,6 +1768,15 @@ static void nativeSetGlobalShadowSettings(JNIEnv* env, jclass clazz, jfloatArray client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius); } +static jboolean nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz, + jobject displayTokenObject) { + sp<IBinder> displayToken(ibinderForJavaObject(env, displayTokenObject)); + if (displayToken == nullptr) { + return JNI_FALSE; + } + return static_cast<jboolean>(SurfaceComposerClient::getDisplayDecorationSupport(displayToken)); +} + static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { SurfaceControl *surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject); return reinterpret_cast<jlong>(surfaceControl->getHandle().get()); @@ -2092,6 +2101,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeMirrorSurface }, {"nativeSetGlobalShadowSettings", "([F[FFFF)V", (void*)nativeSetGlobalShadowSettings }, + {"nativeGetDisplayDecorationSupport", "(Landroid/os/IBinder;)Z", + (void*)nativeGetDisplayDecorationSupport}, {"nativeGetHandle", "(J)J", (void*)nativeGetHandle }, {"nativeSetFixedTransformHint", "(JJI)V", diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 00b7fd7f0887..4c0ba8cbe304 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -86,6 +86,8 @@ message SecureSettingsProto { optional SettingProto accessibility_floating_menu_opacity = 40 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_floating_menu_fade_enabled = 41 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto odi_captions_volume_ui_enabled = 42 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Setting for accessibility magnification for following typing. + optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 7d44fd99fb2e..9449f606b3aa 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3263,6 +3263,7 @@ </staging-public-group> <staging-public-group type="style" first-id="0x01dd0000"> + <public name="TextAppearance.DeviceDefault.Headline" /> </staging-public-group> <staging-public-group type="string" first-id="0x01dc0000"> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index d8111ea5d8ba..dcfca8497ec6 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -398,7 +398,7 @@ easier. <item name="fontFamily">@string/config_bodyFontFamily</item> </style> <style name="TextAppearance.DeviceDefault.Headline" parent="TextAppearance.Material.Headline"> - <item name="fontFamily">@string/config_bodyFontFamily</item> + <item name="fontFamily">@string/config_headlineFontFamily</item> </style> <style name="TextAppearance.DeviceDefault.Display1" parent="TextAppearance.Material.Display1"> <item name="fontFamily">@string/config_bodyFontFamily</item> diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index 7a2c63f70dd1..fd3079fd295d 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -77,11 +77,11 @@ public class PropertyInvalidatedCacheTests { PropertyInvalidatedCache<Integer, Boolean> testCache = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override - protected Boolean recompute(Integer x) { + public Boolean recompute(Integer x) { return tester.query(x); } @Override - protected boolean bypass(Integer x) { + public boolean bypass(Integer x) { return x % 13 == 0; } }; @@ -131,21 +131,21 @@ public class PropertyInvalidatedCacheTests { PropertyInvalidatedCache<Integer, Boolean> cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override - protected Boolean recompute(Integer x) { + public Boolean recompute(Integer x) { return tester.query(x); } }; PropertyInvalidatedCache<Integer, Boolean> cache2 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override - protected Boolean recompute(Integer x) { + public Boolean recompute(Integer x) { return tester.query(x); } }; PropertyInvalidatedCache<Integer, Boolean> cache3 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") { @Override - protected Boolean recompute(Integer x) { + public Boolean recompute(Integer x) { return tester.query(x); } }; @@ -171,7 +171,7 @@ public class PropertyInvalidatedCacheTests { // Create a new cache1. Verify that the new instance is disabled. cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override - protected Boolean recompute(Integer x) { + public Boolean recompute(Integer x) { return tester.query(x); } }; diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java index 90ce305b3dab..412d6ec975ac 100644 --- a/core/tests/coretests/src/android/text/TextLineTest.java +++ b/core/tests/coretests/src/android/text/TextLineTest.java @@ -48,7 +48,7 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */, - 0, 0 /* no ellipsis */); + 0, 0 /* no ellipsis */, false /* useFallbackLinespace */); final float originalWidth = tl.metrics(null); final float expandedWidth = 2 * originalWidth; @@ -105,7 +105,7 @@ public class TextLineTest { tl.set(paint, str, 0, str.length(), TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(str, 0, str.length()) ? -1 : 1, layout.getLineDirections(0), tabStops != null, tabStops, - 0, 0 /* no ellipsis */); + 0, 0 /* no ellipsis */, false /* useFallbackLineSpacing */); return tl; } @@ -276,7 +276,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertFalse(span.mIsUsed); @@ -292,7 +293,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertTrue(span.mIsUsed); @@ -308,7 +310,8 @@ public class TextLineTest { final TextLine tl = TextLine.obtain(); tl.set(new TextPaint(), text, 0, text.length(), 1, Layout.DIRS_ALL_LEFT_TO_RIGHT, - false /* hasTabs */, null /* tabStops */, 9, 12); + false /* hasTabs */, null /* tabStops */, 9, 12, + false /* useFallbackLineSpacing */); tl.measure(text.length(), false /* trailing */, null /* fmi */); assertTrue(span.mIsUsed); } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 62d0b2e0b52f..02e5942a3544 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -120,10 +120,18 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon return null; } + public Region getCurrentMagnificationRegion(int displayId) { + return null; + } + public boolean resetMagnification(int displayId, boolean animate) { return false; } + public boolean resetCurrentMagnification(int displayId, boolean animate) { + return false; + } + public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, boolean animate) { return false; diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index c1a45c47539d..388cf6e15e0b 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -22,6 +22,8 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED; import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE; import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -36,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.os.BatteryStats; +import android.os.WakeLockStats; import android.util.SparseArray; import android.view.Display; @@ -50,6 +53,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + @LargeTest @RunWith(AndroidJUnit4.class) @SuppressWarnings("GuardedBy") @@ -507,4 +512,51 @@ public class BatteryStatsImplTest { final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid); u.addIsolatedUid(childUid); } + + @Test + public void testGetWakeLockStats() { + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + // First wakelock, acquired once, not currently held + mMockClock.realtime = 1000; + mBatteryStatsImpl.noteStartWakeLocked(10100, 100, null, "wakeLock1", null, + BatteryStats.WAKE_TYPE_PARTIAL, false); + + mMockClock.realtime = 3000; + mBatteryStatsImpl.noteStopWakeLocked(10100, 100, null, "wakeLock1", null, + BatteryStats.WAKE_TYPE_PARTIAL); + + // Second wakelock, acquired twice, still held + mMockClock.realtime = 4000; + mBatteryStatsImpl.noteStartWakeLocked(10200, 101, null, "wakeLock2", null, + BatteryStats.WAKE_TYPE_PARTIAL, false); + + mMockClock.realtime = 5000; + mBatteryStatsImpl.noteStopWakeLocked(10200, 101, null, "wakeLock2", null, + BatteryStats.WAKE_TYPE_PARTIAL); + + mMockClock.realtime = 6000; + mBatteryStatsImpl.noteStartWakeLocked(10200, 101, null, "wakeLock2", null, + BatteryStats.WAKE_TYPE_PARTIAL, false); + + mMockClock.realtime = 9000; + + List<WakeLockStats.WakeLock> wakeLockStats = + mBatteryStatsImpl.getWakeLockStats().getWakeLocks(); + assertThat(wakeLockStats).hasSize(2); + + WakeLockStats.WakeLock wakeLock1 = wakeLockStats.stream() + .filter(wl -> wl.uid == 10100 && wl.name.equals("wakeLock1")).findFirst().get(); + + assertThat(wakeLock1.timesAcquired).isEqualTo(1); + assertThat(wakeLock1.timeHeldMs).isEqualTo(0); // Not currently held + assertThat(wakeLock1.totalTimeHeldMs).isEqualTo(2000); // 3000-1000 + + WakeLockStats.WakeLock wakeLock2 = wakeLockStats.stream() + .filter(wl -> wl.uid == 10200 && wl.name.equals("wakeLock2")).findFirst().get(); + + assertThat(wakeLock2.timesAcquired).isEqualTo(2); + assertThat(wakeLock2.timeHeldMs).isEqualTo(3000); // 9000-6000 + assertThat(wakeLock2.totalTimeHeldMs).isEqualTo(4000); // (5000-4000) + (9000-6000) + } } diff --git a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java index c4080e822a3f..182bf6d165a0 100644 --- a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java +++ b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java @@ -35,7 +35,7 @@ public class PropertyInvalidatedCacheTest extends TestCase { } @Override - protected String recompute(Integer qv) { + public String recompute(Integer qv) { mRecomputeCount += 1; return "foo" + qv.toString(); } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 42e470b7f660..eefad8d0e4de 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Locale; +import java.util.Objects; /** * The Paint class holds the style and color information about how to draw @@ -2131,6 +2132,116 @@ public class Paint { } /** + * Returns the font metrics value for the given text. + * + * If the text is rendered with multiple font files, this function returns the large ascent and + * descent that are enough for drawing all font files. + * + * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari, + * changes letter shape based on its location or surrounding characters. + * + * @param text a text to be measured. + * @param start a starting offset in the text. + * @param count a length of the text to be measured. + * @param contextStart a context starting offset in the text. + * @param contextCount a length of the context to be used. + * @param isRtl true if measuring on RTL context, otherwise false. + * @param outMetrics the output font metrics. + */ + public void getFontMetricsInt( + @NonNull CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int count, + @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, + boolean isRtl, + @NonNull FontMetricsInt outMetrics) { + + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + if (start < 0 || start >= text.length()) { + throw new IllegalArgumentException("start argument is out of bounds."); + } + if (count < 0 || start + count > text.length()) { + throw new IllegalArgumentException("count argument is out of bounds."); + } + if (contextStart < 0 || contextStart >= text.length()) { + throw new IllegalArgumentException("ctxStart argument is out of bounds."); + } + if (contextCount < 0 || contextStart + contextCount > text.length()) { + throw new IllegalArgumentException("ctxCount argument is out of bounds."); + } + if (outMetrics == null) { + throw new IllegalArgumentException("outMetrics must not be null."); + } + + if (count == 0) { + getFontMetricsInt(outMetrics); + return; + } + + if (text instanceof String) { + nGetFontMetricsIntForText(mNativePaint, (String) text, start, count, contextStart, + contextCount, isRtl, outMetrics); + } else { + char[] buf = TemporaryBuffer.obtain(contextCount); + TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0); + nGetFontMetricsIntForText(mNativePaint, buf, start - contextStart, count, 0, + contextCount, isRtl, outMetrics); + } + + } + + /** + * Returns the font metrics value for the given text. + * + * If the text is rendered with multiple font files, this function returns the large ascent and + * descent that are enough for drawing all font files. + * + * The context range is used for shaping context. Some script, e.g. Arabic or Devanagari, + * changes letter shape based on its location or surrounding characters. + * + * @param text a text to be measured. + * @param start a starting offset in the text. + * @param count a length of the text to be measured. + * @param contextStart a context starting offset in the text. + * @param contextCount a length of the context to be used. + * @param isRtl true if measuring on RTL context, otherwise false. + * @param outMetrics the output font metrics. + */ + public void getFontMetricsInt(@NonNull char[] text, + @IntRange(from = 0) int start, @IntRange(from = 0) int count, + @IntRange(from = 0) int contextStart, @IntRange(from = 0) int contextCount, + boolean isRtl, + @NonNull FontMetricsInt outMetrics) { + if (text == null) { + throw new IllegalArgumentException("text must not be null"); + } + if (start < 0 || start >= text.length) { + throw new IllegalArgumentException("start argument is out of bounds."); + } + if (count < 0 || start + count > text.length) { + throw new IllegalArgumentException("count argument is out of bounds."); + } + if (contextStart < 0 || contextStart >= text.length) { + throw new IllegalArgumentException("ctxStart argument is out of bounds."); + } + if (contextCount < 0 || contextStart + contextCount > text.length) { + throw new IllegalArgumentException("ctxCount argument is out of bounds."); + } + if (outMetrics == null) { + throw new IllegalArgumentException("outMetrics must not be null."); + } + + if (count == 0) { + getFontMetricsInt(outMetrics); + return; + } + + nGetFontMetricsIntForText(mNativePaint, text, start, count, contextStart, contextCount, + isRtl, outMetrics); + } + + /** * Convenience method for callers that want to have FontMetrics values as * integers. */ @@ -2163,6 +2274,23 @@ public class Paint { " descent=" + descent + " bottom=" + bottom + " leading=" + leading; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof FontMetricsInt)) return false; + FontMetricsInt that = (FontMetricsInt) o; + return top == that.top + && ascent == that.ascent + && descent == that.descent + && bottom == that.bottom + && leading == that.leading; + } + + @Override + public int hashCode() { + return Objects.hash(top, ascent, descent, bottom, leading); + } } /** @@ -3117,6 +3245,13 @@ public class Paint { int contextStart, int contextEnd, boolean isRtl, int offset); private static native int nGetOffsetForAdvance(long paintPtr, char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance); + private static native void nGetFontMetricsIntForText(long paintPtr, char[] text, + int start, int count, int ctxStart, int ctxCount, boolean isRtl, + FontMetricsInt outMetrics); + private static native void nGetFontMetricsIntForText(long paintPtr, String text, + int start, int count, int ctxStart, int ctxCount, boolean isRtl, + FontMetricsInt outMetrics); + // ---------------- @FastNative ------------------------ @@ -3130,7 +3265,6 @@ public class Paint { @FastNative private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi); - // ---------------- @CriticalNative ------------------------ @CriticalNative diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index a6aa4f21e53b..54bab4ad3780 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -276,7 +276,7 @@ import javax.security.auth.x500.X500Principal; * "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32)); * byte[] associatedData = {}; * return key.decrypt(ciphertext, associatedData); - * } + * }</pre> */ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { private static final X500Principal DEFAULT_ATTESTATION_CERT_SUBJECT = diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml index 4ac972c6cfa7..44b2f45052ba 100644 --- a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml +++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml @@ -21,7 +21,7 @@ android:orientation="vertical" android:clipToPadding="false" android:paddingEnd="@dimen/compat_hint_padding_end" - android:paddingBottom="5dp" + android:paddingBottom="8dp" android:clickable="true"> <TextView diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml index c99f3fe89563..dfaeeeb81c07 100644 --- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml +++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml @@ -33,8 +33,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:clipToPadding="false" - android:layout_marginEnd="16dp" - android:layout_marginBottom="16dp" + android:layout_marginEnd="@dimen/compat_button_margin" + android:layout_marginBottom="@dimen/compat_button_margin" android:orientation="vertical"> <ImageButton @@ -62,8 +62,10 @@ <ImageButton android:id="@+id/size_compat_restart_button" android:visibility="gone" - android:layout_width="@dimen/size_compat_button_width" - android:layout_height="@dimen/size_compat_button_height" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/compat_button_margin" + android:layout_marginBottom="@dimen/compat_button_margin" android:src="@drawable/size_compat_restart_button_ripple" android:background="@android:color/transparent" android:contentDescription="@string/restart_button_description"/> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index d338e3bd74f9..1c19a10ab231 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -200,11 +200,8 @@ <!-- Size of user education views on large screens (phone is just match parent). --> <dimen name="bubbles_user_education_width_large_screen">400dp</dimen> - <!-- The width of the size compat restart button including padding. --> - <dimen name="size_compat_button_width">80dp</dimen> - - <!-- The height of the size compat restart button including padding. --> - <dimen name="size_compat_button_height">64dp</dimen> + <!-- Bottom and end margin for compat buttons. --> + <dimen name="compat_button_margin">16dp</dimen> <!-- The radius of the corners of the compat hint bubble. --> <dimen name="compat_hint_corner_radius">28dp</dimen> @@ -212,8 +209,8 @@ <!-- The width of the compat hint point. --> <dimen name="compat_hint_point_width">10dp</dimen> - <!-- The end padding for the compat hint. Computed as (size_compat_button_width / 2 - - compat_hint_corner_radius - compat_hint_point_width /2). --> + <!-- The end padding for the compat hint. Computed as (compat button width (=48) / 2 + + compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). --> <dimen name="compat_hint_padding_end">7dp</dimen> <!-- The width of the size compat hint. --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 8d43f1375a8c..eaeade82ebb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -108,6 +108,8 @@ public class Bubble implements BubbleViewProvider { private Bitmap mBubbleBitmap; // The app badge for the bubble private Bitmap mBadgeBitmap; + // App badge without any markings for important conversations + private Bitmap mRawBadgeBitmap; private int mDotColor; private Path mDotPath; private int mFlags; @@ -248,6 +250,11 @@ public class Bubble implements BubbleViewProvider { } @Override + public Bitmap getRawAppBadge() { + return mRawBadgeBitmap; + } + + @Override public int getDotColor() { return mDotColor; } @@ -409,6 +416,7 @@ public class Bubble implements BubbleViewProvider { mFlyoutMessage = info.flyoutMessage; mBadgeBitmap = info.badgeBitmap; + mRawBadgeBitmap = info.mRawBadgeBitmap; mBubbleBitmap = info.bubbleBitmap; mDotColor = info.dotColor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index ce1f8707da31..7903a5102dde 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -284,17 +284,21 @@ public class BubbleController { mSyncQueue = syncQueue; } - private static void registerOneHandedState(OneHandedController oneHanded) { + private void registerOneHandedState(OneHandedController oneHanded) { oneHanded.registerTransitionCallback( new OneHandedTransitionCallback() { @Override public void onStartFinished(Rect bounds) { - // TODO(b/198403767) mStackView.offSetY(int bounds.top) + if (mStackView != null) { + mStackView.onVerticalOffsetChanged(bounds.top); + } } @Override public void onStopFinished(Rect bounds) { - // TODO(b/198403767) mStackView.offSetY(int bounds.top) + if (mStackView != null) { + mStackView.onVerticalOffsetChanged(bounds.top); + } } }); } @@ -423,7 +427,7 @@ public class BubbleController { } }); - mOneHandedOptional.ifPresent(BubbleController::registerOneHandedState); + mOneHandedOptional.ifPresent(this::registerOneHandedState); } @VisibleForTesting @@ -1315,6 +1319,7 @@ public class BubbleController { * Updates the visibility of the bubbles based on current state. * Does not un-bubble, just hides or un-hides. * Updates stack description for TalkBack focus. + * Updates bubbles' icon views clickable states */ public void updateStack() { if (mStackView == null) { @@ -1332,6 +1337,8 @@ public class BubbleController { } mStackView.updateContentDescription(); + + mStackView.updateBubblesClickableStates(); } @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index a175929cf498..dd751d24e770 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -141,6 +141,10 @@ class BubbleOverflow( return null } + override fun getRawAppBadge(): Bitmap? { + return null + } + override fun getBubbleIcon(): Bitmap { return bitmap } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 79b765356e9e..7bf4439410f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1485,6 +1485,25 @@ public class BubbleStackView extends FrameLayout } } + /** + * Update bubbles' icon views clickable states. + */ + public void updateBubblesClickableStates() { + for (int i = 0; i < mBubbleData.getBubbles().size(); i++) { + final Bubble bubble = mBubbleData.getBubbles().get(i); + if (bubble.getIconView() != null) { + if (mIsExpanded) { + // when stack is expanded all bubbles are clickable + bubble.getIconView().setClickable(true); + } else { + // when stack is collapsed, only the top bubble needs to be clickable, + // so that a11y ignores all the inaccessible bubbles in the stack + bubble.getIconView().setClickable(i == 0); + } + } + } + } + private void updateSystemGestureExcludeRects() { // Exclude the region occupied by the first BubbleView in the stack Rect excludeZone = mSystemGestureExclusionRects.get(0); @@ -2615,11 +2634,13 @@ public class BubbleStackView extends FrameLayout // If available, update the manage menu's settings option with the expanded bubble's app // name and icon. - if (show && mBubbleData.hasBubbleInStackWithKey(mExpandedBubble.getKey())) { + if (show) { final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey()); - mManageSettingsIcon.setImageBitmap(bubble.getAppBadge()); - mManageSettingsText.setText(getResources().getString( - R.string.bubbles_app_settings, bubble.getAppName())); + if (bubble != null) { + mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge()); + mManageSettingsText.setText(getResources().getString( + R.string.bubbles_app_settings, bubble.getAppName())); + } } if (mExpandedBubble.getExpandedView().getTaskView() != null) { @@ -3013,6 +3034,16 @@ public class BubbleStackView extends FrameLayout } /** + * Handles vertical offset changes, e.g. when one handed mode is switched on/off. + * + * @param offset new vertical offset. + */ + void onVerticalOffsetChanged(int offset) { + // adjust dismiss view vertical position, so that it is still visible to the user + mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset); + } + + /** * Holds some commonly queried information about the stack. */ public static class StackViewState { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 91aff3e8a9e0..b01c756c5a7e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -127,6 +127,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask String appName; Bitmap bubbleBitmap; Bitmap badgeBitmap; + Bitmap mRawBadgeBitmap; int dotColor; Path dotPath; Bubble.FlyoutMessage flyoutMessage; @@ -189,6 +190,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, b.isImportantConversation()); info.badgeBitmap = badgeBitmapInfo.icon; + // Raw badge bitmap never includes the important conversation ring + info.mRawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon; info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable).icon; // Dot color & placement diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java index 7e552826e94a..3f6d41bb2b68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java @@ -43,6 +43,9 @@ public interface BubbleViewProvider { /** App badge drawable to draw above bubble icon. */ @Nullable Bitmap getAppBadge(); + /** Base app badge drawable without any markings. */ + @Nullable Bitmap getRawAppBadge(); + /** Path of normalized bubble icon to draw dot on. */ Path getDotPath(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 4f01dc60452e..cc37caec5225 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -22,6 +22,7 @@ import android.content.pm.LauncherApps; import android.os.Handler; import android.view.WindowManager; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.IconProvider; @@ -143,10 +144,10 @@ public class WMShellModule { static OneHandedController provideOneHandedController(Context context, WindowManager windowManager, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, - UiEventLogger uiEventLogger, @ShellMainThread ShellExecutor mainExecutor, - @ShellMainThread Handler mainHandler) { + UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor, + @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return OneHandedController.create(context, windowManager, displayController, displayLayout, - taskStackListener, uiEventLogger, mainExecutor, mainHandler); + taskStackListener, jankMonitor, uiEventLogger, mainExecutor, mainHandler); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index c9c73fd8f191..96f82fa5ce36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -45,6 +45,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayChangeController; @@ -194,7 +195,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, public static OneHandedController create( Context context, WindowManager windowManager, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, - UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) { + InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger, + ShellExecutor mainExecutor, Handler mainHandler) { OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); @@ -210,13 +212,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mainExecutor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( context, displayLayout, settingsUtil, animationController, tutorialHandler, - oneHandedBackgroundPanelOrganizer, mainExecutor); + oneHandedBackgroundPanelOrganizer, jankMonitor, mainExecutor); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); return new OneHandedController(context, displayController, oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler, - settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, + settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState, jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor, mainHandler); } @@ -232,6 +234,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, OneHandedAccessibilityUtil oneHandedAccessibilityUtil, OneHandedTimeoutHandler timeoutHandler, OneHandedState state, + InteractionJankMonitor jankMonitor, OneHandedUiEventLogger uiEventsLogger, IOverlayManager overlayManager, TaskStackListenerImpl taskStackListener, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index ec3ef5a27fe0..87eb40cbde62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -16,12 +16,15 @@ package com.android.wm.shell.onehanded; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION; import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT; import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER; import android.content.Context; import android.graphics.Rect; import android.os.SystemProperties; +import android.text.TextUtils; import android.util.ArrayMap; import android.view.SurfaceControl; import android.window.DisplayAreaAppearedInfo; @@ -34,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -41,6 +45,7 @@ import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Manages OneHanded display areas such as offset. @@ -62,6 +67,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private final Rect mLastVisualDisplayBounds = new Rect(); private final Rect mDefaultDisplayBounds = new Rect(); private final OneHandedSettingsUtil mOneHandedSettingsUtil; + private final InteractionJankMonitor mJankMonitor; + private final Context mContext; private boolean mIsReady; private float mLastVisualOffset = 0; @@ -95,7 +102,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { public void onOneHandedAnimationEnd(SurfaceControl.Transaction tx, OneHandedAnimationController.OneHandedTransitionAnimator animator) { mAnimationController.removeAnimator(animator.getToken()); + final boolean isEntering = animator.getTransitionDirection() + == TRANSITION_DIRECTION_TRIGGER; if (mAnimationController.isAnimatorsConsumed()) { + endCUJTracing(isEntering ? CUJ_ONE_HANDED_ENTER_TRANSITION + : CUJ_ONE_HANDED_EXIT_TRANSITION); finishOffset((int) animator.getDestinationOffset(), animator.getTransitionDirection()); } @@ -105,7 +116,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { public void onOneHandedAnimationCancel( OneHandedAnimationController.OneHandedTransitionAnimator animator) { mAnimationController.removeAnimator(animator.getToken()); + final boolean isEntering = animator.getTransitionDirection() + == TRANSITION_DIRECTION_TRIGGER; if (mAnimationController.isAnimatorsConsumed()) { + cancelCUJTracing(isEntering ? CUJ_ONE_HANDED_ENTER_TRANSITION + : CUJ_ONE_HANDED_EXIT_TRANSITION); finishOffset((int) animator.getDestinationOffset(), animator.getTransitionDirection()); } @@ -121,11 +136,14 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer, + InteractionJankMonitor jankMonitor, ShellExecutor mainExecutor) { super(mainExecutor); + mContext = context; setDisplayLayout(displayLayout); mOneHandedSettingsUtil = oneHandedSettingsUtil; mAnimationController = animationController; + mJankMonitor = jankMonitor; final int animationDurationConfig = context.getResources().getInteger( R.integer.config_one_handed_translate_animation_duration); mEnterExitAnimationDurationMs = @@ -197,6 +215,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { final int direction = yOffset > 0 ? TRANSITION_DIRECTION_TRIGGER : TRANSITION_DIRECTION_EXIT; + if (direction == TRANSITION_DIRECTION_TRIGGER) { + beginCUJTracing(CUJ_ONE_HANDED_ENTER_TRANSITION, "enterOneHanded"); + } else { + beginCUJTracing(CUJ_ONE_HANDED_EXIT_TRANSITION, "stopOneHanded"); + } mDisplayAreaTokenMap.forEach( (token, leash) -> { animateWindows(token, leash, fromPos, yOffset, direction, @@ -302,6 +325,26 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mTransitionCallbacks.add(callback); } + void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) { + final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry = + getDisplayAreaTokenMap().entrySet().iterator().next(); + final InteractionJankMonitor.Configuration.Builder builder = + InteractionJankMonitor.Configuration.Builder.withSurface( + cujType, mContext, firstEntry.getValue()); + if (!TextUtils.isEmpty(tag)) { + builder.setTag(tag); + } + mJankMonitor.begin(builder); + } + + void endCUJTracing(@InteractionJankMonitor.CujType int cujType) { + mJankMonitor.end(cujType); + } + + void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) { + mJankMonitor.cancel(cujType); + } + void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index c6794b84c42d..1716380c1f7c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; -import static android.window.TransitionInfo.isIndependent; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; @@ -48,7 +47,6 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; -import android.util.ArrayMap; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -62,8 +60,8 @@ import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.transition.CounterRotatorHelper; import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.util.CounterRotator; import java.util.Optional; @@ -175,14 +173,25 @@ public class PipTransition extends PipTransitionController { return true; } + // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can + // happen when a new activity requests enter PIP). In this case, we just show this Task in + // its end state, and play other animation as normal. + final TransitionInfo.Change currentPipChange = findCurrentPipChange(info); + if (currentPipChange != null + && currentPipChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { + resetPrevPip(currentPipChange, startTransaction); + } + // Entering PIP. if (isEnteringPip(info, mCurrentPipTaskToken)) { return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); } - // For transition that we don't animate, we may need to update the PIP surface, otherwise it - // will be reset after the transition. - updatePipForUnhandledTransition(info, startTransaction, finishTransaction); + // For transition that we don't animate, but contains the PIP leash, we need to update the + // PIP surface, otherwise it will be reset after the transition. + if (currentPipChange != null) { + updatePipForUnhandledTransition(currentPipChange, startTransaction, finishTransaction); + } return false; } @@ -322,35 +331,11 @@ public class PipTransition extends PipTransitionController { final int displayH = displayRotationChange.getEndAbsBounds().height(); // Counter-rotate all "going-away" things since they are still in the old orientation. - final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>(); - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (!Transitions.isClosingType(change.getMode()) - || !isIndependent(change, info) - || change.getParent() == null) { - continue; - } - CounterRotator crot = counterRotators.get(change.getParent()); - if (crot == null) { - crot = new CounterRotator(); - crot.setup(startTransaction, - info.getChange(change.getParent()).getLeash(), - rotateDelta, displayW, displayH); - if (crot.getSurface() != null) { - // Wallpaper should be placed at the bottom. - final int layer = (change.getFlags() & FLAG_IS_WALLPAPER) == 0 - ? info.getChanges().size() - i - : -1; - startTransaction.setLayer(crot.getSurface(), layer); - } - counterRotators.put(change.getParent(), crot); - } - crot.addChild(startTransaction, change.getLeash()); - } + final CounterRotatorHelper rotator = new CounterRotatorHelper(); + rotator.handleClosingChanges(info, startTransaction, rotateDelta, displayW, displayH); + mFinishCallback = (wct, wctCB) -> { - for (int i = 0; i < counterRotators.size(); ++i) { - counterRotators.valueAt(i).cleanUp(info.getRootLeash()); - } + rotator.cleanUp(); mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); finishCallback.onTransitionFinished(wct, wctCB); }; @@ -597,30 +582,36 @@ public class PipTransition extends PipTransitionController { finishCallback.onTransitionFinished(null, null); } - private void updatePipForUnhandledTransition(@NonNull TransitionInfo info, + private void resetPrevPip(@NonNull TransitionInfo.Change prevPipChange, + @NonNull SurfaceControl.Transaction startTransaction) { + final SurfaceControl leash = prevPipChange.getLeash(); + final Rect bounds = prevPipChange.getEndAbsBounds(); + final Point offset = prevPipChange.getEndRelOffset(); + bounds.offset(-offset.x, -offset.y); + + startTransaction.setWindowCrop(leash, null); + startTransaction.setMatrix(leash, 1, 0, 0, 1); + startTransaction.setCornerRadius(leash, 0); + startTransaction.setPosition(leash, bounds.left, bounds.top); + + mCurrentPipTaskToken = null; + mPipOrganizer.onExitPipFinished(prevPipChange.getTaskInfo()); + } + + private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { - if (mCurrentPipTaskToken == null) { - return; - } - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (!mCurrentPipTaskToken.equals(change.getContainer())) { - continue; - } - // When the PIP window is visible and being a part of the transition, such as display - // rotation, we need to update its bounds and rounded corner. - final SurfaceControl leash = change.getLeash(); - final Rect destBounds = mPipBoundsState.getBounds(); - final boolean isInPip = mPipTransitionState.isInPip(); - mSurfaceTransactionHelper - .crop(startTransaction, leash, destBounds) - .round(startTransaction, leash, isInPip); - mSurfaceTransactionHelper - .crop(finishTransaction, leash, destBounds) - .round(finishTransaction, leash, isInPip); - break; - } + // When the PIP window is visible and being a part of the transition, such as display + // rotation, we need to update its bounds and rounded corner. + final SurfaceControl leash = pipChange.getLeash(); + final Rect destBounds = mPipBoundsState.getBounds(); + final boolean isInPip = mPipTransitionState.isInPip(); + mSurfaceTransactionHelper + .crop(startTransaction, leash, destBounds) + .round(startTransaction, leash, isInPip); + mSurfaceTransactionHelper + .crop(finishTransaction, leash, destBounds) + .round(finishTransaction, leash, isInPip); } private void finishResizeForMenu(Rect destinationBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java new file mode 100644 index 000000000000..08c99b2c4a83 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; + +import android.util.ArrayMap; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.util.CounterRotator; + +import java.util.List; + +/** + * The helper class that performs counter-rotate for all "going-away" window containers if they are + * still in the old rotation in a transition. + */ +public class CounterRotatorHelper { + private final ArrayMap<WindowContainerToken, CounterRotator> mRotatorMap = new ArrayMap<>(); + private SurfaceControl mRootLeash; + + /** Puts the surface controls of closing changes to counter-rotated surfaces. */ + public void handleClosingChanges(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + int rotateDelta, int displayW, int displayH) { + mRootLeash = info.getRootLeash(); + final List<TransitionInfo.Change> changes = info.getChanges(); + final int numChanges = changes.size(); + for (int i = numChanges - 1; i >= 0; --i) { + final TransitionInfo.Change change = changes.get(i); + final WindowContainerToken parent = change.getParent(); + if (!Transitions.isClosingType(change.getMode()) + || !TransitionInfo.isIndependent(change, info) || parent == null) { + continue; + } + + CounterRotator crot = mRotatorMap.get(parent); + if (crot == null) { + crot = new CounterRotator(); + crot.setup(startTransaction, info.getChange(parent).getLeash(), rotateDelta, + displayW, displayH); + final SurfaceControl rotatorSc = crot.getSurface(); + if (rotatorSc != null) { + // Wallpaper should be placed at the bottom. + final int layer = (change.getFlags() & FLAG_IS_WALLPAPER) == 0 + ? numChanges - i + : -1; + startTransaction.setLayer(rotatorSc, layer); + } + mRotatorMap.put(parent, crot); + } + crot.addChild(startTransaction, change.getLeash()); + } + } + + /** Restores to the original state, i.e. reparent back to transition root. */ + public void cleanUp() { + for (int i = mRotatorMap.size() - 1; i >= 0; --i) { + mRotatorMap.valueAt(i).cleanUp(mRootLeash); + } + mRotatorMap.clear(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 072b9252254e..5833ca80d384 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -43,7 +43,6 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; -import static android.window.TransitionInfo.isIndependent; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; @@ -78,7 +77,6 @@ import android.view.animation.Transformation; import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.R; @@ -92,7 +90,6 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.util.CounterRotator; import java.util.ArrayList; @@ -280,16 +277,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final ArrayList<Animator> animations = new ArrayList<>(); mAnimations.put(transition, animations); - final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>(); + final CounterRotatorHelper rotator = new CounterRotatorHelper(); final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; - for (int i = 0; i < counterRotators.size(); ++i) { - counterRotators.valueAt(i).cleanUp(info.getRootLeash()); - } - counterRotators.clear(); - + rotator.cleanUp(); if (mRotationAnimation != null) { mRotationAnimation.kill(); mRotationAnimation = null; @@ -322,29 +315,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { continue; } } else { - // opening/closing an app into a new orientation. Counter-rotate all - // "going-away" things since they are still in the old orientation. - for (int j = info.getChanges().size() - 1; j >= 0; --j) { - final TransitionInfo.Change innerChange = info.getChanges().get(j); - if (!Transitions.isClosingType(innerChange.getMode()) - || !isIndependent(innerChange, info) - || innerChange.getParent() == null) { - continue; - } - CounterRotator crot = counterRotators.get(innerChange.getParent()); - if (crot == null) { - crot = new CounterRotator(); - crot.setup(startTransaction, - info.getChange(innerChange.getParent()).getLeash(), - rotateDelta, displayW, displayH); - if (crot.getSurface() != null) { - int layer = info.getChanges().size() - j; - startTransaction.setLayer(crot.getSurface(), layer); - } - counterRotators.put(innerChange.getParent(), crot); - } - crot.addChild(startTransaction, innerChange.getLeash()); - } + // Opening/closing an app into a new orientation. + rotator.handleClosingChanges(info, startTransaction, rotateDelta, + displayW, displayH); } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index dbd3d8cde42a..8dd91048d29b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -26,11 +26,9 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,7 +37,7 @@ import org.junit.runners.Parameterized /** * Test Pip with orientation changes. - * To run this test: `atest WMShellFlickerTests:PipOrientationTest` + * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest` */ @RequiresDevice @RunWith(Parameterized::class) @@ -84,8 +82,6 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test fun displayEndsAt90Degrees() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWmEnd { hasRotation(Surface.ROTATION_90) } @@ -93,41 +89,23 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test - override fun navBarLayerIsVisible() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) - super.navBarLayerIsVisible() - } + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() @Presubmit @Test - override fun statusBarLayerIsVisible() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerIsVisible() - } + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() @FlakyTest @Test - override fun navBarLayerRotatesAndScales() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) - super.navBarLayerRotatesAndScales() - } + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() @Presubmit @Test fun pipWindowInsideDisplay() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWmStart { frameRegion(pipApp.component).coversAtMost(startingBounds) } @@ -136,8 +114,6 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test fun pipAppShowsOnTop() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWmEnd { isAppWindowOnTop(pipApp.component) } @@ -146,8 +122,6 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test fun pipLayerInsideDisplay() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayersStart { visibleRegion(pipApp.component).coversAtMost(startingBounds) } @@ -156,8 +130,6 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test fun pipAlwaysVisible() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.isAppWindowVisible(pipApp.component) } @@ -166,8 +138,6 @@ class SetRequestedOrientationWhilePinnedTest( @Presubmit @Test fun pipAppLayerCoversFullScreen() { - // This test doesn't work in shell transitions because of b/208576418 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayersEnd { visibleRegion(pipApp.component).coversExactly(endingBounds) } diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml index 2cdbffa7589c..f40aa66932cd 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml @@ -25,6 +25,7 @@ android:resizeableActivity="true" android:supportsPictureInPicture="true" android:launchMode="singleTop" + android:theme="@style/CutoutShortEdges" android:label="FixedApp" android:exported="true"> <intent-filter> @@ -37,6 +38,7 @@ android:supportsPictureInPicture="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity" + android:theme="@style/CutoutShortEdges" android:launchMode="singleTop" android:label="PipApp" android:exported="true"> @@ -52,6 +54,7 @@ <activity android:name=".ImeActivity" android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity" + android:theme="@style/CutoutShortEdges" android:label="ImeApp" android:launchMode="singleTop" android:exported="true"> @@ -68,6 +71,7 @@ <activity android:name=".SplitScreenActivity" android:resizeableActivity="true" android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity" + android:theme="@style/CutoutShortEdges" android:label="SplitScreenPrimaryApp" android:exported="true"> <intent-filter> @@ -79,6 +83,7 @@ <activity android:name=".SplitScreenSecondaryActivity" android:resizeableActivity="true" android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity" + android:theme="@style/CutoutShortEdges" android:label="SplitScreenSecondaryApp" android:exported="true"> <intent-filter> @@ -90,6 +95,7 @@ <activity android:name=".NonResizeableActivity" android:resizeableActivity="false" android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity" + android:theme="@style/CutoutShortEdges" android:label="NonResizeableApp" android:exported="true"> <intent-filter> @@ -100,6 +106,7 @@ <activity android:name=".SimpleActivity" android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity" + android:theme="@style/CutoutShortEdges" android:label="SimpleApp" android:exported="true"> <intent-filter> @@ -111,6 +118,7 @@ android:name=".LaunchBubbleActivity" android:label="LaunchBubbleApp" android:exported="true" + android:theme="@style/CutoutShortEdges" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -121,6 +129,7 @@ android:name=".BubbleActivity" android:label="BubbleApp" android:exported="false" + android:theme="@style/CutoutShortEdges" android:resizeableActivity="true" /> </application> </manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml new file mode 100644 index 000000000000..87a61a88c094 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <style name="CutoutDefault"> + <item name="android:windowLayoutInDisplayCutoutMode">default</item> + </style> + + <style name="CutoutShortEdges"> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> + + <style name="CutoutNever"> + <item name="android:windowLayoutInDisplayCutoutMode">never</item> + </style> +</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 16bc50750e1d..636e875bed7e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -46,6 +46,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -86,6 +87,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Mock OneHandedUiEventLogger mMockUiEventLogger; @Mock + InteractionJankMonitor mMockJankMonitor; + @Mock IOverlayManager mMockOverlayManager; @Mock TaskStackListenerImpl mMockTaskStackListener; @@ -139,6 +142,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { mOneHandedAccessibilityUtil, mSpiedTimeoutHandler, mSpiedTransitionState, + mMockJankMonitor, mMockUiEventLogger, mMockOverlayManager, mMockTaskStackListener, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index 1d92a48c25fd..df21163c68cd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -50,6 +50,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -99,6 +100,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { ShellExecutor mMockShellMainExecutor; @Mock OneHandedSettingsUtil mMockSettingsUitl; + @Mock + InteractionJankMonitor mJankMonitor; List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>(); @@ -141,6 +144,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockAnimationController, mTutorialHandler, mMockBackgroundOrganizer, + mJankMonitor, mMockShellMainExecutor)); for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) { @@ -428,6 +432,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockAnimationController, mTutorialHandler, mMockBackgroundOrganizer, + mJankMonitor, mMockShellMainExecutor)); assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java index bea69c5d80ef..58399b6444fa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java @@ -40,6 +40,7 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; @@ -78,6 +79,8 @@ public class OneHandedStateTest extends OneHandedTestCase { @Mock OneHandedUiEventLogger mMockUiEventLogger; @Mock + InteractionJankMonitor mMockJankMonitor; + @Mock IOverlayManager mMockOverlayManager; @Mock TaskStackListenerImpl mMockTaskStackListener; @@ -128,6 +131,7 @@ public class OneHandedStateTest extends OneHandedTestCase { mOneHandedAccessibilityUtil, mSpiedTimeoutHandler, mSpiedState, + mMockJankMonitor, mMockUiEventLogger, mMockOverlayManager, mMockTaskStackListener, diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index b8029087cb4f..e359145feef7 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -95,6 +95,16 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, endHyphen, advances); } +minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, + const Typeface* typeface, const uint16_t* buf, + size_t start, size_t count, size_t bufSize) { + minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + + return minikin::getFontExtent(textBuf, range, bidiFlags, minikinPaint); +} + bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs); diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index a15803ad2dca..009b84b140ea 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -56,6 +56,10 @@ public: size_t start, size_t count, size_t bufSize, float* advances); + static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, + const Typeface* typeface, const uint16_t* buf, + size_t start, size_t count, size_t bufSize); + static bool hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs); diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 22a1e1fd94b9..f76863255153 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -541,26 +541,6 @@ namespace PaintGlue { return result; } - // ------------------ @FastNative --------------------------- - - static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { - Paint* obj = reinterpret_cast<Paint*>(objHandle); - ScopedUtfChars localesChars(env, locales); - jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); - obj->setMinikinLocaleListId(minikinLocaleListId); - return minikinLocaleListId; - } - - static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, jstring settings) { - Paint* paint = reinterpret_cast<Paint*>(paintHandle); - if (!settings) { - paint->setFontFeatureSettings(std::string()); - } else { - ScopedUtfChars settingsChars(env, settings); - paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); - } - } - static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { const int kElegantTop = 2500; const int kElegantBottom = -1000; @@ -593,6 +573,67 @@ namespace PaintGlue { return spacing; } + static void doFontExtent(JNIEnv* env, jlong paintHandle, const jchar buf[], jint start, + jint count, jint bufSize, jboolean isRtl, jobject fmi) { + const Paint* paint = reinterpret_cast<Paint*>(paintHandle); + const Typeface* typeface = paint->getAndroidTypeface(); + minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR; + minikin::MinikinExtent extent = + MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize); + + SkFontMetrics metrics; + getMetricsInternal(paintHandle, &metrics); + + metrics.fAscent = extent.ascent; + metrics.fDescent = extent.descent; + + // If top/bottom is narrower than ascent/descent, adjust top/bottom to ascent/descent. + metrics.fTop = std::min(metrics.fAscent, metrics.fTop); + metrics.fBottom = std::max(metrics.fDescent, metrics.fBottom); + + GraphicsJNI::set_metrics_int(env, fmi, metrics); + } + + static void getFontMetricsIntForText___C(JNIEnv* env, jclass, jlong paintHandle, + jcharArray text, jint start, jint count, jint ctxStart, + jint ctxCount, jboolean isRtl, jobject fmi) { + ScopedCharArrayRO textArray(env, text); + + doFontExtent(env, paintHandle, textArray.get() + ctxStart, start - ctxStart, count, + ctxCount, isRtl, fmi); + } + + static void getFontMetricsIntForText___String(JNIEnv* env, jclass, jlong paintHandle, + jstring text, jint start, jint count, + jint ctxStart, jint ctxCount, jboolean isRtl, + jobject fmi) { + ScopedStringChars textChars(env, text); + + doFontExtent(env, paintHandle, textChars.get() + ctxStart, start - ctxStart, count, + ctxCount, isRtl, fmi); + } + + // ------------------ @FastNative --------------------------- + + static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) { + Paint* obj = reinterpret_cast<Paint*>(objHandle); + ScopedUtfChars localesChars(env, locales); + jint minikinLocaleListId = minikin::registerLocaleList(localesChars.c_str()); + obj->setMinikinLocaleListId(minikinLocaleListId); + return minikinLocaleListId; + } + + static void setFontFeatureSettings(JNIEnv* env, jobject clazz, jlong paintHandle, + jstring settings) { + Paint* paint = reinterpret_cast<Paint*>(paintHandle); + if (!settings) { + paint->setFontFeatureSettings(std::string()); + } else { + ScopedUtfChars settingsChars(env, settings); + paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size())); + } + } + static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { SkFontMetrics metrics; SkScalar spacing = getMetricsInternal(paintHandle, &metrics); @@ -1015,6 +1056,11 @@ static const JNINativeMethod methods[] = { {"nGetRunAdvance", "(J[CIIIIZI)F", (void*) PaintGlue::getRunAdvance___CIIIIZI_F}, {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*) PaintGlue::getOffsetForAdvance___CIIIIZF_I}, + {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___C}, + {"nGetFontMetricsIntForText", + "(JLjava/lang/String;IIIIZLandroid/graphics/Paint$FontMetricsInt;)V", + (void*)PaintGlue::getFontMetricsIntForText___String}, // --------------- @FastNative ---------------------- @@ -1093,6 +1139,7 @@ static const JNINativeMethod methods[] = { {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, }; + int register_android_graphics_Paint(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods)); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 61caa0bf4660..9109a18f120e 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -3640,7 +3640,7 @@ public class LocationManager { } @Override - protected Boolean recompute(Integer userId) { + public Boolean recompute(Integer userId) { Preconditions.checkArgument(userId >= 0); if (mManager == null) { diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index f9fc17fdb472..45c2a5a90c70 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -283,9 +283,21 @@ public class Filter implements AutoCloseable { synchronized (mCallbackLock) { if (mCallback != null) { mCallback.onFilterEvent(this, events); + } else { + for (FilterEvent event : events) { + if (event instanceof MediaEvent) { + ((MediaEvent)event).release(); + } + } } } }); + } else { + for (FilterEvent event : events) { + if (event instanceof MediaEvent) { + ((MediaEvent)event).release(); + } + } } } } @@ -558,6 +570,8 @@ public class Filter implements AutoCloseable { if (res != Tuner.RESULT_SUCCESS) { TunerUtils.throwExceptionForResult(res, "Failed to close filter."); } else { + mCallback = null; + mExecutor = null; mIsStarted = false; mIsClosed = true; } diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp index 38bac1c236a9..d3d8bba16c7c 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -25,15 +25,21 @@ filegroup { name: "framework-connectivity-netstats-internal-sources", srcs: [ "src/android/app/usage/*.java", - "src/android/net/DataUsage*.*", - "src/android/net/INetworkStats*.*", - "src/android/net/NetworkIdentity*.java", + "src/android/net/DataUsageRequest.*", + "src/android/net/INetworkStatsService.aidl", + "src/android/net/INetworkStatsSession.aidl", + "src/android/net/NetworkIdentity.java", + "src/android/net/NetworkIdentitySet.java", "src/android/net/NetworkStateSnapshot.*", - "src/android/net/NetworkStats*.*", + "src/android/net/NetworkStats.*", + "src/android/net/NetworkStatsAccess.*", + "src/android/net/NetworkStatsCollection.*", + "src/android/net/NetworkStatsHistory.*", "src/android/net/NetworkTemplate.*", "src/android/net/TrafficStats.java", "src/android/net/UnderlyingNetworkInfo.*", "src/android/net/netstats/**/*.*", + "src/com/android/server/NetworkManagementSocketTagger.java", ], path: "src", visibility: [ diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java index a84e7a9c6344..10a22ac360b1 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java @@ -343,7 +343,7 @@ public final class IpSecAlgorithm implements Parcelable { // Load and validate the optional algorithm resource. Undefined or duplicate algorithms in // the resource are not allowed. final String[] resourceAlgos = systemResources.getStringArray( - com.android.internal.R.array.config_optionalIpSecAlgorithms); + android.R.array.config_optionalIpSecAlgorithms); for (String str : resourceAlgos) { if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) { // This error should be caught by CTS and never be thrown to API callers diff --git a/core/java/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java index d89566c9119c..e35f6a648b77 100644 --- a/core/java/com/android/server/NetworkManagementSocketTagger.java +++ b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java @@ -18,7 +18,6 @@ package com.android.server; import android.os.StrictMode; import android.util.Log; -import android.util.Slog; import dalvik.system.SocketTagger; @@ -122,7 +121,7 @@ public final class NetworkManagementSocketTagger extends SocketTagger { public static void resetKernelUidStats(int uid) { int errno = native_deleteTagData(0, uid); if (errno < 0) { - Slog.w(TAG, "problem clearing counters for uid " + uid + " : errno " + errno); + Log.w(TAG, "problem clearing counters for uid " + uid + " : errno " + errno); } } diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index ced2e22f149d..97281ed42452 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -448,7 +448,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { handlerThread.start(); mHandler = new NetworkStatsHandler(handlerThread.getLooper()); mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext, - mHandler.getLooper(), new HandlerExecutor(mHandler), this); + new HandlerExecutor(mHandler), this); mContentResolver = mContext.getContentResolver(); mContentObserver = mDeps.makeContentObserver(mHandler, mSettings, mNetworkStatsSubscriptionsMonitor); @@ -474,11 +474,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ @NonNull public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor(@NonNull Context context, - @NonNull Looper looper, @NonNull Executor executor, - @NonNull NetworkStatsService service) { + @NonNull Executor executor, @NonNull NetworkStatsService service) { // TODO: Update RatType passively in NSS, instead of querying into the monitor // when notifyNetworkStatus. - return new NetworkStatsSubscriptionsMonitor(context, looper, executor, + return new NetworkStatsSubscriptionsMonitor(context, executor, (subscriberId, type) -> service.handleOnCollapsedRatTypeChanged()); } diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java index 9bb7bb80782b..6df6de32da7a 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -21,7 +21,6 @@ import static android.net.NetworkTemplate.getCollapsedRatType; import android.annotation.NonNull; import android.content.Context; -import android.os.Looper; import android.telephony.Annotation; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneStateListener; @@ -79,9 +78,9 @@ public class NetworkStatsSubscriptionsMonitor extends @NonNull private final Executor mExecutor; - NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Looper looper, + NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Executor executor, @NonNull Delegate delegate) { - super(looper); + super(); mSubscriptionManager = (SubscriptionManager) context.getSystemService( Context.TELEPHONY_SUBSCRIPTION_SERVICE); mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 2b357c57b306..1e8cb9fc4622 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -38,6 +38,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; +import android.util.Log; import android.view.MenuItem; import android.widget.TextView; @@ -54,6 +55,7 @@ import java.util.List; public class RestrictedLockUtilsInternal extends RestrictedLockUtils { private static final String LOG_TAG = "RestrictedLockUtils"; + private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); /** * @return drawables for displaying with settings that are locked by a device admin. @@ -92,14 +94,25 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { } final UserManager um = UserManager.get(context); + final UserHandle userHandle = UserHandle.of(userId); final List<UserManager.EnforcingUser> enforcingUsers = - um.getUserRestrictionSources(userRestriction, UserHandle.of(userId)); + um.getUserRestrictionSources(userRestriction, userHandle); if (enforcingUsers.isEmpty()) { // Restriction is not enforced. return null; - } else if (enforcingUsers.size() > 1) { - return EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(userRestriction); + } + final int size = enforcingUsers.size(); + if (size > 1) { + final EnforcedAdmin enforcedAdmin = EnforcedAdmin + .createDefaultEnforcedAdminWithRestriction(userRestriction); + enforcedAdmin.user = userHandle; + if (DEBUG) { + Log.d(LOG_TAG, "Multiple (" + size + ") enforcing users for restriction '" + + userRestriction + "' on user " + userHandle + "; returning default admin " + + "(" + enforcedAdmin + ")"); + } + return enforcedAdmin; } final int restrictionSource = enforcingUsers.get(0).getUserRestrictionSource(); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 16cece93f2de..8e35ee96b691 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -173,6 +173,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, Settings.Secure.ONE_HANDED_MODE_ACTIVATED, Settings.Secure.ONE_HANDED_MODE_ENABLED, Settings.Secure.ONE_HANDED_MODE_TIMEOUT, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 13c1e512cb14..231252502937 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -267,6 +267,7 @@ public class SecureSettingsValidators { new InclusiveIntegerRangeValidator( Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL)); + VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ACCESSIBILITY_BUTTON_TARGETS, ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index cd6447fae8cd..00cdc9b0a058 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1813,6 +1813,10 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, SecureSettingsProto.Accessibility.ODI_CAPTIONS_VOLUME_UI_ENABLED); + dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, + SecureSettingsProto.Accessibility + .ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED); p.end(accessibilityToken); final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index b19ef3a243aa..c805e2d0330a 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -138,6 +138,7 @@ filegroup { "tests/src/com/android/systemui/statusbar/RankingBuilder.java", "tests/src/com/android/systemui/statusbar/SbnBuilder.java", "tests/src/com/android/systemui/SysuiTestableContext.java", + "tests/src/com/android/systemui/util/**/*Fake.java", "tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java", "tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java", "tests/src/com/android/systemui/**/Fake*.java", diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 092758ee34ed..dee4ff5a0bf5 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -15,12 +15,6 @@ "exclude-annotation": "org.junit.Ignore" }, { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.helpers.Staging" - }, - { "exclude-annotation": "android.platform.test.annotations.Postsubmit" }, { @@ -99,9 +93,6 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" }, { - "exclude-annotation": "android.platform.helpers.Staging" - }, - { "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly" }, { diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 0b3eccfd3a91..29221aa04699 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -28,18 +28,88 @@ import kotlin.math.roundToInt const val TAG = "ColorScheme" const val ACCENT1_CHROMA = 48.0f -const val ACCENT2_CHROMA = 16.0f -const val ACCENT3_CHROMA = 32.0f -const val ACCENT3_HUE_SHIFT = 60.0f +const val GOOGLE_BLUE = 0xFF1b6ef3.toInt() +const val MIN_CHROMA = 5 -const val NEUTRAL1_CHROMA = 4.0f -const val NEUTRAL2_CHROMA = 8.0f +internal enum class ChromaStrategy { + EQ, GTE +} -const val GOOGLE_BLUE = 0xFF1b6ef3.toInt() +internal enum class HueStrategy { + SOURCE, ADD, SUBTRACT +} -const val MIN_CHROMA = 5 +internal class Chroma(val strategy: ChromaStrategy, val value: Double) { + fun get(sourceChroma: Double): Double { + return when (strategy) { + ChromaStrategy.EQ -> value + ChromaStrategy.GTE -> sourceChroma.coerceAtLeast(value) + } + } +} + +internal class Hue(val strategy: HueStrategy = HueStrategy.SOURCE, val value: Double = 0.0) { + fun get(sourceHue: Double): Double { + return when (strategy) { + HueStrategy.SOURCE -> sourceHue + HueStrategy.ADD -> ColorScheme.wrapDegreesDouble(sourceHue + value) + HueStrategy.SUBTRACT -> ColorScheme.wrapDegreesDouble(sourceHue - value) + } + } +} + +internal class TonalSpec(val hue: Hue = Hue(), val chroma: Chroma) { + fun shades(sourceColor: Cam): List<Int> { + val hue = hue.get(sourceColor.hue.toDouble()) + val chroma = chroma.get(sourceColor.chroma.toDouble()) + return Shades.of(hue.toFloat(), chroma.toFloat()).toList() + } +} -public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { +internal class CoreSpec( + val a1: TonalSpec, + val a2: TonalSpec, + val a3: TonalSpec, + val n1: TonalSpec, + val n2: TonalSpec +) + +enum class Style(internal val coreSpec: CoreSpec) { + SPRITZ(CoreSpec( + a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), + a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), + a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)) + )), + TONAL_SPOT(CoreSpec( + a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), + a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), + a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)) + )), + VIBRANT(CoreSpec( + a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)), + a2 = TonalSpec(Hue(HueStrategy.ADD, 10.0), Chroma(ChromaStrategy.EQ, 24.0)), + a3 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.GTE, 32.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)) + )), + EXPRESSIVE(CoreSpec( + a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 40.0), Chroma(ChromaStrategy.GTE, 64.0)), + a2 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.EQ, 24.0)), + a3 = TonalSpec(Hue(HueStrategy.SUBTRACT, 80.0), Chroma(ChromaStrategy.GTE, 64.0)), + n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)), + n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 32.0)) + )), +} + +class ColorScheme( + @ColorInt seed: Int, + val darkTheme: Boolean, + val style: Style = Style.TONAL_SPOT +) { val accent1: List<Int> val accent2: List<Int> @@ -47,6 +117,9 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { val neutral1: List<Int> val neutral2: List<Int> + constructor(@ColorInt seed: Int, darkTheme: Boolean): + this(seed, darkTheme, Style.TONAL_SPOT) + constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean): this(getSeedColor(wallpaperColors), darkTheme) @@ -83,14 +156,11 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { seed } val camSeed = Cam.fromInt(seedArgb) - val hue = camSeed.hue - val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA) - val tertiaryHue = wrapDegrees((hue + ACCENT3_HUE_SHIFT).toInt()) - accent1 = Shades.of(hue, chroma).toList() - accent2 = Shades.of(hue, ACCENT2_CHROMA).toList() - accent3 = Shades.of(tertiaryHue.toFloat(), ACCENT3_CHROMA).toList() - neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList() - neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList() + accent1 = style.coreSpec.a1.shades(camSeed) + accent2 = style.coreSpec.a2.shades(camSeed) + accent3 = style.coreSpec.a3.shades(camSeed) + neutral1 = style.coreSpec.n1.shades(camSeed) + neutral2 = style.coreSpec.n2.shades(camSeed) } override fun toString(): String { @@ -100,6 +170,7 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { " accent1: ${humanReadable(accent1)}\n" + " accent2: ${humanReadable(accent2)}\n" + " accent3: ${humanReadable(accent3)}\n" + + " style: $style\n" + "}" } @@ -225,6 +296,20 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { } } + public fun wrapDegreesDouble(degrees: Double): Double { + return when { + degrees < 0 -> { + (degrees % 360) + 360 + } + degrees >= 360 -> { + degrees % 360 + } + else -> { + degrees + } + } + } + private fun hueDiff(a: Float, b: Float): Float { return 180f - ((a - b).absoluteValue - 180f).absoluteValue } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index ffac26b2e272..1ef532407761 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -159,9 +159,9 @@ public interface QSTile { public Supplier<Icon> iconSupplier; public int state = DEFAULT_STATE; public CharSequence label; - public CharSequence secondaryLabel; + @Nullable public CharSequence secondaryLabel; public CharSequence contentDescription; - public CharSequence stateDescription; + @Nullable public CharSequence stateDescription; public CharSequence dualLabelContentDescription; public boolean disabledByPolicy; public boolean dualTarget = false; @@ -170,6 +170,7 @@ public interface QSTile { public SlashState slash; public boolean handlesLongClick = true; public boolean showRippleEffect = true; + @Nullable public Drawable sideViewCustomDrawable; public String spec; diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml new file mode 100644 index 000000000000..f898ef65213a --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 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. +--> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/dream_overlay_complications_layer" + android:padding="20dp" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <TextClock + android:id="@+id/time_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-thin" + android:format12Hour="h:mm" + android:format24Hour="kk:mm" + android:shadowColor="#B2000000" + android:shadowRadius="2.0" + android:singleLine="true" + android:textSize="72sp" + app:layout_constraintBottom_toTopOf="@+id/date_view" + app:layout_constraintStart_toStartOf="parent" /> + <TextClock + android:id="@+id/date_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:shadowColor="#B2000000" + android:shadowRadius="2.0" + android:format12Hour="EEE, MMM d" + android:format24Hour="EEE, MMM d" + android:singleLine="true" + android:textSize="18sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@+id/time_view" + app:layout_constraintStart_toStartOf="@+id/time_view" /> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml index 4929f502fef0..c6b502ec91b0 100644 --- a/packages/SystemUI/res/layout/dream_overlay_container.xml +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -28,6 +28,8 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> + <include layout="@layout/dream_overlay_complications_layer" /> + <com.android.systemui.dreams.DreamOverlayStatusBarView android:id="@+id/dream_overlay_status_bar" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_container.xml index 63a878f772f9..c717e3756f3d 100644 --- a/packages/SystemUI/res/layout/keyguard_media_header.xml +++ b/packages/SystemUI/res/layout/keyguard_media_container.xml @@ -16,7 +16,7 @@ --> <!-- Layout for media controls on the lockscreen --> -<com.android.systemui.statusbar.notification.stack.MediaHeaderView +<com.android.systemui.statusbar.notification.stack.MediaContainerView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2bf121d2e9ab..2916c1c9b357 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1165,10 +1165,10 @@ <string name="wallet_lockscreen_settings_label">Lock screen settings</string> <!-- QR Code Scanner label, title [CHAR LIMIT=32] --> - <string name="qr_code_scanner_title">Scan QR</string> + <string name="qr_code_scanner_title">QR Code</string> <!-- QR Code Scanner description [CHAR LIMIT=NONE] --> - <string name="qr_code_scanner_description">Click to scan a QR code</string> + <string name="qr_code_scanner_description">Tap to scan</string> <!-- Name of the work status bar icon. --> <string name="status_bar_work">Work profile</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 24e93efa09cb..953b0e018306 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -21,24 +21,24 @@ import android.content.Context import android.hardware.SensorManager import android.hardware.devicestate.DeviceStateManager import android.os.Handler -import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider -import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import com.android.systemui.unfold.updates.DeviceFoldStateProvider +import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener -import java.lang.IllegalStateException +import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import java.util.concurrent.Executor /** * Factory for [UnfoldTransitionProgressProvider]. * - * This is needed as Launcher has to create the object manually. - * Sysui create it using dagger (see [UnfoldTransitionModule]). + * This is needed as Launcher has to create the object manually. Sysui create it using dagger (see + * [UnfoldTransitionModule]). */ fun createUnfoldTransitionProgressProvider( context: Context, @@ -52,10 +52,45 @@ fun createUnfoldTransitionProgressProvider( ): UnfoldTransitionProgressProvider { if (!config.isEnabled) { - throw IllegalStateException("Trying to create " + - "UnfoldTransitionProgressProvider when the transition is disabled") + throw IllegalStateException( + "Trying to create " + + "UnfoldTransitionProgressProvider when the transition is disabled") } + val foldStateProvider = + createFoldStateProvider( + context, + config, + screenStatusProvider, + deviceStateManager, + sensorManager, + mainHandler, + mainExecutor) + + val unfoldTransitionProgressProvider = + if (config.isHingeAngleEnabled) { + PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) + } else { + FixedTimingTransitionProgressProvider(foldStateProvider) + } + + return ScaleAwareTransitionProgressProvider( + unfoldTransitionProgressProvider, context.contentResolver) + .apply { + // Always present callback that logs animation beginning and end. + addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix)) + } +} + +fun createFoldStateProvider( + context: Context, + config: UnfoldTransitionConfig, + screenStatusProvider: ScreenStatusProvider, + deviceStateManager: DeviceStateManager, + sensorManager: SensorManager, + mainHandler: Handler, + mainExecutor: Executor +): FoldStateProvider { val hingeAngleProvider = if (config.isHingeAngleEnabled) { HingeSensorAngleProvider(sensorManager) @@ -63,28 +98,13 @@ fun createUnfoldTransitionProgressProvider( EmptyHingeAngleProvider() } - val foldStateProvider = DeviceFoldStateProvider( + return DeviceFoldStateProvider( context, hingeAngleProvider, screenStatusProvider, deviceStateManager, mainExecutor, - mainHandler - ) - - val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) { - PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) - } else { - FixedTimingTransitionProgressProvider(foldStateProvider) - } - return ScaleAwareTransitionProgressProvider( - unfoldTransitionProgressProvider, - context.contentResolver - ).apply { - // Always present callback that logs animation beginning and end. - addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix)) - } + mainHandler) } -fun createConfig(context: Context): UnfoldTransitionConfig = - ResourceUnfoldTransitionConfig(context) +fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context) diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index dc64f14b830d..a701b44cf916 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -23,7 +23,7 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener -import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN @@ -84,7 +84,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( cancelTransition(endValue = 1f, animate = true) } } - FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> { + FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> { // Do not cancel if we haven't started the transition yet. // This could happen when we fully unfolded the device before the screen // became available. In this case we start and immediately cancel the animation diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 6d9631c12430..cd1ea215ccdd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -138,7 +138,7 @@ class DeviceFoldStateProvider( if (isTransitionInProgess) { cancelTimeout() } - handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS) + handler.postDelayed(timeoutRunnable, HALF_OPENED_TIMEOUT_MILLIS) } private fun cancelTimeout() { @@ -163,16 +163,14 @@ class DeviceFoldStateProvider( } private inner class HingeAngleListener : Consumer<Float> { - override fun accept(angle: Float) { onHingeAngle(angle) } } private inner class TimeoutRunnable : Runnable { - override fun run() { - notifyFoldUpdate(FOLD_UPDATE_ABORTED) + notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) } } } @@ -180,9 +178,7 @@ class DeviceFoldStateProvider( private fun stateToString(@FoldUpdate update: Int): String { return when (update) { FOLD_UPDATE_START_OPENING -> "START_OPENING" - FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN" FOLD_UPDATE_START_CLOSING -> "START_CLOSING" - FOLD_UPDATE_ABORTED -> "ABORTED" FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE" FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN" FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN" @@ -195,11 +191,11 @@ private const val TAG = "DeviceFoldProvider" private const val DEBUG = false /** - * Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or - * [FOLD_UPDATE_START_OPENING] event, if an end state is not reached. + * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a + * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not reached. */ @VisibleForTesting -const val ABORT_CLOSING_MILLIS = 1000L +const val HALF_OPENED_TIMEOUT_MILLIS = 1000L /** Threshold after which we consider the device fully unfolded. */ @VisibleForTesting diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index bffebcd4512b..df3563df5fc6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -37,9 +37,7 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { @IntDef(prefix = ["FOLD_UPDATE_"], value = [ FOLD_UPDATE_START_OPENING, - FOLD_UPDATE_HALF_OPEN, FOLD_UPDATE_START_CLOSING, - FOLD_UPDATE_ABORTED, FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_FINISH_HALF_OPEN, FOLD_UPDATE_FINISH_FULL_OPEN, @@ -50,10 +48,8 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { } const val FOLD_UPDATE_START_OPENING = 0 -const val FOLD_UPDATE_HALF_OPEN = 1 -const val FOLD_UPDATE_START_CLOSING = 2 -const val FOLD_UPDATE_ABORTED = 3 -const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4 -const val FOLD_UPDATE_FINISH_HALF_OPEN = 5 -const val FOLD_UPDATE_FINISH_FULL_OPEN = 6 -const val FOLD_UPDATE_FINISH_CLOSED = 7 +const val FOLD_UPDATE_START_CLOSING = 1 +const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 2 +const val FOLD_UPDATE_FINISH_HALF_OPEN = 3 +const val FOLD_UPDATE_FINISH_FULL_OPEN = 4 +const val FOLD_UPDATE_FINISH_CLOSED = 5 diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index a10efa982701..4784bc12099b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -225,6 +225,13 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie } @Override + public void onDrag(int displayId) { + if (mWindowMagnificationConnectionImpl != null) { + mWindowMagnificationConnectionImpl.onDrag(displayId); + } + } + + @Override public void requestWindowMagnificationConnection(boolean connect) { if (connect) { setWindowMagnificationConnection(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java index 2133da202ce9..1d22633455e9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java @@ -142,4 +142,14 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S } } } + + void onDrag(int displayId) { + if (mConnectionCallback != null) { + try { + mConnectionCallback.onDrag(displayId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to inform taking control by a user", e); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index b064ba904120..aa1a43397f65 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -252,7 +252,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mMagnificationFrame.height()); mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect, Surface.ROTATION_0).apply(); - mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds); + + // Notify source bounds change when the magnifier is not animating. + if (!mAnimationController.isAnimating()) { + mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, + mSourceBounds); + } } }; mUpdateStateDescriptionRunnable = () -> { @@ -596,7 +601,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private void modifyWindowMagnification(SurfaceControl.Transaction t) { mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback); updateMirrorViewLayout(); - } /** @@ -800,7 +804,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold * are as same as current values, or the transition is interrupted * due to the new transition request. */ - void enableWindowMagnification(float scale, float centerX, float centerY, + public void enableWindowMagnification(float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable IRemoteMagnificationAnimationCallback animationCallback) { mAnimationController.enableWindowMagnification(scale, centerX, centerY, @@ -960,6 +964,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold @Override public boolean onDrag(float offsetX, float offsetY) { moveWindowMagnifier(offsetX, offsetY); + mWindowMagnifierCallback.onDrag(mDisplayId); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java index 628a5e88b89e..bdded10dfa1d 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility; import android.graphics.Rect; +import android.view.ViewConfiguration; /** * A callback to inform {@link com.android.server.accessibility.AccessibilityManagerService} about @@ -53,4 +54,13 @@ interface WindowMagnifierCallback { * @param displayId The logical display id. */ void onAccessibilityActionPerformed(int displayId); + + /** + * Called when the user is performing dragging gesture. It is started after the offset + * between the down location and the move event location exceed + * {@link ViewConfiguration#getScaledTouchSlop()}. + * + * @param displayId The logical display id. + */ + void onDrag(int displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java new file mode 100644 index 000000000000..08d969b5eb77 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for lockscreen to shade transition events. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface LSShadeTransitionLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 1f953d7ee6c9..b32358643efb 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -61,6 +61,14 @@ public class LogModule { return factory.create("NotifHeadsUpLog", 1000); } + /** Provides a logging buffer for all logs for lockscreen to shade transition events. */ + @Provides + @SysUISingleton + @LSShadeTransitionLog + public static LogBuffer provideLSShadeTransitionControllerBuffer(LogBufferFactory factory) { + return factory.create("LSShadeTransitionLog", 50); + } + /** Provides a logging buffer for all logs related to managing notification sections. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 5ff624db33c7..44727f29888d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -27,7 +27,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.notification.stack.MediaHeaderView +import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.Utils @@ -96,14 +96,14 @@ class KeyguardMediaController @Inject constructor( /** * single pane media container placed at the top of the notifications list */ - var singlePaneContainer: MediaHeaderView? = null + var singlePaneContainer: MediaContainerView? = null private set private var splitShadeContainer: ViewGroup? = null /** * Attaches media container in single pane mode, situated at the top of the notifications list */ - fun attachSinglePaneContainer(mediaView: MediaHeaderView?) { + fun attachSinglePaneContainer(mediaView: MediaContainerView?) { val needsListener = singlePaneContainer == null singlePaneContainer = mediaView if (needsListener) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index ce3b443e677e..29321b46328a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -865,6 +865,10 @@ class MediaCarouselController @Inject constructor( println("playerKeys: ${MediaPlayerData.playerKeys()}") println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}") println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}") + println("current size: $currentCarouselWidth x $currentCarouselHeight") + println("location: $desiredLocation") + println("state: ${desiredHostState?.expansion}, " + + "only active ${desiredHostState?.showsOnlyActiveMedia}") } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 9c4227e25567..d190dcb3ffb8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -227,6 +227,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private @Behavior int mBehavior; private boolean mTransientShown; + private boolean mTransientShownFromGestureOnSystemBar; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; private LightBarController mLightBarController; private final LightBarController mMainLightBarController; @@ -871,6 +872,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, + windowStateToString(mNavigationBarWindowState)); pw.println(" mNavigationBarMode=" + BarTransitions.modeToString(mNavigationBarMode)); + pw.println(" mTransientShown=" + mTransientShown); + pw.println(" mTransientShownFromGestureOnSystemBar=" + + mTransientShownFromGestureOnSystemBar); dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions()); mNavigationBarView.dump(pw); } @@ -989,7 +993,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types) { + public void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar) { if (displayId != mDisplayId) { return; } @@ -998,6 +1003,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } if (!mTransientShown) { mTransientShown = true; + mTransientShownFromGestureOnSystemBar = isGestureOnSystemBar; handleTransientChanged(); } } @@ -1016,12 +1022,14 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private void clearTransient() { if (mTransientShown) { mTransientShown = false; + mTransientShownFromGestureOnSystemBar = false; handleTransientChanged(); } } private void handleTransientChanged() { - mNavigationBarView.onTransientStateChanged(mTransientShown); + mNavigationBarView.onTransientStateChanged(mTransientShown, + mTransientShownFromGestureOnSystemBar); final int barMode = barMode(mTransientShown, mAppearance); if (updateBarMode(barMode) && mLightBarController != null) { mLightBarController.onNavigationBarModeChanged(barMode); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 7adb7ac92dc1..5fbdd88b9f66 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -447,12 +447,17 @@ public class NavigationBarView extends FrameLayout implements mRegionSamplingHelper.setWindowHasBlurs(hasBlurs); } - void onTransientStateChanged(boolean isTransient) { + void onTransientStateChanged(boolean isTransient, boolean isGestureOnSystemBar) { mEdgeBackGestureHandler.onNavBarTransientStateChanged(isTransient); // The visibility of the navigation bar buttons is dependent on the transient state of // the navigation bar. if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + // Always allow the overlay if in non-gestural nav mode, otherwise, only allow showing + // the overlay if the user is swiping directly over a system bar + boolean allowNavBarOverlay = !QuickStepContract.isGesturalMode(mNavBarMode) + || isGestureOnSystemBar; + isTransient = isTransient && allowNavBarOverlay; mNavBarOverlayController.setButtonState(isTransient, /* force */ false); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index feda99fd3471..002dd10f7356 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -19,7 +19,7 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -86,6 +86,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private static final String TAG = TaskbarDelegate.class.getSimpleName(); private final EdgeBackGestureHandler mEdgeBackGestureHandler; + private final NavigationBarOverlayController mNavBarOverlayController; private boolean mInitialized; private CommandQueue mCommandQueue; private OverviewProxyService mOverviewProxyService; @@ -140,6 +141,13 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void hide() { + clearTransient(); + } + }; + + private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> { + if (visible) { + mAutoHideController.touchAutoHide(); } }; @@ -147,6 +155,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, public TaskbarDelegate(Context context) { mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class) .create(context); + mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.init(mNavbarOverlayVisibilityChangeCallback, + mEdgeBackGestureHandler::updateNavigationBarOverlayExcludeRegion); + } mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); mPipListener = mEdgeBackGestureHandler::setPipStashExclusionBounds; @@ -206,6 +219,9 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mNavigationModeController.addListener(this)); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mNavBarHelper.init(); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.registerListeners(); + } mEdgeBackGestureHandler.onNavBarAttached(); // Initialize component callback Display display = mDisplayManager.getDisplay(displayId); @@ -229,6 +245,9 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mNavigationModeController.removeListener(this); mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mNavBarHelper.destroy(); + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.unregisterListeners(); + } mEdgeBackGestureHandler.onNavBarDetached(); mScreenPinningNotify = null; if (mWindowContext != null) { @@ -350,14 +369,17 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override - public void showTransient(int displayId, int[] types) { + public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) { if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_NAVIGATION_BAR)) { + if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) { return; } - mTaskbarTransientShowing = true; + if (!mTaskbarTransientShowing) { + mTaskbarTransientShowing = true; + onTransientStateChanged(); + } } @Override @@ -365,15 +387,14 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, if (displayId != mDisplayId) { return; } - if (!containsType(types, ITYPE_NAVIGATION_BAR)) { + if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) { return; } - mTaskbarTransientShowing = false; + clearTransient(); } @Override public void onTaskbarAutohideSuspend(boolean suspend) { - mTaskbarTransientShowing = suspend; if (suspend) { mAutoHideController.suspendAutoHide(); } else { @@ -381,6 +402,30 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } } + private void clearTransient() { + if (mTaskbarTransientShowing) { + mTaskbarTransientShowing = false; + onTransientStateChanged(); + } + } + + private void onTransientStateChanged() { + mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTaskbarTransientShowing); + + // The visibility of the navigation bar buttons is dependent on the transient state of + // the navigation bar. + if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) { + mNavBarOverlayController.setButtonState(mTaskbarTransientShowing, /* force */ false); + } + } + + @Override + public void onRecentsAnimationStateChanged(boolean running) { + if (running) { + mNavBarOverlayController.setButtonState(/* visible */false, /* force */true); + } + } + @Override public void onNavigationModeChanged(int mode) { mNavigationMode = mode; diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java index 1195184050e3..18d28bf511bc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoSizingList.java @@ -36,6 +36,7 @@ public class AutoSizingList extends LinearLayout { private final int mItemSize; private final Handler mHandler; + @Nullable private ListAdapter mAdapter; private int mCount; private boolean mEnableAutoSizing; diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 6d1f8f7d01b1..d20141b33838 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -19,6 +19,7 @@ import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; import android.widget.Scroller; +import androidx.annotation.Nullable; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -51,14 +52,17 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private final ArrayList<TileRecord> mTiles = new ArrayList<>(); private final ArrayList<TileLayout> mPages = new ArrayList<>(); + @Nullable private PageIndicator mPageIndicator; private float mPageIndicatorPosition; + @Nullable private PageListener mPageListener; private boolean mListening; private Scroller mScroller; + @Nullable private AnimatorSet mBounceAnimatorSet; private float mLastExpansion; private boolean mDistributeTiles = false; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 6c7d6e01e9ec..e06b768dbe6f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -26,6 +26,8 @@ import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; +import androidx.annotation.Nullable; + import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSTile; @@ -88,6 +90,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final View mQSFooterActions; private final View mQQSFooterActions; + @Nullable private PagedTileLayout mPagedLayout; private boolean mOnFirstPage = true; @@ -95,6 +98,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; // Animator for elements in the first page, including secondary labels and qqs brightness // slider, as well as animating the alpha of the QS tile layout (as we are tracking QQS tiles) + @Nullable private TouchAnimator mFirstPageAnimator; // TranslationX animator for QQS/QS tiles private TouchAnimator mTranslationXAnimator; @@ -109,13 +113,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // This animates fading of SecurityFooter and media divider private TouchAnimator mAllPagesDelayedAnimator; // Animator for brightness slider(s) + @Nullable private TouchAnimator mBrightnessAnimator; // Animator for Footer actions in QQS private TouchAnimator mQQSFooterActionsAnimator; // Height animator for QQS tiles (height changing from QQS size to QS size) + @Nullable private HeightExpansionAnimator mQQSTileHeightAnimator; // Height animator for QS tile in first page but not in QQS, to present the illusion that they // are expanding alongside the QQS tiles + @Nullable private HeightExpansionAnimator mOtherFirstPageTilesHeightAnimator; // Pair of animators for each non first page. The creation is delayed until the user first // scrolls to that page, in order to get the proper measures and layout. @@ -215,7 +222,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } @Override - public void onViewAttachedToWindow(View v) { + public void onViewAttachedToWindow(@Nullable View v) { mTunerService.addTunable(this, ALLOW_FANCY_ANIMATION, MOVE_FULL_ROWS); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index d43404b8781c..04e22522bcd0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -64,15 +64,18 @@ public class QSDetail extends LinearLayout { protected TextView mDetailDoneButton; @VisibleForTesting QSDetailClipper mClipper; + @Nullable private DetailAdapter mDetailAdapter; private QSPanelController mQsPanelController; protected View mQsDetailHeader; protected TextView mQsDetailHeaderTitle; private ViewStub mQsDetailHeaderSwitchStub; + @Nullable private Switch mQsDetailHeaderSwitch; protected ImageView mQsDetailHeaderProgress; + @Nullable protected QSTileHost mHost; private boolean mScanState; @@ -87,6 +90,7 @@ public class QSDetail extends LinearLayout { private boolean mSwitchState; private QSFooter mFooter; + @Nullable private QSContainerController mQsContainerController; public QSDetail(Context context, @Nullable AttributeSet attrs) { @@ -183,12 +187,14 @@ public class QSDetail extends LinearLayout { } public interface Callback { - void onShowingDetail(DetailAdapter detail, int x, int y); + /** Handle an event of showing detail. */ + void onShowingDetail(@Nullable DetailAdapter detail, int x, int y); void onToggleStateChanged(boolean state); void onScanStateChanged(boolean state); } - public void handleShowingDetail(final DetailAdapter adapter, int x, int y, + /** Handle an event of showing detail. */ + public void handleShowingDetail(final @Nullable DetailAdapter adapter, int x, int y, boolean toggleQs) { final boolean showingDetail = adapter != null; final boolean wasShowingDetail = mDetailAdapter != null; @@ -378,7 +384,8 @@ public class QSDetail extends LinearLayout { } @Override - public void onShowingDetail(final DetailAdapter detail, final int x, final int y) { + public void onShowingDetail( + final @Nullable DetailAdapter detail, final int x, final int y) { post(new Runnable() { @Override public void run() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java index 63cedd081fb1..43136d32c68f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java @@ -23,12 +23,15 @@ import android.graphics.drawable.TransitionDrawable; import android.view.View; import android.view.ViewAnimationUtils; +import androidx.annotation.Nullable; + /** Helper for quick settings detail panel clip animations. **/ public class QSDetailClipper { private final View mDetail; private final TransitionDrawable mBackground; + @Nullable private Animator mAnimator; public QSDetailClipper(View detail) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java index b50af004aff9..afd4f0f7d1e2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailDisplayer.java @@ -16,6 +16,8 @@ package com.android.systemui.qs; +import androidx.annotation.Nullable; + import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.qs.DetailAdapter; @@ -26,13 +28,14 @@ import javax.inject.Inject; */ @SysUISingleton public class QSDetailDisplayer { + @Nullable private QSPanelController mQsPanelController; @Inject public QSDetailDisplayer() { } - public void setQsPanelController(QSPanelController qsPanelController) { + public void setQsPanelController(@Nullable QSPanelController qsPanelController) { mQsPanelController = qsPanelController; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java index e93c349ee41f..eb3247b53823 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java @@ -33,6 +33,8 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile; @@ -50,6 +52,7 @@ public class QSDetailItems extends FrameLayout { private final Adapter mAdapter = new Adapter(); private String mTag; + @Nullable private Callback mCallback; private boolean mItemsVisible = true; private AutoSizingList mItemList; @@ -130,7 +133,8 @@ public class QSDetailItems extends FrameLayout { mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget(); } - public void setItems(Item[] items) { + /** Set items. */ + public void setItems(@Nullable Item[] items) { mHandler.removeMessages(H.SET_ITEMS); mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget(); } @@ -266,9 +270,12 @@ public class QSDetailItems extends FrameLayout { } public int iconResId; + @Nullable public QSTile.Icon icon; + @Nullable public Drawable overlay; public CharSequence line1; + @Nullable public CharSequence line2; public Object tag; public boolean canDisconnect; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 4d23958d56ab..066a286b271d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -48,6 +48,7 @@ public class QSFooterView extends FrameLayout { private TextView mBuildText; private View mActionsContainer; + @Nullable protected TouchAnimator mFooterAnimator; private boolean mQsDisabled; @@ -56,6 +57,7 @@ public class QSFooterView extends FrameLayout { private boolean mShouldShowBuildText; + @Nullable private OnClickListener mExpandClickListener; private final ContentObserver mDeveloperSettingsObserver = new ContentObserver( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index bb8a1e8e22a4..41dced6bffeb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -117,6 +117,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private QSPanelController mQSPanelController; private QuickQSPanelController mQuickQSPanelController; private QSCustomizerController mQSCustomizerController; + @Nullable private ScrollListener mScrollListener; /** * When true, QS will translate from outside the screen. It will be clipped with parallax diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index ff9790c3595f..d4d6da8f5910 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -401,6 +401,9 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr pw.print(" "); pw.println(record.tileView.toString()); } } + if (mMediaHost != null) { + pw.println(" media bounds: " + mMediaHost.getCurrentBounds()); + } } public QSPanel.QSTileLayout getTileLayout() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 7f19d0e6c25c..878f7530fa8b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -48,6 +48,7 @@ import android.view.Window; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; @@ -85,8 +86,10 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen protected H mHandler; private boolean mIsVisible; + @Nullable private CharSequence mFooterTextContent = null; private int mFooterIconId; + @Nullable private Drawable mPrimaryFooterIconDrawable; @Inject @@ -240,6 +243,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen mMainHandler.post(mUpdateDisplayState); } + @Nullable protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile, boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, @@ -497,6 +501,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen return mContext.getString(R.string.ok); } + @Nullable private String getNegativeButton() { if (mSecurityController.isParentalControlsEnabled()) { return mContext.getString(R.string.monitoring_button_view_controls); @@ -504,6 +509,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen return null; } + @Nullable protected CharSequence getManagementMessage(boolean isDeviceManaged, CharSequence organizationName) { if (!isDeviceManaged) { @@ -521,6 +527,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen return mContext.getString(R.string.monitoring_description_management); } + @Nullable protected CharSequence getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts, boolean hasCACertsInWorkProfile) { if (!(hasCACerts || hasCACertsInWorkProfile)) return null; @@ -534,6 +541,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen return mContext.getString(R.string.monitoring_description_ca_certificate); } + @Nullable protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged, boolean isNetworkLoggingEnabled) { if (!isNetworkLoggingEnabled) return null; @@ -545,6 +553,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen } } + @Nullable protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile) { if (vpnName == null && vpnNameWorkProfile == null) return null; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 1030c21ecc79..cca491343f76 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -29,6 +29,8 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.UiEventLogger; @@ -98,6 +100,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final CustomTileStatePersister mCustomTileStatePersister; private final List<Callback> mCallbacks = new ArrayList<>(); + @Nullable private AutoTileManager mAutoTiles; private final StatusBarIconController mIconController; private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); @@ -472,6 +475,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D saveTilesToSettings(newTiles); } + /** Create a {@link QSTile} of a {@code tileSpec} type. */ + @Nullable public QSTile createTile(String tileSpec) { for (int i = 0; i < mQsFactories.size(); i++) { QSTile t = mQsFactories.get(i).createTile(tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 8b9498394384..866b1b8cabb2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -33,6 +33,7 @@ import android.widget.LinearLayout; import android.widget.Space; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.policy.SystemBarUtils; import com.android.settingslib.Utils; @@ -44,7 +45,6 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconMa import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.VariableDateView; -import com.android.systemui.statusbar.window.StatusBarWindowView; import java.util.List; @@ -57,8 +57,11 @@ public class QuickStatusBarHeader extends FrameLayout { private boolean mExpanded; private boolean mQsDisabled; + @Nullable private TouchAnimator mAlphaAnimator; + @Nullable private TouchAnimator mTranslationAnimator; + @Nullable private TouchAnimator mIconsAlphaAnimator; private TouchAnimator mIconsAlphaAnimatorFixed; @@ -85,7 +88,9 @@ public class QuickStatusBarHeader extends FrameLayout { private StatusIconContainer mIconContainer; private View mPrivacyChip; + @Nullable private TintedIconManager mTintedIconManager; + @Nullable private QSExpansionPathInterpolator mQSExpansionPathInterpolator; private StatusBarContentInsetsProvider mInsetsProvider; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QuickTileLayout.java index bb2340c2cad5..130bcab84f4a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickTileLayout.java @@ -7,13 +7,15 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; +import androidx.annotation.Nullable; + public class QuickTileLayout extends LinearLayout { public QuickTileLayout(Context context) { this(context, null); } - public QuickTileLayout(Context context, AttributeSet attrs) { + public QuickTileLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setGravity(Gravity.CENTER); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java b/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java index a9b2376e46e5..90118539c912 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java @@ -60,7 +60,9 @@ public class SlashDrawable extends Drawable { private final RectF mSlashRect = new RectF(0, 0, 0, 0); private float mRotation; private boolean mSlashed; + @Nullable private Mode mTintMode; + @Nullable private ColorStateList mTintList; private boolean mAnimationEnabled = true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index bff318a6f44e..da82d2cb7e8d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -9,6 +9,8 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.Nullable; + import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.qs.QSPanel.QSTileLayout; @@ -49,7 +51,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { this(context, null); } - public TileLayout(Context context, AttributeSet attrs) { + public TileLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); setFocusableInTouchMode(true); mLessRows = ((Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0) @@ -67,7 +69,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } @Override - public void setListening(boolean listening, UiEventLogger uiEventLogger) { + public void setListening(boolean listening, @Nullable UiEventLogger uiEventLogger) { if (mListening == listening) return; mListening = listening; for (TileRecord record : mRecords) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java index ca8f68160454..bc62416ca3a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java @@ -20,6 +20,8 @@ import android.util.Property; import android.view.View; import android.view.animation.Interpolator; +import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.List; @@ -37,12 +39,19 @@ public class TouchAnimator { private final float mStartDelay; private final float mEndDelay; private final float mSpan; + @Nullable private final Interpolator mInterpolator; + @Nullable private final Listener mListener; private float mLastT = -1; - private TouchAnimator(Object[] targets, KeyframeSet[] keyframeSets, - float startDelay, float endDelay, Interpolator interpolator, Listener listener) { + private TouchAnimator( + Object[] targets, + KeyframeSet[] keyframeSets, + float startDelay, + float endDelay, + @Nullable Interpolator interpolator, + @Nullable Listener listener) { mTargets = targets; mKeyframeSets = keyframeSets; mStartDelay = startDelay; @@ -126,7 +135,9 @@ public class TouchAnimator { private float mStartDelay; private float mEndDelay; + @Nullable private Interpolator mInterpolator; + @Nullable private Listener mListener; public Builder addFloat(Object target, String property, float... values) { @@ -183,7 +194,8 @@ public class TouchAnimator { return this; } - public Builder setInterpolator(Interpolator intepolator) { + /** Sets interpolator. */ + public Builder setInterpolator(@Nullable Interpolator intepolator) { mInterpolator = intepolator; return this; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java index 32ac73375f1d..2959c3b30eec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java @@ -25,6 +25,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; @@ -40,6 +41,7 @@ public class QSCarrier extends LinearLayout { private ImageView mMobileSignal; private ImageView mMobileRoaming; private View mSpacer; + @Nullable private CellSignalState mLastSignalState; private boolean mProviderModelInitialized = false; private boolean mIsSingleCarrier; @@ -125,7 +127,7 @@ public class QSCarrier extends LinearLayout { return true; } - private boolean hasValidTypeContentDescription(String typeContentDescription) { + private boolean hasValidTypeContentDescription(@Nullable String typeContentDescription) { return TextUtils.equals(typeContentDescription, mContext.getString(R.string.data_connection_no_internet)) || TextUtils.equals(typeContentDescription, diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 9ebdb1cf35fe..b59c0ccc510a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -28,6 +28,7 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.Toolbar; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.RecyclerView; @@ -107,7 +108,7 @@ public class QSCustomizer extends LinearLayout { mQsContainerController = controller; } - public void setQs(QS qs) { + public void setQs(@Nullable QS qs) { mQs = qs; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index 618a429f6b51..97390112c3ed 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -28,6 +28,7 @@ import android.widget.TextView; import android.widget.Toolbar; import android.widget.Toolbar.OnMenuItemClickListener; +import androidx.annotation.Nullable; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -198,7 +199,7 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { } /** */ - public void setQs(QSFragment qsFragment) { + public void setQs(@Nullable QSFragment qsFragment) { mView.setQs(qsFragment); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index eae256532c54..b29687fe74c3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -31,6 +31,8 @@ import android.text.TextUtils; import android.util.ArraySet; import android.widget.Button; +import androidx.annotation.Nullable; + import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -79,7 +81,7 @@ public class TileQueryHelper { mUserTracker = userTracker; } - public void setListener(TileStateListener listener) { + public void setListener(@Nullable TileStateListener listener) { mListener = listener; } @@ -269,6 +271,7 @@ public class TileQueryHelper { }); } + @Nullable private State getState(Collection<QSTile> tiles, String spec) { for (QSTile tile : tiles) { if (spec.equals(tile.getTileSpec())) { @@ -278,7 +281,8 @@ public class TileQueryHelper { return null; } - private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) { + private void addTile( + String spec, @Nullable CharSequence appLabel, State state, boolean isSystem) { if (mSpecs.contains(spec)) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 10efec309971..4f15351322ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -89,7 +89,9 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final TileServiceManager mServiceManager; private final int mUser; private final CustomTileStatePersister mCustomTileStatePersister; + @Nullable private android.graphics.drawable.Icon mDefaultIcon; + @Nullable private CharSequence mDefaultLabel; private final Context mUserContext; @@ -197,8 +199,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener /** * Compare two icons, only works for resources. */ - private boolean iconEquals(android.graphics.drawable.Icon icon1, - android.graphics.drawable.Icon icon2) { + private boolean iconEquals(@Nullable android.graphics.drawable.Icon icon1, + @Nullable android.graphics.drawable.Icon icon2) { if (icon1 == icon2) { return true; } @@ -372,6 +374,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener Uri.fromParts("package", mComponent.getPackageName(), null)); } + @Nullable private Intent resolveIntent(Intent i) { ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, mUser); return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES) diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index e6612fe8f08b..17ae7ffa9980 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -36,6 +36,7 @@ import android.service.quicksettings.TileService; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -85,6 +86,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final BroadcastDispatcher mBroadcastDispatcher; private Set<Integer> mQueuedMessages = new ArraySet<>(); + @Nullable private QSTileServiceWrapper mWrapper; private boolean mListening; private IBinder mClickBinder; @@ -95,6 +97,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); private boolean mUnbindImmediate; + @Nullable private TileChangeListener mChangeListener; // Return value from bindServiceAsUser, determines whether safe to call unbind. private boolean mIsBound; @@ -466,6 +469,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements } } + @Nullable @Override public IBinder asBinder() { return mWrapper != null ? mWrapper.asBinder() : null; diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index bfa2aaa4e785..0a3c17c9045a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -35,6 +35,8 @@ import android.service.quicksettings.TileService; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Dependency; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -297,6 +299,7 @@ public class TileServices extends IQSService.Stub { } } + @Nullable @Override public Tile getTile(IBinder token) { CustomTile customTile = getTileForToken(token); @@ -330,12 +333,14 @@ public class TileServices extends IQSService.Stub { return keyguardStateController.isMethodSecure() && keyguardStateController.isShowing(); } + @Nullable private CustomTile getTileForToken(IBinder token) { synchronized (mServices) { return mTokenMap.get(token); } } + @Nullable private CustomTile getTileForComponent(ComponentName component) { synchronized (mServices) { return mTiles.get(component); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 76950d1448d6..5e68f611293e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -18,6 +18,8 @@ import android.content.Context; import android.os.Build; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSIconView; @@ -173,6 +175,8 @@ public class QSFactoryImpl implements QSFactory { mColorCorrectionTileProvider = colorCorrectionTileProvider; } + /** Creates a tile with a type based on {@code tileSpec} */ + @Nullable public final QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { @@ -182,6 +186,7 @@ public class QSFactoryImpl implements QSFactory { return tile; } + @Nullable protected QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. switch (tileSpec) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index b5f07d175274..6d9d5b16fcab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -116,6 +116,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private boolean mAnnounceNextStateChange; private String mTileSpec; + @Nullable private EnforcedAdmin mEnforcedAdmin; private boolean mShowingDetail; private int mIsFullQs; @@ -260,6 +261,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy return new QSIconViewImpl(context); } + /** Returns corresponding DetailAdapter. */ + @Nullable public DetailAdapter getDetailAdapter() { return null; // optional } @@ -342,7 +345,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy refreshState(null); } - protected final void refreshState(Object arg) { + protected final void refreshState(@Nullable Object arg) { mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); } @@ -432,9 +435,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy * * @return the intent to launch */ + @Nullable public abstract Intent getLongClickIntent(); - protected void handleRefreshState(Object arg) { + protected void handleRefreshState(@Nullable Object arg) { handleUpdateState(mTmpState, arg); boolean changed = mTmpState.copyTo(mState); if (mReadyState == READY_STATE_READYING) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java index 72c68ce12ca4..f1e82b6a18a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SlashImageView.java @@ -27,6 +27,7 @@ import com.android.systemui.qs.SlashDrawable; public class SlashImageView extends ImageView { + @Nullable @VisibleForTesting protected SlashDrawable mSlash; private boolean mAnimationEnabled = true; @@ -35,6 +36,7 @@ public class SlashImageView extends ImageView { super(context); } + @Nullable protected SlashDrawable getSlash() { return mSlash; } @@ -52,7 +54,7 @@ public class SlashImageView extends ImageView { } @Override - public void setImageDrawable(Drawable drawable) { + public void setImageDrawable(@Nullable Drawable drawable) { if (drawable == null) { mSlash = null; super.setImageDrawable(null); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 5552105a77c0..754f8e26ee87 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -320,6 +320,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { // We probably won't ever have space in the UI for more than 20 devices, so don't // get info for them. private static final int MAX_DEVICES = 20; + @Nullable private QSDetailItems mItems; @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index e5601f29af0b..698a2538ee87 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -257,7 +257,9 @@ public class CellularTile extends QSTileImpl<SignalState> { private static final class CallbackInfo { boolean airplaneModeEnabled; + @Nullable CharSequence dataSubscriptionName; + @Nullable CharSequence dataContentDescription; boolean activityIn; boolean activityOut; @@ -320,6 +322,7 @@ public class CellularTile extends QSTileImpl<SignalState> { return mContext.getString(R.string.quick_settings_cellular_detail_title); } + @Nullable @Override public Boolean getToggleState() { return mDataController.isMobileDataSupported() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 49c548da63f5..a06dc8b2c19a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -360,6 +360,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener { + @Nullable private ZenModePanel mZenPanel; private boolean mAuto; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index cd81b4a11703..9df942d3260a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -145,13 +145,15 @@ public class InternetTile extends QSTileImpl<SignalState> { && mHost.getUserContext().getUserId() == UserHandle.USER_SYSTEM); } - private CharSequence getSecondaryLabel(boolean isTransient, String statusLabel) { + @Nullable + private CharSequence getSecondaryLabel(boolean isTransient, @Nullable String statusLabel) { return isTransient ? mContext.getString(R.string.quick_settings_wifi_secondary_label_transient) : statusLabel; } - private static String removeDoubleQuotes(String string) { + @Nullable + private static String removeDoubleQuotes(@Nullable String string) { if (string == null) return null; final int length = string.length(); if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { @@ -163,6 +165,7 @@ public class InternetTile extends QSTileImpl<SignalState> { private static final class EthernetCallbackInfo { boolean mConnected; int mEthernetSignalIconId; + @Nullable String mEthernetContentDescription; @Override @@ -180,11 +183,14 @@ public class InternetTile extends QSTileImpl<SignalState> { boolean mEnabled; boolean mConnected; int mWifiSignalIconId; + @Nullable String mSsid; boolean mActivityIn; boolean mActivityOut; + @Nullable String mWifiSignalContentDescription; boolean mIsTransient; + @Nullable public String mStatusLabel; boolean mNoDefaultNetwork; boolean mNoValidatedNetwork; @@ -211,7 +217,9 @@ public class InternetTile extends QSTileImpl<SignalState> { private static final class CellularCallbackInfo { boolean mAirplaneModeEnabled; + @Nullable CharSequence mDataSubscriptionName; + @Nullable CharSequence mDataContentDescription; int mMobileSignalIconId; int mQsTypeIcon; @@ -540,7 +548,8 @@ public class InternetTile extends QSTileImpl<SignalState> { } } - private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { + private CharSequence appendMobileDataType( + @Nullable CharSequence current, @Nullable CharSequence dataType) { if (TextUtils.isEmpty(dataType)) { return Html.fromHtml((current == null ? "" : current.toString()), 0); } @@ -551,6 +560,7 @@ public class InternetTile extends QSTileImpl<SignalState> { return Html.fromHtml(concat, 0); } + @Nullable private CharSequence getMobileDataContentName(CellularCallbackInfo cb) { if (cb.mRoaming && !TextUtils.isEmpty(cb.mDataContentDescription)) { String roaming = mContext.getString(R.string.data_connection_roaming); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index 0886b46f9d2f..a61f0ce0c864 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -54,6 +54,7 @@ public class NfcTile extends QSTileImpl<BooleanState> { private static final String NFC = "nfc"; private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_nfc); + @Nullable private NfcAdapter mAdapter; private BroadcastDispatcher mBroadcastDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index 9996ecd74fd2..b65802506cbf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -131,6 +131,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { return mQRCodeScannerController.isCameraAvailable(); } + @Nullable @Override public Intent getLongClickIntent() { return null; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index d9919bdac889..247f02b120db 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -72,8 +72,10 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { private final SecureSettings mSecureSettings; private final QuickAccessWalletController mController; + @Nullable private WalletCard mSelectedCard; private boolean mIsWalletUpdating = true; + @Nullable @VisibleForTesting Drawable mCardViewDrawable; @Inject @@ -200,6 +202,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { && mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT) != null; } + @Nullable @Override public Intent getLongClickIntent() { return null; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 8ff75cb3662d..45e43ee20d67 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -136,6 +136,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> return 0; } + @Nullable @Override public Intent getLongClickIntent() { return null; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index b0a1b18a8cd5..0be0619145b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -134,6 +134,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS return new Intent(Settings.ACTION_PRIVACY_SETTINGS); } + @Nullable @Override public DetailAdapter getDetailAdapter() { return super.getDetailAdapter(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java index 6ca550c9ddb2..076ef3573dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailItemView.java @@ -28,6 +28,8 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.internal.util.ArrayUtils; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; @@ -50,15 +52,15 @@ public class UserDetailItemView extends LinearLayout { this(context, null); } - public UserDetailItemView(Context context, AttributeSet attrs) { + public UserDetailItemView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr) { + public UserDetailItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - public UserDetailItemView(Context context, AttributeSet attrs, int defStyleAttr, + public UserDetailItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index f793a5086871..ce6aaae6e98c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -77,6 +77,7 @@ public class UserDetailView extends PseudoGridView { private final Context mContext; protected UserSwitcherController mController; + @Nullable private View mCurrentUserView; private final UiEventLogger mUiEventLogger; private final FalsingManager mFalsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java index e110a6492b20..db1b6e68640e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java @@ -48,6 +48,7 @@ public class UserTile extends QSTileImpl<State> implements UserInfoController.On private final UserSwitcherController mUserSwitcherController; private final UserInfoController mUserInfoController; + @Nullable private Pair<String, Drawable> mLastUpdate; @Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 1608248f7c54..c82ff341bb12 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -258,6 +258,7 @@ public class WifiTile extends QSTileImpl<SignalState> { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); } + @Nullable private static String removeDoubleQuotes(String string) { if (string == null) return null; final int length = string.length(); @@ -271,11 +272,14 @@ public class WifiTile extends QSTileImpl<SignalState> { boolean enabled; boolean connected; int wifiSignalIconId; + @Nullable String ssid; boolean activityIn; boolean activityOut; + @Nullable String wifiSignalContentDescription; boolean isTransient; + @Nullable public String statusLabel; @Override @@ -321,7 +325,9 @@ public class WifiTile extends QSTileImpl<SignalState> { protected class WifiDetailAdapter implements DetailAdapter, AccessPointController.AccessPointCallback, QSDetailItems.Callback { + @Nullable private QSDetailItems mItems; + @Nullable private WifiEntry[] mAccessPoints; @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java index 544246ee97aa..4fe155cfaeb9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -52,6 +52,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; private final InternetDialogController mInternetDialogController; + @Nullable private List<WifiEntry> mWifiEntries; @VisibleForTesting protected int mWifiEntriesCount; @@ -189,6 +190,7 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern mWifiSummaryText.setText(summary); } + @Nullable Drawable getWifiDrawable(int level, boolean hasNoInternet) { // If the Wi-Fi level is equal to WIFI_LEVEL_UNREACHABLE(-1), then a null drawable // will be returned. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index e7982bfb7bcd..8e019426af14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -42,7 +42,6 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; @@ -98,6 +97,7 @@ public class InternetDialog extends SystemUIDialog implements private InternetDialogFactory mInternetDialogFactory; private SubscriptionManager mSubscriptionManager; private TelephonyManager mTelephonyManager; + @Nullable private AlertDialog mAlertDialog; private UiEventLogger mUiEventLogger; private Context mContext; @@ -130,12 +130,14 @@ public class InternetDialog extends SystemUIDialog implements private Button mDoneButton; private Button mAirplaneModeButton; private Drawable mBackgroundOn; + @Nullable private Drawable mBackgroundOff = null; private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private boolean mCanConfigMobileData; // Wi-Fi entries private int mWifiNetworkHeight; + @Nullable @VisibleForTesting protected WifiEntry mConnectedWifiEntry; @VisibleForTesting @@ -536,6 +538,7 @@ public class InternetDialog extends SystemUIDialog implements return mInternetDialogController.getDialogTitleText(); } + @Nullable CharSequence getSubtitleText() { return mInternetDialogController.getSubtitleText( mIsProgressBarVisible && !mIsSearchingHidden); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 3189d2f95c28..f89b7a3c0971 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -303,6 +303,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } + @Nullable protected Intent getWifiDetailsSettingsIntent(String key) { if (TextUtils.isEmpty(key)) { if (DEBUG) { @@ -320,6 +321,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return mContext.getText(R.string.quick_settings_internet_label); } + @Nullable CharSequence getSubtitleText(boolean isProgressBarVisible) { if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) { // When Wi-Fi is disabled. @@ -391,6 +393,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return null; } + @Nullable Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) { if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) { return null; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index f380911b6403..84e21e4126cc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -113,7 +113,8 @@ public class TakeScreenshotService extends Service { @Override public IBinder onBind(@NonNull Intent intent) { - registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS)); + registerReceiver(mCloseSystemDialogs, new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS), + Context.RECEIVER_EXPORTED); final Messenger m = new Messenger(mHandler); if (DEBUG_SERVICE) { Log.d(TAG, "onBind: returning connection: " + m); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 3cecbb71407a..597e4242af66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -348,11 +348,19 @@ public class CommandQueue extends IStatusBar.Stub implements String packageName) { } /** - * @see IStatusBar#showTransient(int, int[]). + * @see IStatusBar#showTransient(int, int[], boolean). */ default void showTransient(int displayId, @InternalInsetsType int[] types) { } /** + * @see IStatusBar#showTransient(int, int[], boolean). + */ + default void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar) { + showTransient(displayId, types); + } + + /** * @see IStatusBar#abortTransient(int, int[]). */ default void abortTransient(int displayId, @InternalInsetsType int[] types) { } @@ -1038,9 +1046,10 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void showTransient(int displayId, int[] types) { + public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) { synchronized (mLock) { - mHandler.obtainMessage(MSG_SHOW_TRANSIENT, displayId, 0, types).sendToTarget(); + mHandler.obtainMessage(MSG_SHOW_TRANSIENT, displayId, isGestureOnSystemBar ? 1 : 0, + types).sendToTarget(); } } @@ -1444,8 +1453,9 @@ public class CommandQueue extends IStatusBar.Stub implements case MSG_SHOW_TRANSIENT: { final int displayId = msg.arg1; final int[] types = (int[]) msg.obj; + final boolean isGestureOnSystemBar = msg.arg2 != 0; for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showTransient(displayId, types); + mCallbacks.get(i).showTransient(displayId, types, isGestureOnSystemBar); } break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt index 4272bb14ff3a..66591b3c7d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt @@ -74,9 +74,12 @@ class DisableFlagsLogger constructor( * Returns a string representing the, old, new, and new-after-modification disable flag states, * as well as the differences between each of the states. * - * Example: - * Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (hB.iGR) | New after local modification: - * EnaihBcRso.qInGR (.n) + * Example if [old], [new], and [newAfterLocalModification] are all different: + * Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (changed: hB.iGR) | New after local + * modification: EnaihBcRso.qInGR (changed: .n) + * + * Example if [old] and [new] are the same: + * EnaihBcRso.qiNGR (unchanged) * * A capital character signifies the flag is set and a lowercase character signifies that the * flag isn't set. The flag states will be logged in the same order as the passed-in lists. @@ -96,54 +99,51 @@ class DisableFlagsLogger constructor( new: DisableState, newAfterLocalModification: DisableState? = null ): String { - val builder = StringBuilder("Received new disable state. ") + val builder = StringBuilder("Received new disable state: ") - old?.let { + // This if/else has slightly repetitive code but is easier to read. + if (old != null && old != new) { builder.append("Old: ") builder.append(getFlagsString(old)) builder.append(" | ") - } - - builder.append("New: ") - if (old != null && old != new) { - builder.append(getFlagsStringWithDiff(old, new)) - } else { + builder.append("New: ") + builder.append(getFlagsString(new)) + builder.append(" ") + builder.append(getDiffString(old, new)) + } else if (old != null && old == new) { + // If old and new are the same, we only need to print one of them. builder.append(getFlagsString(new)) + builder.append(" ") + builder.append(getDiffString(old, new)) + } else { // old == null + builder.append(getFlagsString(new)) + // Don't get a diff string because we have no [old] to compare with. } if (newAfterLocalModification != null && new != newAfterLocalModification) { builder.append(" | New after local modification: ") - builder.append(getFlagsStringWithDiff(new, newAfterLocalModification)) + builder.append(getFlagsString(newAfterLocalModification)) + builder.append(" ") + builder.append(getDiffString(new, newAfterLocalModification)) } return builder.toString() } /** - * Returns a string representing [new] state, as well as the difference from [old] to [new] - * (if there is one). - */ - private fun getFlagsStringWithDiff(old: DisableState, new: DisableState): String { - val builder = StringBuilder() - builder.append(getFlagsString(new)) - builder.append(" ") - builder.append(getDiffString(old, new)) - return builder.toString() - } - - /** - * Returns a string representing the difference between [old] and [new], or an empty string if - * there is no difference. + * Returns a string representing the difference between [old] and [new]. * - * For example, if old was "abc.DE" and new was "aBC.De", the difference returned would be - * "(BC.e)". + * - If [old] was "abc.DE" and [new] was "aBC.De", the difference returned would be + * "(changed: BC.e)". + * - If [old] and [new] are the same, the difference returned would be "(unchanged)". */ private fun getDiffString(old: DisableState, new: DisableState): String { if (old == new) { - return "" + return "(unchanged)" } val builder = StringBuilder("(") + builder.append("changed: ") disable1FlagsList.forEach { val newSymbol = it.getFlagStatus(new.disable1) if (it.getFlagStatus(old.disable1) != newSymbol) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 8ce73d777087..cd4b74514937 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -133,7 +133,7 @@ public class KeyguardIndicationController { private final DockManager mDockManager; private final DevicePolicyManager mDevicePolicyManager; private final UserManager mUserManager; - private final @Main DelayableExecutor mExecutor; + protected final @Main DelayableExecutor mExecutor; private final LockPatternUtils mLockPatternUtils; private final IActivityManager mIActivityManager; private final FalsingManager mFalsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 491a1750a93e..648e14cddff6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -8,12 +8,13 @@ import android.content.Context import android.content.res.Configuration import android.os.SystemClock import android.util.DisplayMetrics +import android.util.IndentingPrintWriter import android.util.MathUtils import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration import androidx.annotation.VisibleForTesting -import com.android.internal.logging.nano.MetricsProto.MetricsEvent +import com.android.systemui.Dumpable import com.android.systemui.ExpandHelper import com.android.systemui.Gefingerpoken import com.android.systemui.R @@ -22,23 +23,26 @@ import com.android.systemui.biometrics.UdfpsKeyguardViewController import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.media.MediaHierarchyManager import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QS +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.KeyguardBypassController -import com.android.systemui.statusbar.phone.LockscreenGestureLogger -import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent +import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.Utils +import java.io.FileDescriptor +import java.io.PrintWriter import javax.inject.Inject private const val SPRING_BACK_ANIMATION_LENGTH_MS = 375L @@ -51,19 +55,19 @@ private const val RUBBERBAND_FACTOR_EXPANDABLE = 0.5f @SysUISingleton class LockscreenShadeTransitionController @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, - private val lockscreenGestureLogger: LockscreenGestureLogger, + private val logger: LSShadeTransitionLogger, private val keyguardBypassController: KeyguardBypassController, private val lockScreenUserManager: NotificationLockscreenUserManager, private val falsingCollector: FalsingCollector, private val ambientState: AmbientState, - private val displayMetrics: DisplayMetrics, private val mediaHierarchyManager: MediaHierarchyManager, private val scrimController: ScrimController, private val depthController: NotificationShadeDepthController, private val context: Context, configurationController: ConfigurationController, - falsingManager: FalsingManager -) { + falsingManager: FalsingManager, + dumpManager: DumpManager, +) : Dumpable { private var pulseHeight: Float = 0f private var useSplitShade: Boolean = false private lateinit var nsslController: NotificationStackScrollLayoutController @@ -139,6 +143,23 @@ class LockscreenShadeTransitionController @Inject constructor( touchHelper.updateResources(context) } }) + dumpManager.registerDumpable(this) + statusBarStateController.addCallback(object : StatusBarStateController.StateListener { + override fun onExpandedChanged(isExpanded: Boolean) { + // safeguard: When the panel is fully collapsed, let's make sure to reset. + // See b/198098523 + if (!isExpanded) { + if (dragDownAmount != 0f && dragDownAnimator?.isRunning != true) { + logger.logDragDownAmountResetWhenFullyCollapsed() + dragDownAmount = 0f + } + if (pulseHeight != 0f && pulseHeightAnimator?.isRunning != true) { + logger.logPulseHeightNotResetWhenFullyCollapsed() + setPulseHeight(0f, animate = false) + } + } + } + }) } private fun updateResources() { @@ -182,19 +203,19 @@ class LockscreenShadeTransitionController @Inject constructor( */ internal fun onDraggedDown(startingChild: View?, dragLengthY: Int) { if (canDragDown()) { + val cancelRunnable = Runnable { + logger.logGoingToLockedShadeAborted() + setDragDownAmountAnimated(0f) + } if (nsslController.isInLockedDownShade()) { + logger.logDraggedDownLockDownShade(startingChild) statusBarStateController.setLeaveOpenOnKeyguardHide(true) statusbar.dismissKeyguardThenExecute(OnDismissAction { nextHideKeyguardNeedsNoAnimation = true false - }, - null /* cancelRunnable */, false /* afterKeyguardGone */) + }, cancelRunnable, false /* afterKeyguardGone */) } else { - lockscreenGestureLogger.write( - MetricsEvent.ACTION_LS_SHADE, - (dragLengthY / displayMetrics.density).toInt(), - 0 /* velocityDp */) - lockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN) + logger.logDraggedDown(startingChild, dragLengthY) if (!ambientState.isDozing() || startingChild != null) { // go to locked shade while animating the drag down amount from its current // value @@ -216,11 +237,11 @@ class LockscreenShadeTransitionController @Inject constructor( dragDownAmount = 0f forceApplyAmount = false } - val cancelRunnable = Runnable { setDragDownAmountAnimated(0f) } goToLockedShadeInternal(startingChild, animationHandler, cancelRunnable) } } } else { + logger.logUnSuccessfulDragDown(startingChild) setDragDownAmountAnimated(0f) } } @@ -229,6 +250,7 @@ class LockscreenShadeTransitionController @Inject constructor( * Called by the touch helper when the drag down was aborted and should be reset. */ internal fun onDragDownReset() { + logger.logDragDownAborted() nsslController.setDimmed(true /* dimmed */, true /* animated */) nsslController.resetScrollPosition() nsslController.resetCheckSnoozeLeavebehind() @@ -246,10 +268,16 @@ class LockscreenShadeTransitionController @Inject constructor( /** * Called by the touch helper when the drag down was started */ - internal fun onDragDownStarted() { + internal fun onDragDownStarted(startingChild: ExpandableView?) { + logger.logDragDownStarted(startingChild) nsslController.cancelLongPress() nsslController.checkSnoozeLeavebehind() - dragDownAnimator?.cancel() + dragDownAnimator?.apply { + if (isRunning) { + logger.logAnimationCancelled(isPulse = false) + cancel() + } + } } /** @@ -294,12 +322,12 @@ class LockscreenShadeTransitionController @Inject constructor( set(value) { if (field != value || forceApplyAmount) { field = value - if (!nsslController.isInLockedDownShade() || forceApplyAmount) { + if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) { nsslController.setTransitionToFullShadeAmount(field) notificationPanelController.setTransitionToFullShadeAmount(field, false /* animate */, 0 /* delay */) - dragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance) - qS.setTransitionToFullShadeAmount(field, dragProgress) + qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance) + qS.setTransitionToFullShadeAmount(field, qSDragProgress) // TODO: appear media also in split shade val mediaAmount = if (useSplitShade) 0f else field mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount) @@ -308,7 +336,10 @@ class LockscreenShadeTransitionController @Inject constructor( } } - var dragProgress = 0f + /** + * The drag progress of the quick settings drag down amount + */ + var qSDragProgress = 0f private set private fun transitionToShadeAmountCommon(dragDownAmount: Float) { @@ -325,6 +356,7 @@ class LockscreenShadeTransitionController @Inject constructor( delay: Long = 0, endlistener: (() -> Unit)? = null ) { + logger.logDragDownAnimation(target) val dragDownAnimator = ValueAnimator.ofFloat(dragDownAmount, target) dragDownAnimator.interpolator = Interpolators.FAST_OUT_SLOW_IN dragDownAnimator.duration = SPRING_BACK_ANIMATION_LENGTH_MS @@ -380,7 +412,9 @@ class LockscreenShadeTransitionController @Inject constructor( */ @JvmOverloads fun goToLockedShade(expandedView: View?, needsQSAnimation: Boolean = true) { - if (statusBarStateController.state == StatusBarState.KEYGUARD) { + val isKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD + logger.logTryGoToLockedShade(isKeyguard) + if (isKeyguard) { val animationHandler: ((Long) -> Unit)? if (needsQSAnimation) { // Let's use the default animation @@ -416,6 +450,7 @@ class LockscreenShadeTransitionController @Inject constructor( ) { if (statusbar.isShadeDisabled) { cancelAction?.run() + logger.logShadeDisabledOnGoToLockedShade() return } var userId: Int = lockScreenUserManager.getCurrentUserId() @@ -454,9 +489,11 @@ class LockscreenShadeTransitionController @Inject constructor( } cancelAction?.run() } + logger.logShowBouncerOnGoToLockedShade() statusbar.showBouncerWithDimissAndCancelIfKeyguard(onDismissAction, cancelHandler) draggedDownEntry = entry } else { + logger.logGoingToLockedShade(animationHandler != null) statusBarStateController.setState(StatusBarState.SHADE_LOCKED) // This call needs to be after updating the shade state since otherwise // the scrimstate resets too early @@ -476,6 +513,7 @@ class LockscreenShadeTransitionController @Inject constructor( * @param previousState which state were we in when we hid the keyguard? */ fun onHideKeyguard(delay: Long, previousState: Int) { + logger.logOnHideKeyguard() if (animationHandlerOnKeyguardDismiss != null) { animationHandlerOnKeyguardDismiss!!.invoke(delay) animationHandlerOnKeyguardDismiss = null @@ -498,6 +536,7 @@ class LockscreenShadeTransitionController @Inject constructor( * not triggered by gestures, e.g. when clicking on the shelf or expand button. */ private fun performDefaultGoToFullShadeAnimation(delay: Long) { + logger.logDefaultGoToFullShadeAnimation(delay) notificationPanelController.animateToFullShade(delay) animateAppear(delay) } @@ -534,6 +573,7 @@ class LockscreenShadeTransitionController @Inject constructor( * @param cancelled was the interaction cancelled and this is a reset? */ fun finishPulseAnimation(cancelled: Boolean) { + logger.logPulseExpansionFinished(cancelled) if (cancelled) { setPulseHeight(0f, animate = true) } else { @@ -546,7 +586,28 @@ class LockscreenShadeTransitionController @Inject constructor( * Notify this class that a pulse expansion is starting */ fun onPulseExpansionStarted() { - pulseHeightAnimator?.cancel() + logger.logPulseExpansionStarted() + pulseHeightAnimator?.apply { + if (isRunning) { + logger.logAnimationCancelled(isPulse = true) + cancel() + } + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + IndentingPrintWriter(pw, " ").let { + it.println("LSShadeTransitionController:") + it.increaseIndent() + it.println("pulseHeight: $pulseHeight") + it.println("useSplitShade: $useSplitShade") + it.println("dragDownAmount: $dragDownAmount") + it.println("qSDragProgress: $qSDragProgress") + it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled") + it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded") + it.println("hasPendingHandlerOnKeyguardDismiss: " + + "${animationHandlerOnKeyguardDismiss != null}") + } } } @@ -626,7 +687,7 @@ class DragDownHelper( captureStartingChild(initialTouchX, initialTouchY) initialTouchY = y initialTouchX = x - dragDownCallback.onDragDownStarted() + dragDownCallback.onDragDownStarted(startingChild) dragDownAmountOnStart = dragDownCallback.dragDownAmount return startingChild != null || dragDownCallback.isDragDownAnywhereEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt index 761a20326200..ea51bd89df42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt @@ -24,15 +24,18 @@ import android.content.res.Configuration import android.os.PowerManager import android.os.PowerManager.WAKE_REASON_GESTURE import android.os.SystemClock +import android.util.IndentingPrintWriter import android.view.MotionEvent import android.view.VelocityTracker import android.view.ViewConfiguration +import com.android.systemui.Dumpable import com.android.systemui.Gefingerpoken import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator @@ -43,6 +46,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController +import java.io.FileDescriptor +import java.io.PrintWriter import javax.inject.Inject import kotlin.math.max @@ -61,8 +66,9 @@ constructor( private val statusBarStateController: StatusBarStateController, private val falsingManager: FalsingManager, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, - private val falsingCollector: FalsingCollector -) : Gefingerpoken { + private val falsingCollector: FalsingCollector, + dumpManager: DumpManager +) : Gefingerpoken, Dumpable { companion object { private val SPRING_BACK_ANIMATION_LENGTH_MS = 375 } @@ -120,6 +126,7 @@ constructor( } }) mPowerManager = context.getSystemService(PowerManager::class.java) + dumpManager.registerDumpable(this) } private fun initResources(context: Context) { @@ -329,4 +336,17 @@ constructor( fun onStartedWakingUp() { isWakingToShadeLocked = false } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + IndentingPrintWriter(pw, " ").let { + it.println("PulseExpansionHandler:") + it.increaseIndent() + it.println("isExpanding: $isExpanding") + it.println("leavingLockscreen: $leavingLockscreen") + it.println("mPulsing: $mPulsing") + it.println("isWakingToShadeLocked: $isWakingToShadeLocked") + it.println("qsExpanded: $qsExpanded") + it.println("bouncerShowing: $bouncerShowing") + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 4717b3afb66d..09c608df6ca8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -626,7 +626,6 @@ public class NotificationEntryManager implements entry = new NotificationEntry( notification, ranking, - mFgsFeatureController.isForegroundServiceDismissalEnabled(), SystemClock.uptimeMillis()); mAllNotifications.add(entry); mLeakDetector.trackInstance(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt index cd8897ea229d..bd9383de3bab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt @@ -21,6 +21,7 @@ import android.provider.DeviceConfig import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP @@ -37,6 +38,7 @@ private var sUsePeopleFiltering: Boolean? = null /** * Feature controller for the NOTIFICATIONS_USE_PEOPLE_FILTERING config. */ +@SysUISingleton class NotificationSectionsFeatureManager @Inject constructor( val proxy: DeviceConfigProxy, val context: Context diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index b6b9c3f1cf9d..bf81ea5c264c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -56,10 +56,12 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.Pair; +import android.util.Slog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; @@ -240,6 +242,10 @@ public class NotifCollection implements Dumpable { Assert.isMainThread(); checkForReentrantCall(); + // TODO (b/206842750): This method is called from (silent) clear all and non-clear all + // contexts and should be checking the NO_CLEAR flag, rather than depending on NSSL + // to pass in a properly filtered list of notifications + final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>(); for (int i = 0; i < entriesToDismiss.size(); i++) { NotificationEntry entry = entriesToDismiss.get(i).first; @@ -742,12 +748,13 @@ public class NotifCollection implements Dumpable { * * See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code. */ - private static boolean shouldAutoDismissChildren( + @VisibleForTesting + static boolean shouldAutoDismissChildren( NotificationEntry entry, String dismissedGroupKey) { return entry.getSbn().getGroupKey().equals(dismissedGroupKey) && !entry.getSbn().getNotification().isGroupSummary() - && !hasFlag(entry, Notification.FLAG_FOREGROUND_SERVICE) + && !hasFlag(entry, Notification.FLAG_ONGOING_EVENT) && !hasFlag(entry, Notification.FLAG_BUBBLE) && entry.getDismissState() != DISMISSED; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index d56938ae7422..f22acb78f302 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -174,7 +174,6 @@ public final class NotificationEntry extends ListEntry { private boolean mAutoHeadsUp; private boolean mPulseSupressed; - private boolean mAllowFgsDismissal; private int mBucket = BUCKET_ALERTING; @Nullable private Long mPendingAnimationDuration; private boolean mIsMarkedForUserTriggeredMovement; @@ -192,14 +191,6 @@ public final class NotificationEntry extends ListEntry { public NotificationEntry( @NonNull StatusBarNotification sbn, @NonNull Ranking ranking, - long creationTime) { - this(sbn, ranking, false, creationTime); - } - - public NotificationEntry( - @NonNull StatusBarNotification sbn, - @NonNull Ranking ranking, - boolean allowFgsDismissal, long creationTime ) { super(requireNonNull(requireNonNull(sbn).getKey()), creationTime); @@ -209,8 +200,6 @@ public final class NotificationEntry extends ListEntry { mKey = sbn.getKey(); setSbn(sbn); setRanking(ranking); - - mAllowFgsDismissal = allowFgsDismissal; } @Override @@ -743,13 +732,11 @@ public final class NotificationEntry extends ListEntry { /** * @return Can the underlying notification be cleared? This can be different from whether the * notification can be dismissed in case notifications are sensitive on the lockscreen. - * @see #canViewBeDismissed() */ - // TOOD: This logic doesn't belong on NotificationEntry. It should be moved to the - // ForegroundsServiceDismissalFeatureController or some other controller that can be added - // as a dependency to any class that needs to answer this question. + // TODO: This logic doesn't belong on NotificationEntry. It should be moved to a controller + // that can be added as a dependency to any class that needs to answer this question. public boolean isClearable() { - if (!isDismissable()) { + if (!mSbn.isClearable()) { return false; } @@ -757,7 +744,7 @@ public final class NotificationEntry extends ListEntry { if (children != null && children.size() > 0) { for (int i = 0; i < children.size(); i++) { NotificationEntry child = children.get(i); - if (!child.isDismissable()) { + if (!child.getSbn().isClearable()) { return false; } } @@ -766,28 +753,25 @@ public final class NotificationEntry extends ListEntry { } /** - * Notifications might have any combination of flags: - * - FLAG_ONGOING_EVENT - * - FLAG_NO_CLEAR - * - FLAG_FOREGROUND_SERVICE - * - * We want to allow dismissal of notifications that represent foreground services, which may - * have all 3 flags set. If we only find NO_CLEAR though, we don't want to allow dismissal + * @return Can the underlying notification be individually dismissed? + * @see #canViewBeDismissed() */ - private boolean isDismissable() { - boolean ongoing = ((mSbn.getNotification().flags & Notification.FLAG_ONGOING_EVENT) != 0); - boolean noclear = ((mSbn.getNotification().flags & Notification.FLAG_NO_CLEAR) != 0); - boolean fgs = ((mSbn.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0); - - if (mAllowFgsDismissal) { - if (noclear && !ongoing && !fgs) { - return false; + // TODO: This logic doesn't belong on NotificationEntry. It should be moved to a controller + // that can be added as a dependency to any class that needs to answer this question. + public boolean isDismissable() { + if (mSbn.isOngoing()) { + return false; + } + List<NotificationEntry> children = getAttachedNotifChildren(); + if (children != null && children.size() > 0) { + for (int i = 0; i < children.size(); i++) { + NotificationEntry child = children.get(i); + if (child.getSbn().isOngoing()) { + return false; + } } - return true; - } else { - return mSbn.isClearable(); } - + return true; } public boolean canViewBeDismissed() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java index f50038c08bd3..3bd91b5c8480 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java @@ -111,7 +111,7 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback String group = entry.getSbn().getGroup(); if (mNotifCollection.isOnlyChildInGroup(entry)) { NotificationEntry summary = mNotifCollection.getGroupSummary(group); - if (summary != null && summary.isClearable()) return summary; + if (summary != null && summary.isDismissable()) return summary; } return null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java index 3b114bbfd33a..8daf8be0cc8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java @@ -111,7 +111,7 @@ public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCal public NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) { if (mGroupMembershipManager.isOnlyChildInGroup(entry)) { NotificationEntry groupSummary = mGroupMembershipManager.getLogicalGroupSummary(entry); - return groupSummary.isClearable() ? groupSummary : null; + return groupSummary.isDismissable() ? groupSummary : null; } return null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt new file mode 100644 index 000000000000..f949af0688be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.stack.MediaContainerView +import javax.inject.Inject + +@SysUISingleton +class MediaContainerController @Inject constructor( + private val layoutInflater: LayoutInflater +) : NodeController { + + override val nodeLabel = "MediaContainer" + var mediaContainerView: MediaContainerView? = null + private set + + fun reinflateView(parent: ViewGroup) { + var oldPos = -1 + mediaContainerView?.let { _view -> + _view.removeFromTransientContainer() + if (_view.parent === parent) { + oldPos = parent.indexOfChild(_view) + parent.removeView(_view) + } + } + val inflated = layoutInflater.inflate( + R.layout.keyguard_media_container, + parent, + false /* attachToRoot */) + as MediaContainerView + if (oldPos != -1) { + parent.addView(inflated, oldPos) + } + mediaContainerView = inflated + } + + override val view: View + get() = mediaContainerView!! +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index f59e4ab2007b..f13470ec2c94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.render +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -32,6 +33,8 @@ import com.android.systemui.util.traceSection * need to present in the shade, notably the section headers. */ class NodeSpecBuilder( + private val mediaContainerController: MediaContainerController, + private val sectionsFeatureManager: NotificationSectionsFeatureManager, private val viewBarn: NotifViewBarn ) { fun buildNodeSpec( @@ -39,6 +42,13 @@ class NodeSpecBuilder( notifList: List<ListEntry> ): NodeSpec = traceSection("NodeSpecBuilder.buildNodeSpec") { val root = NodeSpecImpl(null, rootController) + + // The media container should be added as the first child of the root node + // TODO: Perhaps the node spec building process should be more of a pipeline of its own? + if (sectionsFeatureManager.isMediaControlsEnabled()) { + root.children.add(NodeSpecImpl(root, mediaContainerController)) + } + var currentSection: NotifSection? = null val prevSections = mutableSetOf<NotifSection?>() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt index 8c15647c5038..4e9017e05ecd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt @@ -60,7 +60,7 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( override fun reinflateView(parent: ViewGroup) { var oldPos = -1 _view?.let { _view -> - _view.transientContainer?.removeView(_view) + _view.removeFromTransientContainer() if (_view.parent === parent) { oldPos = parent.indexOfChild(_view) parent.removeView(_view) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index 1a8d720a12c6..43a75a5d5da8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.View +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -33,13 +34,15 @@ class ShadeViewManager constructor( context: Context, listContainer: NotificationListContainer, private val stackController: NotifStackController, + mediaContainerController: MediaContainerController, + featureManager: NotificationSectionsFeatureManager, logger: ShadeViewDifferLogger, private val viewBarn: NotifViewBarn ) { // We pass a shim view here because the listContainer may not actually have a view associated // with it and the differ never actually cares about the root node's view. private val rootController = RootNodeController(listContainer, View(context)) - private val specBuilder = NodeSpecBuilder(viewBarn) + private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, viewBarn) private val viewDiffer = ShadeViewDiffer(rootController, logger) /** Method for attaching this manager to the pipeline. */ @@ -68,6 +71,8 @@ class ShadeViewManager constructor( class ShadeViewManagerFactory @Inject constructor( private val context: Context, private val logger: ShadeViewDifferLogger, + private val mediaContainerController: MediaContainerController, + private val sectionsFeatureManager: NotificationSectionsFeatureManager, private val viewBarn: NotifViewBarn ) { fun create(listContainer: NotificationListContainer, stackController: NotifStackController) = @@ -75,6 +80,8 @@ class ShadeViewManagerFactory @Inject constructor( context, listContainer, stackController, + mediaContainerController, + sectionsFeatureManager, logger, viewBarn) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index f8984704220e..08a230b18eab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1462,7 +1462,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void performDismiss(boolean fromAccessibility) { Dependency.get(MetricsLogger.class).count(NotificationCounters.NOTIFICATION_DISMISSED, 1); dismiss(fromAccessibility); - if (mEntry.isClearable()) { + if (mEntry.isDismissable()) { if (mOnUserInteractionCallback != null) { mOnUserInteractionCallback.onDismiss(mEntry, REASON_CANCEL, mOnUserInteractionCallback.getGroupSummaryToDismiss(mEntry)); @@ -2673,9 +2673,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as * otherwise some state might not be updated. To request about the general clearability - * see {@link NotificationEntry#isClearable()}. + * see {@link NotificationEntry#isDismissable()}. */ public boolean canViewBeDismissed() { + return mEntry.isDismissable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); + } + + /** + * @return Whether this view is allowed to be cleared with clear all. Only valid for visible + * notifications as otherwise some state might not be updated. To request about the general + * clearability see {@link NotificationEntry#isClearable()}. + */ + public boolean canViewBeCleared() { return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index e8e6e310322d..0b6d7594ce50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -32,7 +32,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; public class FooterView extends StackScrollerDecorView { - private FooterViewButton mDismissButton; + private FooterViewButton mClearAllButton; private FooterViewButton mManageButton; private boolean mShowHistory; @@ -57,16 +57,16 @@ public class FooterView extends StackScrollerDecorView { pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); pw.println("manageButton showHistory: " + mShowHistory); pw.println("manageButton visibility: " - + DumpUtilsKt.visibilityString(mDismissButton.getVisibility())); + + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); pw.println("dismissButton visibility: " - + DumpUtilsKt.visibilityString(mDismissButton.getVisibility())); + + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); }); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mDismissButton = (FooterViewButton) findSecondaryView(); + mClearAllButton = (FooterViewButton) findSecondaryView(); mManageButton = findViewById(R.id.manage_text); } @@ -74,8 +74,8 @@ public class FooterView extends StackScrollerDecorView { mManageButton.setOnClickListener(listener); } - public void setDismissButtonClickListener(OnClickListener listener) { - mDismissButton.setOnClickListener(listener); + public void setClearAllButtonClickListener(OnClickListener listener) { + mClearAllButton.setOnClickListener(listener); } public boolean isOnEmptySpace(float touchX, float touchY) { @@ -106,8 +106,8 @@ public class FooterView extends StackScrollerDecorView { protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateColors(); - mDismissButton.setText(R.string.clear_all_notifications_text); - mDismissButton.setContentDescription( + mClearAllButton.setText(R.string.clear_all_notifications_text); + mClearAllButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); showHistory(mShowHistory); } @@ -118,8 +118,8 @@ public class FooterView extends StackScrollerDecorView { public void updateColors() { Resources.Theme theme = mContext.getTheme(); int textColor = getResources().getColor(R.color.notif_pill_text, theme); - mDismissButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); - mDismissButton.setTextColor(textColor); + mClearAllButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mClearAllButton.setTextColor(textColor); mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); mManageButton.setTextColor(textColor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index e65846865ef8..7dc2e1949274 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -57,7 +57,7 @@ public class AmbientState { private int mTopPadding; private boolean mShadeExpanded; private float mMaxHeadsUpTranslation; - private boolean mDismissAllInProgress; + private boolean mClearAllInProgress; private int mLayoutMinHeight; private int mLayoutMaxHeight; private NotificationShelf mShelf; @@ -384,12 +384,12 @@ public class AmbientState { return mMaxHeadsUpTranslation; } - public void setDismissAllInProgress(boolean dismissAllInProgress) { - mDismissAllInProgress = dismissAllInProgress; + public void setClearAllInProgress(boolean clearAllInProgress) { + mClearAllInProgress = clearAllInProgress; } - public boolean isDismissAllInProgress() { - return mDismissAllInProgress; + public boolean isClearAllInProgress() { + return mClearAllInProgress; } public void setLayoutMinHeight(int layoutMinHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java index 0247a99bc6c0..c9a0f6c428c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java @@ -25,9 +25,9 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; /** * Root view to insert Lock screen media controls into the notification stack. */ -public class MediaHeaderView extends ExpandableView { +public class MediaContainerView extends ExpandableView { - public MediaHeaderView(Context context, AttributeSet attrs) { + public MediaContainerView(Context context, AttributeSet attrs) { super(context, attrs); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 464fd06587e9..b589d9ae1abf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -45,7 +45,7 @@ public class NotificationRoundnessManager { private ExpandableNotificationRow mTrackedHeadsUp; private float mAppearFraction; private boolean mRoundForPulsingViews; - private boolean mIsDismissAllInProgress; + private boolean mIsClearAllInProgress; private ExpandableView mSwipedView = null; private ExpandableView mViewBeforeSwipedView = null; @@ -156,8 +156,8 @@ public class NotificationRoundnessManager { } } - void setDismissAllInProgress(boolean isClearingAll) { - mIsDismissAllInProgress = isClearingAll; + void setClearAllInProgress(boolean isClearingAll) { + mIsClearAllInProgress = isClearingAll; } private float getRoundnessFraction(ExpandableView view, boolean top) { @@ -170,8 +170,8 @@ public class NotificationRoundnessManager { return 1f; } if (view instanceof ExpandableNotificationRow - && ((ExpandableNotificationRow) view).canViewBeDismissed() - && mIsDismissAllInProgress) { + && ((ExpandableNotificationRow) view).canViewBeCleared() + && mIsClearAllInProgress) { return 1.0f; } if ((view.isPinned() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 1d90780ffee8..b02dc0cffdb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -16,17 +16,15 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.ColorInt -import android.annotation.LayoutRes import android.util.Log -import android.view.LayoutInflater import android.view.View import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.R import com.android.systemui.media.KeyguardMediaController import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.render.MediaContainerController import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager import com.android.systemui.statusbar.notification.dagger.AlertingHeader @@ -60,6 +58,7 @@ class NotificationSectionsManager @Inject internal constructor( private val sectionsFeatureManager: NotificationSectionsFeatureManager, private val logger: NotificationSectionsLogger, private val notifPipelineFlags: NotifPipelineFlags, + private val mediaContainerController: MediaContainerController, @IncomingHeader private val incomingHeaderController: SectionHeaderController, @PeopleHeader private val peopleHeaderController: SectionHeaderController, @AlertingHeader private val alertingHeaderController: SectionHeaderController, @@ -68,7 +67,7 @@ class NotificationSectionsManager @Inject internal constructor( private val configurationListener = object : ConfigurationController.ConfigurationListener { override fun onLocaleListChanged() { - reinflateViews(LayoutInflater.from(parent.context)) + reinflateViews() } } @@ -91,39 +90,19 @@ class NotificationSectionsManager @Inject internal constructor( val peopleHeaderView: SectionHeaderView? get() = peopleHeaderController.headerView - @get:VisibleForTesting - var mediaControlsView: MediaHeaderView? = null - private set + @VisibleForTesting + val mediaControlsView: MediaContainerView? + get() = mediaContainerController.mediaContainerView /** Must be called before use. */ - fun initialize(parent: NotificationStackScrollLayout, layoutInflater: LayoutInflater) { + fun initialize(parent: NotificationStackScrollLayout) { check(!initialized) { "NotificationSectionsManager already initialized" } initialized = true this.parent = parent - reinflateViews(layoutInflater) + reinflateViews() configurationController.addCallback(configurationListener) } - private fun <T : ExpandableView> reinflateView( - view: T?, - layoutInflater: LayoutInflater, - @LayoutRes layoutResId: Int - ): T { - var oldPos = -1 - view?.let { - view.transientContainer?.removeView(view) - if (view.parent === parent) { - oldPos = parent.indexOfChild(view) - parent.removeView(view) - } - } - val inflated = layoutInflater.inflate(layoutResId, parent, false) as T - if (oldPos != -1) { - parent.addView(inflated, oldPos) - } - return inflated - } - fun createSectionsForBuckets(): Array<NotificationSection> = sectionsFeatureManager.getNotificationBuckets() .map { NotificationSection(parent, it) } @@ -132,13 +111,12 @@ class NotificationSectionsManager @Inject internal constructor( /** * Reinflates the entire notification header, including all decoration views. */ - fun reinflateViews(layoutInflater: LayoutInflater) { + fun reinflateViews() { silentHeaderController.reinflateView(parent) alertingHeaderController.reinflateView(parent) peopleHeaderController.reinflateView(parent) incomingHeaderController.reinflateView(parent) - mediaControlsView = - reinflateView(mediaControlsView, layoutInflater, R.layout.keyguard_media_header) + mediaContainerController.reinflateView(parent) keyguardMediaController.attachSinglePaneContainer(mediaControlsView) } 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 943f05fe15ad..915a85df679c 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 @@ -255,8 +255,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mIsCurrentUserSetup; protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; - private boolean mDismissAllInProgress; - private FooterDismissListener mFooterDismissListener; + private boolean mClearAllInProgress; + private FooterClearAllListener mFooterClearAllListener; private boolean mFlingAfterUpEvent; /** @@ -439,8 +439,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mQsScrollBoundaryPosition; private HeadsUpAppearanceController mHeadsUpAppearanceController; private final Rect mTmpRect = new Rect(); - private DismissListener mDismissListener; - private DismissAllAnimationListener mDismissAllAnimationListener; + private ClearAllListener mClearAllListener; + private ClearAllAnimationListener mClearAllAnimationListener; private ShadeController mShadeController; private Consumer<Boolean> mOnStackYChanged; @@ -574,7 +574,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); updateSplitNotificationShade(); - mSectionsManager.initialize(this, LayoutInflater.from(context)); + mSectionsManager.initialize(this); mSections = mSectionsManager.createSectionsForBuckets(); mAmbientState = Dependency.get(AmbientState.class); @@ -665,7 +665,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable inflateFooterView(); inflateEmptyShadeView(); updateFooter(); - mSectionsManager.reinflateViews(LayoutInflater.from(mContext)); + mSectionsManager.reinflateViews(); } public void setIsRemoteInputActive(boolean isActive) { @@ -1207,7 +1207,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void clampScrollPosition() { int scrollRange = getScrollRange(); - if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) { + if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) { boolean animateStackY = false; if (scrollRange < getScrollAmountToScrollBoundary() && mAnimateStackYForContentHeightChange) { @@ -1729,7 +1729,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration, - true /* isDismissAll */); + true /* isClearAll */); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -2228,7 +2228,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable height += viewHeight; numShownItems++; - if (viewHeight > 0 || !(expandableView instanceof MediaHeaderView)) { + if (viewHeight > 0 || !(expandableView instanceof MediaContainerView)) { // Only count the media as a notification if it has a positive height. numShownNotifs++; } @@ -3202,7 +3202,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable ignoreChildren = false; } childWasSwipedOut |= isFullySwipedOut(row); - } else if (child instanceof MediaHeaderView) { + } else if (child instanceof MediaContainerView) { childWasSwipedOut = true; } if (!childWasSwipedOut) { @@ -4071,8 +4071,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable clearTransient(); clearHeadsUpDisappearRunning(); - if (mAmbientState.isDismissAllInProgress()) { - setDismissAllInProgress(false); + if (mAmbientState.isClearAllInProgress()) { + setClearAllInProgress(false); if (mShadeNeedsToClose) { mShadeNeedsToClose = false; postDelayed( @@ -4446,19 +4446,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setDismissAllInProgress(boolean dismissAllInProgress) { - mDismissAllInProgress = dismissAllInProgress; - mAmbientState.setDismissAllInProgress(dismissAllInProgress); - mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress); - handleDismissAllClipping(); + public void setClearAllInProgress(boolean clearAllInProgress) { + mClearAllInProgress = clearAllInProgress; + mAmbientState.setClearAllInProgress(clearAllInProgress); + mController.getNoticationRoundessManager().setClearAllInProgress(clearAllInProgress); + handleClearAllClipping(); } - boolean getDismissAllInProgress() { - return mDismissAllInProgress; + boolean getClearAllInProgress() { + return mClearAllInProgress; } @ShadeViewRefactor(RefactorComponent.ADAPTER) - private void handleDismissAllClipping() { + private void handleClearAllClipping() { final int count = getChildCount(); boolean previousChildWillBeDismissed = false; for (int i = 0; i < count; i++) { @@ -4466,12 +4466,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (child.getVisibility() == GONE) { continue; } - if (mDismissAllInProgress && previousChildWillBeDismissed) { + if (mClearAllInProgress && previousChildWillBeDismissed) { child.setMinClipTopAmount(child.getClipTopAmount()); } else { child.setMinClipTopAmount(0); } - previousChildWillBeDismissed = canChildBeDismissed(child); + previousChildWillBeDismissed = canChildBeCleared(child); } } @@ -5022,7 +5022,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; - if (isVisible(row) && includeChildInDismissAll(row, selection)) { + if (isVisible(row) && includeChildInClearAll(row, selection)) { return true; } } @@ -5052,7 +5052,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (isChildrenVisible(parent)) { for (ExpandableNotificationRow child : parent.getAttachedChildren()) { - if (isVisible(child) && includeChildInDismissAll(child, selection)) { + if (isVisible(child) && includeChildInClearAll(child, selection)) { viewsToHide.add(child); } } @@ -5073,13 +5073,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable continue; } ExpandableNotificationRow parent = (ExpandableNotificationRow) view; - if (includeChildInDismissAll(parent, selection)) { + if (includeChildInClearAll(parent, selection)) { viewsToRemove.add(parent); } List<ExpandableNotificationRow> children = parent.getAttachedChildren(); if (isVisible(parent) && children != null) { for (ExpandableNotificationRow child : children) { - if (includeChildInDismissAll(parent, selection)) { + if (includeChildInClearAll(parent, selection)) { viewsToRemove.add(child); } } @@ -5090,7 +5090,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * 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 onDismissAllAnimationsEnd. + * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. */ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @VisibleForTesting @@ -5099,18 +5099,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend = getRowsToDismissInBackend(selection); - if (mDismissListener != null) { - mDismissListener.onDismiss(selection); + if (mClearAllListener != null) { + mClearAllListener.onClearAll(selection); } final Runnable dismissInBackend = () -> { - onDismissAllAnimationsEnd(rowsToDismissInBackend, selection); + onClearAllAnimationsEnd(rowsToDismissInBackend, selection); }; if (viewsToAnimateAway.isEmpty()) { dismissInBackend.run(); return; } // Disable normal animations - setDismissAllInProgress(true); + setClearAllInProgress(true); mShadeNeedsToClose = closeShade; // Decrease the delay for every row we animate to give the sense of @@ -5131,10 +5131,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - private boolean includeChildInDismissAll( + private boolean includeChildInClearAll( ExpandableNotificationRow row, @SelectedRows int selection) { - return canChildBeDismissed(row) && matchesSelection(row, selection); + return canChildBeCleared(row) && matchesSelection(row, selection); } /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */ @@ -5150,9 +5150,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable protected void inflateFooterView() { FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_footer, this, false); - footerView.setDismissButtonClickListener(v -> { - if (mFooterDismissListener != null) { - mFooterDismissListener.onDismiss(); + footerView.setClearAllButtonClickListener(v -> { + if (mFooterClearAllListener != null) { + mFooterClearAllListener.onClearAll(); } clearNotifications(ROWS_ALL, true /* closeShade */); footerView.setSecondaryVisible(false /* visible */, true /* animate */); @@ -5389,20 +5389,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mCheckForLeavebehind; } - void setDismissListener (DismissListener listener) { - mDismissListener = listener; + void setClearAllListener(ClearAllListener listener) { + mClearAllListener = listener; } - void setDismissAllAnimationListener(DismissAllAnimationListener dismissAllAnimationListener) { - mDismissAllAnimationListener = dismissAllAnimationListener; + void setClearAllAnimationListener(ClearAllAnimationListener clearAllAnimationListener) { + mClearAllAnimationListener = clearAllAnimationListener; } public void setHighPriorityBeforeSpeedBump(boolean highPriorityBeforeSpeedBump) { mHighPriorityBeforeSpeedBump = highPriorityBeforeSpeedBump; } - void setFooterDismissListener(FooterDismissListener listener) { - mFooterDismissListener = listener; + void setFooterClearAllListener(FooterClearAllListener listener) { + mFooterClearAllListener = listener; } void setShadeController(ShadeController shadeController) { @@ -5976,9 +5976,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable static boolean canChildBeDismissed(View v) { if (v instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) v; - if (row.isBlockingHelperShowingAndTranslationFinished()) { - return true; - } if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { return false; } @@ -5990,6 +5987,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return false; } + static boolean canChildBeCleared(View v) { + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) { + return false; + } + return row.canViewBeCleared(); + } + if (v instanceof PeopleHubView) { + return ((PeopleHubView) v).getCanSwipe(); + } + return false; + } + // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------ void onEntryUpdated(NotificationEntry entry) { @@ -6003,11 +6014,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Called after the animations for a "clear all notifications" action has ended. */ - private void onDismissAllAnimationsEnd( + private void onClearAllAnimationsEnd( List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows) { - if (mDismissAllAnimationListener != null) { - mDismissAllAnimationListener.onAnimationEnd(viewsToRemove, selectedRows); + if (mClearAllAnimationListener != null) { + mClearAllAnimationListener.onAnimationEnd(viewsToRemove, selectedRows); } } @@ -6149,15 +6160,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** Only rows where entry.isHighPriority() is false. */ public static final int ROWS_GENTLE = 2; - interface DismissListener { - void onDismiss(@SelectedRows int selectedRows); + interface ClearAllListener { + void onClearAll(@SelectedRows int selectedRows); } - interface FooterDismissListener { - void onDismiss(); + interface FooterClearAllListener { + void onClearAll(); } - interface DismissAllAnimationListener { + interface ClearAllAnimationListener { void onAnimationEnd( List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows); } 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 fc612a934b43..ff75eef80ac8 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 @@ -28,7 +28,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.canChildBeDismissed; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.canChildBeCleared; import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY; import android.content.res.Configuration; @@ -466,7 +466,7 @@ public class NotificationStackScrollLayoutController { */ public void handleChildViewDismissed(View view) { - if (mView.getDismissAllInProgress()) { + if (mView.getClearAllInProgress()) { return; } mView.onSwipeEnd(); @@ -510,7 +510,7 @@ public class NotificationStackScrollLayoutController { && (parent.areGutsExposed() || mSwipeHelper.getExposedMenuView() == parent || (parent.getAttachedChildren().size() == 1 - && parent.getEntry().isClearable()))) { + && parent.getEntry().isDismissable()))) { // In this case the group is expanded and showing the menu for the // group, further interaction should apply to the group, not any // child notifications so we use the parent of the child. We also do the @@ -720,10 +720,10 @@ public class NotificationStackScrollLayoutController { mView.setController(this); mView.setTouchHandler(new TouchHandler()); mView.setStatusBar(mStatusBar); - mView.setDismissAllAnimationListener(this::onAnimationEnd); - mView.setDismissListener((selection) -> mUiEventLogger.log( + mView.setClearAllAnimationListener(this::onAnimationEnd); + mView.setClearAllListener((selection) -> mUiEventLogger.log( NotificationPanelEvent.fromSelection(selection))); - mView.setFooterDismissListener(() -> + mView.setFooterClearAllListener(() -> mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { @@ -1452,7 +1452,7 @@ public class NotificationStackScrollLayoutController { } } else { for (ExpandableNotificationRow rowToRemove : viewsToRemove) { - if (canChildBeDismissed(rowToRemove)) { + if (canChildBeCleared(rowToRemove)) { mNotificationEntryManager.performRemoveNotification( rowToRemove.getEntry().getSbn(), getDismissedByUserStats(rowToRemove.getEntry()), @@ -1503,7 +1503,7 @@ public class NotificationStackScrollLayoutController { * from the keyguard host to the quick settings one. */ public int getFullShadeTransitionInset() { - MediaHeaderView view = mKeyguardMediaController.getSinglePaneContainer(); + MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer(); if (view == null || view.getHeight() == 0 || mStatusBarStateController.getState() != KEYGUARD) { return 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 2c70a5fd9ce7..8f0579cc4693 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -458,7 +458,7 @@ public class StackScrollAlgorithm { final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); ((FooterView.FooterViewState) viewState).hideContent = isShelfShowing || noSpaceForFooter - || (ambientState.isDismissAllInProgress() + || (ambientState.isClearAllInProgress() && !hasOngoingNotifs(algorithmState)); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt new file mode 100644 index 000000000000..4de78f5d6190 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.util.DisplayMetrics +import android.view.View +import com.android.internal.logging.nano.MetricsProto.MetricsEvent +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.LSShadeTransitionLog +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.ExpandableView +import javax.inject.Inject + +private const val TAG = "LockscreenShadeTransitionController" + +class LSShadeTransitionLogger @Inject constructor( + @LSShadeTransitionLog private val buffer: LogBuffer, + private val lockscreenGestureLogger: LockscreenGestureLogger, + private val displayMetrics: DisplayMetrics +) { + fun logUnSuccessfulDragDown(startingChild: View?) { + val entry = (startingChild as ExpandableNotificationRow?)?.entry + buffer.log(TAG, LogLevel.INFO, { + str1 = entry?.key ?: "no entry" + }, { + "Tried to drag down but can't drag down on $str1" + }) + } + + fun logDragDownAborted() { + buffer.log(TAG, LogLevel.INFO, {}, { + "The drag down was reset" + }) + } + + fun logDragDownStarted(startingChild: ExpandableView?) { + val entry = (startingChild as ExpandableNotificationRow?)?.entry + buffer.log(TAG, LogLevel.INFO, { + str1 = entry?.key ?: "no entry" + }, { + "The drag down has started on $str1" + }) + } + + fun logDraggedDownLockDownShade(startingChild: View?) { + val entry = (startingChild as ExpandableNotificationRow?)?.entry + buffer.log(TAG, LogLevel.INFO, { + str1 = entry?.key ?: "no entry" + }, { + "Dragged down in locked down shade on $str1" + }) + } + + fun logDraggedDown(startingChild: View?, dragLengthY: Int) { + val entry = (startingChild as ExpandableNotificationRow?)?.entry + buffer.log(TAG, LogLevel.INFO, { + str1 = entry?.key ?: "no entry" + }, { + "Drag down succeeded on $str1" + }) + // Do logging to event log not just our own buffer + lockscreenGestureLogger.write( + MetricsEvent.ACTION_LS_SHADE, + (dragLengthY / displayMetrics.density).toInt(), + 0 /* velocityDp */) + lockscreenGestureLogger.log( + LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN) + } + + fun logDefaultGoToFullShadeAnimation(delay: Long) { + buffer.log(TAG, LogLevel.DEBUG, { + long1 = delay + }, { + "Default animation started to full shade with delay $long1" + }) + } + + fun logTryGoToLockedShade(keyguard: Boolean) { + buffer.log(TAG, LogLevel.INFO, { + bool1 = keyguard + }, { + "Trying to go to locked shade " + if (bool1) "from keyguard" else "not from keyguard" + }) + } + + fun logShadeDisabledOnGoToLockedShade() { + buffer.log(TAG, LogLevel.WARNING, {}, { + "The shade was disabled when trying to go to the locked shade" + }) + } + + fun logShowBouncerOnGoToLockedShade() { + buffer.log(TAG, LogLevel.INFO, {}, { + "Showing bouncer when trying to go to the locked shade" + }) + } + + fun logGoingToLockedShade(customAnimationHandler: Boolean) { + buffer.log(TAG, LogLevel.INFO, { + bool1 = customAnimationHandler + }, { + "Going to locked shade " + if (customAnimationHandler) "with" else "without" + + " a custom handler" + }) + } + + fun logOnHideKeyguard() { + buffer.log(TAG, LogLevel.INFO, {}, { + "Notified that the keyguard is being hidden" + }) + } + + fun logPulseExpansionStarted() { + buffer.log(TAG, LogLevel.INFO, {}, { + "Pulse Expansion has started" + }) + } + + fun logPulseExpansionFinished(cancelled: Boolean) { + if (cancelled) { + buffer.log(TAG, LogLevel.INFO, {}, { + "Pulse Expansion is requested to cancel" + }) + } else { + buffer.log(TAG, LogLevel.INFO, {}, { + "Pulse Expansion is requested to finish" + }) + } + } + + fun logDragDownAnimation(target: Float) { + buffer.log(TAG, LogLevel.DEBUG, { + double1 = target.toDouble() + }, { + "Drag down amount animating to " + double1 + }) + } + + fun logAnimationCancelled(isPulse: Boolean) { + if (isPulse) { + buffer.log(TAG, LogLevel.DEBUG, {}, { + "Pulse animation cancelled" + }) + } else { + buffer.log(TAG, LogLevel.DEBUG, {}, { + "drag down animation cancelled" + }) + } + } + + fun logDragDownAmountResetWhenFullyCollapsed() { + buffer.log(TAG, LogLevel.WARNING, {}, { + "Drag down amount stuck and reset after shade was fully collapsed" + }) + } + + fun logPulseHeightNotResetWhenFullyCollapsed() { + buffer.log(TAG, LogLevel.WARNING, {}, { + "Pulse height stuck and reset after shade was fully collapsed" + }) + } + + fun logGoingToLockedShadeAborted() { + buffer.log(TAG, LogLevel.INFO, {}, { + "Going to the Locked Shade has been aborted" + }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 03b1627b03c6..4d625cfbfbf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -183,7 +183,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.notification.stack.MediaHeaderView; +import com.android.systemui.statusbar.notification.stack.MediaContainerView; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -1581,7 +1581,7 @@ public class NotificationPanelViewController extends PanelViewController { if (row.isRemoved()) { continue; } - } else if (child instanceof MediaHeaderView) { + } else if (child instanceof MediaContainerView) { if (child.getVisibility() == GONE) { continue; } @@ -2433,7 +2433,7 @@ public class NotificationPanelViewController extends PanelViewController { // mLockscreenShadeTransitionController.getDragProgress change. // When in lockscreen, getDragProgress indicates the true expanded fraction of QS float shadeExpandedFraction = mTransitioningToFullShadeProgress > 0 - ? mLockscreenShadeTransitionController.getDragProgress() + ? mLockscreenShadeTransitionController.getQSDragProgress() : getExpandedFraction(); mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); @@ -4766,7 +4766,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public float getLockscreenShadeDragProgress() { - return mLockscreenShadeTransitionController.getDragProgress(); + return mLockscreenShadeTransitionController.getQSDragProgress(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index 51f2e6728b80..cf9a5dba0320 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -490,7 +490,8 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types) { + public void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar) { if (displayId != mDisplayId) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index e2d0bb9991ed..31407b1d5cb2 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -66,6 +66,8 @@ public class ThemeOverlayApplier implements Dumpable { "android.theme.customization.accent_color"; static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = "android.theme.customization.system_palette"; + static final String OVERLAY_CATEGORY_THEME_STYLE = + "android.theme.customization.theme_style"; static final String OVERLAY_COLOR_SOURCE = "android.theme.customization.color_source"; diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index c86d77bfcc9f..fb8055199d61 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -66,6 +66,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.monet.ColorScheme; +import com.android.systemui.monet.Style; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; @@ -121,8 +122,8 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private boolean mNeedsOverlayCreation; // Dominant color extracted from wallpaper, NOT the color used on the overlay protected int mMainWallpaperColor = Color.TRANSPARENT; - // Accent color extracted from wallpaper, NOT the color used on the overlay - protected int mWallpaperAccentColor = Color.TRANSPARENT; + // Theme variant: Vibrant, Tonal, Expressive, etc + private Style mThemeStyle = Style.TONAL_SPOT; // Accent colors overlay private FabricatedOverlay mSecondaryOverlay; // Neutral system colors overlay @@ -453,26 +454,20 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { private void reevaluateSystemTheme(boolean forceReload) { final WallpaperColors currentColors = mCurrentColors.get(mUserTracker.getUserId()); final int mainColor; - final int accentCandidate; if (currentColors == null) { mainColor = Color.TRANSPARENT; - accentCandidate = Color.TRANSPARENT; } else { mainColor = getNeutralColor(currentColors); - accentCandidate = getAccentColor(currentColors); } - if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate - && !forceReload) { + if (mMainWallpaperColor == mainColor && !forceReload) { return; } - mMainWallpaperColor = mainColor; - mWallpaperAccentColor = accentCandidate; if (mIsMonetEnabled) { - mSecondaryOverlay = getOverlay(mWallpaperAccentColor, ACCENT); - mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL); + mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); + mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); mNeedsOverlayCreation = true; if (DEBUG) { Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay @@ -497,11 +492,11 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { /** * Given a color candidate, return an overlay definition. */ - protected @Nullable FabricatedOverlay getOverlay(int color, int type) { + protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) { boolean nightMode = (mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; - mColorScheme = new ColorScheme(color, nightMode); + mColorScheme = new ColorScheme(color, nightMode, style); List<Integer> colorShades = type == ACCENT ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors(); String name = type == ACCENT ? "accent" : "neutral"; @@ -537,6 +532,7 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { currentUser); if (DEBUG) Log.d(TAG, "updateThemeOverlays. Setting: " + overlayPackageJson); final Map<String, OverlayIdentifier> categoryToPackage = new ArrayMap<>(); + Style newStyle = mThemeStyle; if (!TextUtils.isEmpty(overlayPackageJson)) { try { JSONObject object = new JSONObject(overlayPackageJson); @@ -547,11 +543,25 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { categoryToPackage.put(category, identifier); } } + + try { + newStyle = Style.valueOf( + object.getString(ThemeOverlayApplier.OVERLAY_CATEGORY_THEME_STYLE)); + } catch (IllegalArgumentException e) { + newStyle = Style.TONAL_SPOT; + } } catch (JSONException e) { Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e); } } + if (mIsMonetEnabled && newStyle != mThemeStyle) { + mThemeStyle = newStyle; + mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle); + mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle); + mNeedsOverlayCreation = true; + } + // Let's generate system overlay if the style picker decided to override it. OverlayIdentifier systemPalette = categoryToPackage.get(OVERLAY_CATEGORY_SYSTEM_PALETTE); if (mIsMonetEnabled && systemPalette != null && systemPalette.getPackageName() != null) { @@ -561,9 +571,11 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { colorString = "#" + colorString; } int color = Color.parseColor(colorString); - mNeutralOverlay = getOverlay(color, NEUTRAL); + mNeutralOverlay = getOverlay(color, NEUTRAL, mThemeStyle); + mSecondaryOverlay = getOverlay(color, ACCENT, mThemeStyle); mNeedsOverlayCreation = true; categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); } catch (Exception e) { // Color.parseColor doesn't catch any exceptions from the calls it makes Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName(), e); @@ -574,30 +586,6 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { // setting. We need to sanitize the input, otherwise the overlay transaction will // fail. categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); - } catch (NumberFormatException e) { - // This is a package name. All good, let's continue - } - } - - // Same for accent color. - OverlayIdentifier accentPalette = categoryToPackage.get(OVERLAY_CATEGORY_ACCENT_COLOR); - if (mIsMonetEnabled && accentPalette != null && accentPalette.getPackageName() != null) { - try { - String colorString = accentPalette.getPackageName().toLowerCase(); - if (!colorString.startsWith("#")) { - colorString = "#" + colorString; - } - int color = Color.parseColor(colorString); - mSecondaryOverlay = getOverlay(color, ACCENT); - mNeedsOverlayCreation = true; - categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); - } catch (Exception e) { - // Color.parseColor doesn't catch any exceptions from the calls it makes - Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName(), e); - } - } else if (!mIsMonetEnabled && accentPalette != null) { - try { - Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); } catch (NumberFormatException e) { // This is a package name. All good, let's continue @@ -642,7 +630,6 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mSystemColors=" + mCurrentColors); pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor)); - pw.println("mWallpaperAccentColor=" + Integer.toHexString(mWallpaperAccentColor)); pw.println("mSecondaryOverlay=" + mSecondaryOverlay); pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mIsMonetEnabled=" + mIsMonetEnabled); @@ -650,5 +637,6 @@ public class ThemeOverlayController extends CoreStartable implements Dumpable { pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); pw.println("mAcceptColorEvents=" + mAcceptColorEvents); pw.println("mDeferredThemeEvaluation=" + mDeferredThemeEvaluation); + pw.println("mThemeStyle=" + mThemeStyle); } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt new file mode 100644 index 000000000000..1f5959e879bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProvider.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.unfold + +import android.annotation.IntDef +import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener +import com.android.systemui.unfold.FoldStateLoggingProvider.LoggedFoldedStates + +/** Reports device fold states for logging purposes. */ +// TODO(b/198305865): Log state changes. +interface FoldStateLoggingProvider : CallbackController<FoldStateLoggingListener> { + + fun init() + fun uninit() + + interface FoldStateLoggingListener { + fun onFoldUpdate(foldStateUpdate: FoldStateChange) + } + + @IntDef(prefix = ["LOGGED_FOLD_STATE_"], value = [FULLY_OPENED, FULLY_CLOSED, HALF_OPENED]) + @Retention(AnnotationRetention.SOURCE) + annotation class LoggedFoldedStates +} + +data class FoldStateChange( + @LoggedFoldedStates val previous: Int, + @LoggedFoldedStates val current: Int, + val dtMillis: Long +) + +const val FULLY_OPENED = 1 +const val FULLY_CLOSED = 2 +const val HALF_OPENED = 3 diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt new file mode 100644 index 000000000000..2683971f852c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldStateLoggingProviderImpl.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.unfold + +import android.util.Log +import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener +import com.android.systemui.unfold.FoldStateLoggingProvider.LoggedFoldedStates +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING +import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING +import com.android.systemui.unfold.updates.FoldStateProvider +import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate +import com.android.systemui.util.time.SystemClock + +/** + * Reports device fold states for logging purposes. + * + * Wraps the state provided by [FoldStateProvider] to output only [FULLY_OPENED], [FULLY_CLOSED] and + * [HALF_OPENED] for logging purposes. + * + * Note that [HALF_OPENED] state is only emitted after the device angle is stable for some timeout. + * Check [FoldStateProvider] impl for it. + * + * This doesn't log the following transitions: + * - [HALF_OPENED] -> [FULLY_OPENED]: not interesting, as there is no transition going on + * - [HALF_OPENED] -> [HALF_OPENED]: not meaningful. + */ +class FoldStateLoggingProviderImpl( + private val foldStateProvider: FoldStateProvider, + private val clock: SystemClock +) : FoldStateLoggingProvider, FoldStateProvider.FoldUpdatesListener { + + private val outputListeners: MutableList<FoldStateLoggingListener> = mutableListOf() + + @LoggedFoldedStates private var lastState: Int? = null + private var actionStartMillis: Long? = null + + override fun init() { + foldStateProvider.addCallback(this) + foldStateProvider.start() + } + + override fun uninit() { + foldStateProvider.removeCallback(this) + foldStateProvider.stop() + } + + override fun onHingeAngleUpdate(angle: Float) {} + + override fun onFoldUpdate(@FoldUpdate update: Int) { + val now = clock.elapsedRealtime() + when (update) { + FOLD_UPDATE_START_OPENING -> { + lastState = FULLY_CLOSED + actionStartMillis = now + } + FOLD_UPDATE_START_CLOSING -> actionStartMillis = now + FOLD_UPDATE_FINISH_HALF_OPEN -> dispatchState(HALF_OPENED) + FOLD_UPDATE_FINISH_FULL_OPEN -> dispatchState(FULLY_OPENED) + FOLD_UPDATE_FINISH_CLOSED -> dispatchState(FULLY_CLOSED) + } + } + + private fun dispatchState(@LoggedFoldedStates current: Int) { + val now = clock.elapsedRealtime() + val previous = lastState + val lastActionStart = actionStartMillis + + if (previous != null && previous != current && lastActionStart != null) { + val time = now - lastActionStart + val foldStateChange = FoldStateChange(previous, current, time) + outputListeners.forEach { it.onFoldUpdate(foldStateChange) } + if (DEBUG) { + Log.d(TAG, "From $previous to $current in $time") + } + } + + actionStartMillis = null + lastState = current + } + + override fun addCallback(listener: FoldStateLoggingListener) { + outputListeners.add(listener) + } + + override fun removeCallback(listener: FoldStateLoggingListener) { + outputListeners.remove(listener) + } +} + +private const val DEBUG = false +private const val TAG = "FoldStateLoggingProviderImpl" diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index ccde3162b177..07f9c5487c41 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -17,10 +17,11 @@ package com.android.systemui.unfold import com.android.keyguard.KeyguardUnfoldTransition -import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider -import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider +import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.util.kotlin.getOrNull import dagger.BindsInstance import dagger.Module import dagger.Provides @@ -36,15 +37,17 @@ annotation class SysUIUnfoldScope /** * Creates an injectable [SysUIUnfoldComponent] that provides objects that have been scoped with - * [@SysUIUnfoldScope]. Since [SysUIUnfoldComponent] depends upon: + * [@SysUIUnfoldScope]. + * + * Since [SysUIUnfoldComponent] depends upon: * * [Optional<UnfoldTransitionProgressProvider>] * * [Optional<ScopedUnfoldTransitionProgressProvider>] * * [Optional<NaturalRotationProgressProvider>] + * * no objects will get constructed if these parameters are empty. */ @Module(subcomponents = [SysUIUnfoldComponent::class]) class SysUIUnfoldModule { - constructor() {} @Provides @SysUISingleton @@ -53,12 +56,16 @@ class SysUIUnfoldModule { rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>, @Named(UNFOLD_STATUS_BAR) scopedProvider: Optional<ScopedUnfoldTransitionProgressProvider>, factory: SysUIUnfoldComponent.Factory - ) = - provider.flatMap { p1 -> - rotationProvider.flatMap { p2 -> - scopedProvider.map { p3 -> factory.create(p1, p2, p3) } - } + ): Optional<SysUIUnfoldComponent> { + val p1 = provider.getOrNull() + val p2 = rotationProvider.getOrNull() + val p3 = scopedProvider.getOrNull() + return if (p1 == null || p2 == null || p3 == null) { + Optional.empty() + } else { + Optional.of(factory.create(p1, p2, p3)) } + } } @SysUIUnfoldScope diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 75dfd48ad9f3..f2c156108ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -24,8 +24,10 @@ import android.view.IWindowManager import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.util.time.SystemClockImpl import com.android.wm.shell.unfold.ShellUnfoldProgressProvider import dagger.Lazy import dagger.Module @@ -48,7 +50,7 @@ class UnfoldTransitionModule { sensorManager: SensorManager, @Main executor: Executor, @Main handler: Handler - ) = + ): Optional<UnfoldTransitionProgressProvider> = if (config.isEnabled) { Optional.of( createUnfoldTransitionProgressProvider( @@ -59,15 +61,47 @@ class UnfoldTransitionModule { sensorManager, handler, executor, - tracingTagPrefix = "systemui" - ) - ) + tracingTagPrefix = "systemui")) } else { Optional.empty() } @Provides @Singleton + fun provideFoldStateProvider( + context: Context, + config: UnfoldTransitionConfig, + screenStatusProvider: Lazy<LifecycleScreenStatusProvider>, + deviceStateManager: DeviceStateManager, + sensorManager: SensorManager, + @Main executor: Executor, + @Main handler: Handler + ): Optional<FoldStateProvider> = + if (!config.isHingeAngleEnabled) { + Optional.empty() + } else { + Optional.of( + createFoldStateProvider( + context, + config, + screenStatusProvider.get(), + deviceStateManager, + sensorManager, + handler, + executor)) + } + + @Provides + @Singleton + fun providesFoldStateLoggingProvider( + optionalFoldStateProvider: Optional<FoldStateProvider> + ): Optional<FoldStateLoggingProvider> = + optionalFoldStateProvider.map { foldStateProvider -> + FoldStateLoggingProviderImpl(foldStateProvider, SystemClockImpl()) + } + + @Provides + @Singleton fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig = createConfig(context) @@ -77,13 +111,9 @@ class UnfoldTransitionModule { context: Context, windowManager: IWindowManager, unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> - ) = - unfoldTransitionProgressProvider.map { - provider -> NaturalRotationUnfoldProgressProvider( - context, - windowManager, - provider - ) + ): Optional<NaturalRotationUnfoldProgressProvider> = + unfoldTransitionProgressProvider.map { provider -> + NaturalRotationUnfoldProgressProvider(context, windowManager, provider) } @Provides @@ -91,10 +121,8 @@ class UnfoldTransitionModule { @Singleton fun provideStatusBarScopedTransitionProvider( source: Optional<NaturalRotationUnfoldProgressProvider> - ) = - source.map { - provider -> ScopedUnfoldTransitionProgressProvider(provider) - } + ): Optional<ScopedUnfoldTransitionProgressProvider> = + source.map { provider -> ScopedUnfoldTransitionProgressProvider(provider) } @Provides @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index 2e183b38a7dc..ba9b638fac99 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -223,8 +223,7 @@ public class WalletScreenController implements } mUiEventLogger.log(WalletUiEvent.QAW_CLICK_CARD); - mActivityStarter.startActivity( - ((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent().getIntent(), true); + mActivityStarter.startPendingIntentDismissingKeyguard(cardInfo.getPendingIntent()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index e8f6de76138d..5e9579cb6a83 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -761,7 +761,7 @@ public class BubblesManager implements Dumpable { } static BubbleEntry notifToBubbleEntry(NotificationEntry e) { - return new BubbleEntry(e.getSbn(), e.getRanking(), e.isClearable(), + return new BubbleEntry(e.getSbn(), e.getRanking(), e.isDismissable(), e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(), e.shouldSuppressPeek()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 8fdcaddc93fb..5ad651728c66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; @@ -60,6 +61,7 @@ import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IRemoteMagnificationAnimationCallback; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; @@ -75,6 +77,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -89,8 +92,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Mock private Handler mHandler; @Mock - private WindowMagnificationAnimationController mWindowMagnificationAnimationController; - @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock private MirrorWindowControl mMirrorWindowControl; @@ -101,6 +102,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private TestableWindowManager mWindowManager; private SysUiState mSysUiState = new SysUiState(); private Resources mResources; + private WindowMagnificationAnimationController mWindowMagnificationAnimationController; private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; @@ -125,10 +127,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return null; }).when(mHandler).post( any(Runnable.class)); - mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); mResources = getContext().getOrCreateTestableResources().getResources(); + mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( + mContext); mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); @@ -157,6 +160,52 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { } @Test + public void enableWindowMagnification_notifySourceBoundsChanged() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + Float.NaN, /* magnificationFrameOffsetRatioX= */ 0, + /* magnificationFrameOffsetRatioY= */ 0, null); + }); + + // Waits for the surface created + verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged( + (eq(mContext.getDisplayId())), any()); + } + + @Test + public void enableWindowMagnification_withAnimation_schedulesFrame() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(2.0f, 10, + 10, /* magnificationFrameOffsetRatioX= */ 0, + /* magnificationFrameOffsetRatioY= */ 0, + Mockito.mock(IRemoteMagnificationAnimationCallback.class)); + }); + + verify(mSfVsyncFrameProvider, + timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeast(2)).postFrameCallback(any()); + } + + @Test + public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() { + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + Float.NaN, 0, 0, null); + }); + + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.moveWindowMagnifier(10, 10); + }); + + final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class); + verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged( + (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture()); + assertEquals(mWindowMagnificationController.getCenterX(), + sourceBoundsCaptor.getValue().exactCenterX(), 0); + assertEquals(mWindowMagnificationController.getCenterY(), + sourceBoundsCaptor.getValue().exactCenterY(), 0); + } + + @Test public void enableWindowMagnification_systemGestureExclusionRectsIsSet() { mInstrumentation.runOnMainSync(() -> { mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java index c898150d857c..343658d31272 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java @@ -149,6 +149,16 @@ public class WindowMagnificationTest extends SysuiTestCase { } @Test + public void onDrag_enabled_notifyCallback() throws RemoteException { + mCommandQueue.requestWindowMagnificationConnection(true); + waitForIdleSync(); + + mWindowMagnification.onDrag(TEST_DISPLAY); + + verify(mConnectionCallback).onDrag(TEST_DISPLAY); + } + + @Test public void onConfigurationChanged_updateModeSwitches() { final Configuration config = new Configuration(); config.densityDpi = Configuration.DENSITY_DPI_ANY; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt index 8cc2776bc16b..43d9a755269f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController -import com.android.systemui.statusbar.notification.stack.MediaHeaderView +import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView @@ -57,7 +57,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() - private val mediaHeaderView: MediaHeaderView = MediaHeaderView(context, null) + private val mediaContainerView: MediaContainerView = MediaContainerView(context, null) private val hostView = UniqueObjectHostView(context) private lateinit var keyguardMediaController: KeyguardMediaController @@ -78,7 +78,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { context, configurationController ) - keyguardMediaController.attachSinglePaneContainer(mediaHeaderView) + keyguardMediaController.attachSinglePaneContainer(mediaContainerView) keyguardMediaController.useSplitShade = false } @@ -88,7 +88,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { keyguardMediaController.refreshMediaPosition() - assertThat(mediaHeaderView.visibility).isEqualTo(GONE) + assertThat(mediaContainerView.visibility).isEqualTo(GONE) } @Test @@ -102,7 +102,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private fun testStateVisibility(state: Int, visibility: Int) { whenever(statusBarStateController.state).thenReturn(state) keyguardMediaController.refreshMediaPosition() - assertThat(mediaHeaderView.visibility).isEqualTo(visibility) + assertThat(mediaContainerView.visibility).isEqualTo(visibility) } @Test @@ -112,7 +112,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { keyguardMediaController.refreshMediaPosition() - assertThat(mediaHeaderView.visibility).isEqualTo(GONE) + assertThat(mediaContainerView.visibility).isEqualTo(GONE) } @Test @@ -130,7 +130,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) assertThat(splitShadeContainer.visibility).isEqualTo(GONE) - assertThat(mediaHeaderView.visibility).isEqualTo(VISIBLE) + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } @Test @@ -149,6 +149,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() { keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) assertTrue("HostView wasn't attached to the single pane container", - mediaHeaderView.childCount == 1) + mediaContainerView.childCount == 1) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index 8cd7d94d8952..5a0604883863 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -100,4 +100,34 @@ public class ColorSchemeTest extends SysuiTestCase { Cam cam = Cam.fromInt(tertiaryMid); Assert.assertEquals(cam.getHue(), 50.0, 10.0); } + + @Test + public void testSpritz() { + int colorInt = 0xffB3588A; // H350 C50 T50 + ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */, + Style.SPRITZ /* style */); + int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2); + Cam cam = Cam.fromInt(primaryMid); + Assert.assertEquals(cam.getChroma(), 4.0, 1.0); + } + + @Test + public void testVibrant() { + int colorInt = 0xffB3588A; // H350 C50 T50 + ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */, + Style.VIBRANT /* style */); + int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); + Cam cam = Cam.fromInt(neutralMid); + Assert.assertEquals(cam.getChroma(), 8.0, 1.0); + } + + @Test + public void testExpressive() { + int colorInt = 0xffB3588A; // H350 C50 T50 + ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */, + Style.EXPRESSIVE /* style */); + int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2); + Cam cam = Cam.fromInt(neutralMid); + Assert.assertEquals(cam.getChroma(), 16.0, 1.0); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index 2e1fb07e6aa5..8ccf5596b0ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -218,7 +218,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { String expected = "TestableQSPanelControllerBase:\n" + " Tile records:\n" + " " + mockTileString + "\n" - + " " + mockTileViewString + "\n"; + + " " + mockTileViewString + "\n" + + " media bounds: null\n"; assertEquals(expected, w.getBuffer().toString()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 5de4c115ee56..6c29ecc7ae50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -151,17 +151,17 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testShowTransient() { int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}; - mCommandQueue.showTransient(DEFAULT_DISPLAY, types); + mCommandQueue.showTransient(DEFAULT_DISPLAY, types, true /* isGestureOnSystemBar */); waitForIdleSync(); - verify(mCallbacks).showTransient(eq(DEFAULT_DISPLAY), eq(types)); + verify(mCallbacks).showTransient(eq(DEFAULT_DISPLAY), eq(types), eq(true)); } @Test public void testShowTransientForSecondaryDisplay() { int[] types = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}; - mCommandQueue.showTransient(SECONDARY_DISPLAY, types); + mCommandQueue.showTransient(SECONDARY_DISPLAY, types, true /* isGestureOnSystemBar */); waitForIdleSync(); - verify(mCallbacks).showTransient(eq(SECONDARY_DISPLAY), eq(types)); + verify(mCallbacks).showTransient(eq(SECONDARY_DISPLAY), eq(types), eq(true)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt index 38ad6b85f8aa..c0d1155b2700 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt @@ -37,7 +37,7 @@ class DisableFlagsLoggerTest : SysuiTestCase() { private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags) @Test - fun getDisableFlagsString_oldAndNewSame_statesLoggedButDiffsNotLogged() { + fun getDisableFlagsString_oldAndNewSame_newAndUnchangedLoggedOldNotLogged() { val state = DisableFlagsLogger.DisableState( 0b111, // ABC 0b01 // mN @@ -45,10 +45,9 @@ class DisableFlagsLoggerTest : SysuiTestCase() { val result = disableFlagsLogger.getDisableFlagsString(state, state) - assertThat(result).contains("Old: ABC.mN") - assertThat(result).contains("New: ABC.mN") - assertThat(result).doesNotContain("(") - assertThat(result).doesNotContain(")") + assertThat(result).doesNotContain("Old") + assertThat(result).contains("ABC.mN") + assertThat(result).contains("(unchanged)") } @Test @@ -66,7 +65,7 @@ class DisableFlagsLoggerTest : SysuiTestCase() { assertThat(result).contains("Old: ABC.mN") assertThat(result).contains("New: abC.Mn") - assertThat(result).contains("(ab.Mn)") + assertThat(result).contains("(changed: ab.Mn)") } @Test @@ -82,7 +81,7 @@ class DisableFlagsLoggerTest : SysuiTestCase() { ) ) - assertThat(result).contains("(.n)") + assertThat(result).contains("(changed: .n)") } @Test @@ -96,8 +95,7 @@ class DisableFlagsLoggerTest : SysuiTestCase() { ) assertThat(result).doesNotContain("Old") - assertThat(result).contains("New: abC.mN") - // We have no state to diff on, so we shouldn't see any diff in parentheses + assertThat(result).contains("abC.mN") assertThat(result).doesNotContain("(") assertThat(result).doesNotContain(")") } @@ -141,7 +139,7 @@ class DisableFlagsLoggerTest : SysuiTestCase() { ) ) - assertThat(result).contains("local modification: Abc.Mn (A.M)") + assertThat(result).contains("local modification: Abc.Mn (changed: A.M)") } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 89435ae164b5..13b8e81a0711 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -9,6 +9,8 @@ import com.android.systemui.ExpandHelper import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBuffer import com.android.systemui.media.MediaHierarchyManager import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QS @@ -18,7 +20,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.phone.KeyguardBypassController -import com.android.systemui.statusbar.phone.LockscreenGestureLogger +import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.NotificationPanelViewController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.StatusBar @@ -56,7 +58,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { lateinit var transitionController: LockscreenShadeTransitionController lateinit var row: ExpandableNotificationRow @Mock lateinit var statusbarStateController: SysuiStatusBarStateController - @Mock lateinit var lockscreenGestureLogger: LockscreenGestureLogger + @Mock lateinit var logger: LSShadeTransitionLogger + @Mock lateinit var dumpManager: DumpManager @Mock lateinit var keyguardBypassController: KeyguardBypassController @Mock lateinit var lockScreenUserManager: NotificationLockscreenUserManager @Mock lateinit var falsingCollector: FalsingCollector @@ -66,6 +69,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var scrimController: ScrimController @Mock lateinit var configurationController: ConfigurationController @Mock lateinit var falsingManager: FalsingManager + @Mock lateinit var buffer: LogBuffer @Mock lateinit var notificationPanelController: NotificationPanelViewController @Mock lateinit var nsslController: NotificationStackScrollLayoutController @Mock lateinit var depthController: NotificationShadeDepthController @@ -86,18 +90,18 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { .addOverride(R.bool.config_use_split_notification_shade, false) transitionController = LockscreenShadeTransitionController( statusBarStateController = statusbarStateController, - lockscreenGestureLogger = lockscreenGestureLogger, + logger = logger, keyguardBypassController = keyguardBypassController, lockScreenUserManager = lockScreenUserManager, falsingCollector = falsingCollector, ambientState = ambientState, - displayMetrics = displayMetrics, mediaHierarchyManager = mediaHierarchyManager, scrimController = scrimController, depthController = depthController, context = context, configurationController = configurationController, - falsingManager = falsingManager + falsingManager = falsingManager, + dumpManager = dumpManager ) whenever(nsslController.view).thenReturn(stackscroller) whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 41163bf433c1..a7f8b6e01949 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.notification.collection; +import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; @@ -797,7 +799,7 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test - public void testDismissingSummaryDoesNotDismissForegroundServiceChildren() { + public void testDismissingSummaryDoesDismissForegroundServiceChildren() { // GIVEN a collection with three grouped notifs in it CollectionEvent notif0 = postNotif( buildNotif(TEST_PACKAGE, 0) @@ -814,7 +816,31 @@ public class NotifCollectionTest extends SysuiTestCase { // WHEN the summary is dismissed mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); - // THEN the foreground service child is not dismissed + // THEN the foreground service child is dismissed + assertEquals(DISMISSED, notif0.entry.getDismissState()); + assertEquals(PARENT_DISMISSED, notif1.entry.getDismissState()); + assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); + } + + @Test + public void testDismissingSummaryDoesNotDismissOngoingChildren() { + // GIVEN a collection with three grouped notifs in it + CollectionEvent notif0 = postNotif( + buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, GROUP_1) + .setGroupSummary(mContext, true)); + CollectionEvent notif1 = postNotif( + buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, GROUP_1) + .setFlag(mContext, FLAG_ONGOING_EVENT, true)); + CollectionEvent notif2 = postNotif( + buildNotif(TEST_PACKAGE, 2) + .setGroup(mContext, GROUP_1)); + + // WHEN the summary is dismissed + mCollection.dismissNotification(notif0.entry, defaultStats(notif0.entry)); + + // THEN the ongoing child is not dismissed assertEquals(DISMISSED, notif0.entry.getDismissState()); assertEquals(NOT_DISMISSED, notif1.entry.getDismissState()); assertEquals(PARENT_DISMISSED, notif2.entry.getDismissState()); @@ -1427,6 +1453,37 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mCollectionListener, never()).onEntryUpdated(any(), anyBoolean()); } + @Test + public void testCannotDismissOngoingNotificationChildren() { + // GIVEN an ongoing notification + final NotificationEntry container = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE) + .setId(47) + .setGroup(mContext, "group") + .setFlag(mContext, FLAG_ONGOING_EVENT, true) + .build(); + + // THEN its children are not dismissible + assertFalse(mCollection.shouldAutoDismissChildren( + container, container.getSbn().getGroupKey())); + } + + @Test + public void testCanDismissFgsNotificationChildren() { + // GIVEN an FGS but not ongoing notification + final NotificationEntry container = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE) + .setId(47) + .setGroup(mContext, "group") + .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true) + .build(); + container.setDismissState(NOT_DISMISSED); + + // THEN its children are dismissible + assertTrue(mCollection.shouldAutoDismissChildren( + container, container.getSbn().getGroupKey())); + } + private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { return new NotificationEntryBuilder() .setPkg(pkg) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index 5271745a2b44..f77381000ae2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry @@ -31,18 +32,18 @@ import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.notification.stack.PriorityBucket import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test -import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest class NodeSpecBuilderTest : SysuiTestCase() { - @Mock - private lateinit var viewBarn: NotifViewBarn + private val mediaContainerController: MediaContainerController = mock() + private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock() + private val viewBarn: NotifViewBarn = mock() private var rootController: NodeController = buildFakeController("rootController") private var headerController0: NodeController = buildFakeController("header0") @@ -66,13 +67,12 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - `when`(viewBarn.requireNodeController(any())).thenAnswer { + whenever(mediaContainerController.mediaContainerView).thenReturn(mock()) + whenever(viewBarn.requireNodeController(any())).thenAnswer { fakeViewBarn.getViewByEntry(it.getArgument(0)) } - specBuilder = NodeSpecBuilder(viewBarn) + specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, viewBarn) } @Test @@ -109,6 +109,30 @@ class NodeSpecBuilderTest : SysuiTestCase() { @Test fun testSimpleMapping() { checkOutput( + // GIVEN a simple flat list of notifications all in the same headerless section + listOf( + notif(0, section0NoHeader), + notif(1, section0NoHeader), + notif(2, section0NoHeader), + notif(3, section0NoHeader) + ), + + // THEN we output a similarly simple flag list of nodes + tree( + notifNode(0), + notifNode(1), + notifNode(2), + notifNode(3) + ) + ) + } + + @Test + fun testSimpleMappingWithMedia() { + // WHEN media controls are enabled + whenever(sectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true) + + checkOutput( // GIVEN a simple flat list of notifications all in the same headerless section listOf( notif(0, section0NoHeader), @@ -117,8 +141,9 @@ class NodeSpecBuilderTest : SysuiTestCase() { notif(3, section0NoHeader) ), - // THEN we output a similarly simple flag list of nodes + // THEN we output a similarly simple flag list of nodes, with media at the top tree( + node(mediaContainerController), notifNode(0), notifNode(1), notifNode(2), @@ -333,7 +358,7 @@ private class FakeViewBarn { private fun buildFakeController(name: String): NodeController { val controller = Mockito.mock(NodeController::class.java) - `when`(controller.nodeLabel).thenReturn(name) + whenever(controller.nodeLabel).thenReturn(name) return controller } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index e9e191107e5c..4ea932157457 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.notification.row; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; +import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; import static org.junit.Assert.assertEquals; @@ -29,6 +32,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -330,4 +334,28 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertTrue(row.getIsNonblockable()); } + + @Test + public void testCanDismissNoClear() throws Exception { + ExpandableNotificationRow row = + mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); + modifySbn(row.getEntry()) + .setFlag(mContext, FLAG_NO_CLEAR, true) + .build(); + row.performDismiss(false); + verify(mNotificationTestHelper.mOnUserInteractionCallback) + .onDismiss(any(), anyInt(), any()); + } + + @Test + public void testCannotDismissOngoing() throws Exception { + ExpandableNotificationRow row = + mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification()); + modifySbn(row.getEntry()) + .setFlag(mContext, FLAG_ONGOING_EVENT, true) + .build(); + row.performDismiss(false); + verify(mNotificationTestHelper.mOnUserInteractionCallback, never()) + .onDismiss(any(), anyInt(), any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java index 9039e1b8f8e3..1f92b0a42061 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java @@ -57,7 +57,7 @@ public class FooterViewTest extends SysuiTestCase { @Test public void setDismissOnClick() { - mView.setDismissButtonClickListener(mock(View.OnClickListener.class)); + mView.setClearAllButtonClickListener(mock(View.OnClickListener.class)); assertTrue(mView.findSecondaryView().hasOnClickListeners()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index e4721b13332c..4457ae046260 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -115,6 +115,7 @@ public class NotificationTestHelper { private final IconManager mIconManager; private StatusBarStateController mStatusBarStateController; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; + public final OnUserInteractionCallback mOnUserInteractionCallback; public NotificationTestHelper( Context context, @@ -173,6 +174,7 @@ public class NotificationTestHelper { verify(collection).addCollectionListener(collectionListenerCaptor.capture()); mBindPipelineEntryListener = collectionListenerCaptor.getValue(); mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class); + mOnUserInteractionCallback = mock(OnUserInteractionCallback.class); } /** @@ -499,7 +501,7 @@ public class NotificationTestHelper { new FalsingCollectorFake(), mStatusBarStateController, mPeopleNotificationIdentifier, - mock(OnUserInteractionCallback.class), + mOnUserInteractionCallback, Optional.of(mock(BubblesManager.class)), mock(NotificationGutsManager.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 4d2c0c306c00..ac9fcc064375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -41,7 +41,6 @@ import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -54,6 +53,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.render.MediaContainerController; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -87,6 +87,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Mock private NotificationRowComponent mNotificationRowComponent; @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; @Mock private NotificationSectionsLogger mLogger; + @Mock private MediaContainerController mMediaContainerController; @Mock private SectionHeaderController mIncomingHeaderController; @Mock private SectionHeaderController mPeopleHeaderController; @Mock private SectionHeaderController mAlertingHeaderController; @@ -114,6 +115,8 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { }); when(mNotificationRowComponent.getActivatableNotificationViewController()) .thenReturn(mActivatableNotificationViewController); + when(mMediaContainerController.getMediaContainerView()) + .thenReturn(mock(MediaContainerView.class)); when(mIncomingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); when(mPeopleHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); when(mAlertingHeaderController.getHeaderView()).thenReturn(mock(SectionHeaderView.class)); @@ -126,6 +129,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mSectionsFeatureManager, mLogger, mNotifPipelineFlags, + mMediaContainerController, mIncomingHeaderController, mPeopleHeaderController, mAlertingHeaderController, @@ -134,7 +138,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { // Required in order for the header inflation to work properly when(mNssl.generateLayoutParams(any(AttributeSet.class))) .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext)); + mSectionsManager.initialize(mNssl); when(mNssl.indexOfChild(any(View.class))).thenReturn(-1); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); @@ -142,7 +146,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Test(expected = IllegalStateException.class) public void testDuplicateInitializeThrows() { - mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext)); + mSectionsManager.initialize(mNssl); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 4cc1be696637..04c6f6c63927 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -379,18 +379,18 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Test public void testDismissListener() { - ArgumentCaptor<NotificationStackScrollLayout.DismissListener> + ArgumentCaptor<NotificationStackScrollLayout.ClearAllListener> dismissListenerArgumentCaptor = ArgumentCaptor.forClass( - NotificationStackScrollLayout.DismissListener.class); + NotificationStackScrollLayout.ClearAllListener.class); mController.attach(mNotificationStackScrollLayout); - verify(mNotificationStackScrollLayout).setDismissListener( + verify(mNotificationStackScrollLayout).setClearAllListener( dismissListenerArgumentCaptor.capture()); - NotificationStackScrollLayout.DismissListener dismissListener = + NotificationStackScrollLayout.ClearAllListener dismissListener = dismissListenerArgumentCaptor.getValue(); - dismissListener.onDismiss(ROWS_ALL); + dismissListener.onClearAll(ROWS_ALL); verify(mUiEventLogger).log(NotificationPanelEvent.fromSelection(ROWS_ALL)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index eda0e83d7eb3..46ba09795143 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -447,7 +447,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { public void testClearNotifications_All() { final int[] numCalls = {0}; final int[] selected = {-1}; - mStackScroller.setDismissListener(selectedRows -> { + mStackScroller.setClearAllListener(selectedRows -> { numCalls[0]++; selected[0] = selectedRows; }); @@ -461,7 +461,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { public void testClearNotifications_Gentle() { final int[] numCalls = {0}; final int[] selected = {-1}; - mStackScroller.setDismissListener(selectedRows -> { + mStackScroller.setClearAllListener(selectedRows -> { numCalls[0]++; selected[0] = selectedRows; }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 5ada6d4239d1..35f671bf8298 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -455,7 +455,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mStatusBarStateController, mFalsingManager, mLockscreenShadeTransitionController, - new FalsingCollectorFake()); + new FalsingCollectorFake(), + mDumpManager); when(mKeyguardStatusViewComponentFactory.build(any())) .thenReturn(mKeyguardStatusViewComponent); when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 34407b0440c6..f804d83a66a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -39,6 +39,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; +import android.database.ContentObserver; import android.graphics.Color; import android.os.Handler; import android.os.UserHandle; @@ -55,6 +56,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.monet.Style; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; @@ -117,6 +119,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessLifecycleObserver; @Captor private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallback; + @Captor + private ArgumentCaptor<ContentObserver> mSettingsObserver; + private Style mCurrentStyle; @Before public void setup() { @@ -130,10 +135,11 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { @Nullable @Override - protected FabricatedOverlay getOverlay(int color, int type) { + protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); + mCurrentStyle = style; return overlay; } }; @@ -148,6 +154,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mWakefulnessLifecycle).addObserver(mWakefulnessLifecycleObserver.capture()); verify(mDumpManager).registerDumpable(any(), any()); verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); + verify(mSecureSettings).registerContentObserverForUser( + eq(Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)), + eq(false), mSettingsObserver.capture(), eq(UserHandle.USER_ALL) + ); } @Test @@ -211,12 +221,6 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mThemeOverlayApplier) .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); - // Assert that we received the colors that we were expecting - assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) - .isEqualTo(new OverlayIdentifier("ffff0000")); - assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) - .isEqualTo(new OverlayIdentifier("ffff0000")); - // Should not change theme after changing wallpapers, if intent doesn't have // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true. clearInvocations(mThemeOverlayApplier); @@ -322,6 +326,40 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { } @Test + public void onSettingChanged_honorThemeStyle() { + when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true); + for (Style style : Style.values()) { + reset(mSecureSettings); + + String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.theme_style\":\"" + style.name() + "\"}"; + + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + + mSettingsObserver.getValue().onChange(true, null, 0, mUserTracker.getUserId()); + + assertThat(mCurrentStyle).isEqualTo(style); + } + } + + @Test + public void onSettingChanged_invalidStyle() { + when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true); + String jsonString = "{\"android.theme.customization.system_palette\":\"A16B00\"," + + "\"android.theme.customization.theme_style\":\"some_invalid_name\"}"; + + when(mSecureSettings.getStringForUser( + eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt())) + .thenReturn(jsonString); + + mSettingsObserver.getValue().onChange(true, null, 0, mUserTracker.getUserId()); + + assertThat(mCurrentStyle).isEqualTo(Style.TONAL_SPOT); + } + + @Test public void onWallpaperColorsChanged_ResetThemeWithNewHomeAndLockWallpaper() { // Should ask for a new theme when wallpaper colors change WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), @@ -611,11 +649,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { @Nullable @Override - protected FabricatedOverlay getOverlay(int color, int type) { + protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier("com.thebest.livewallpaperapp.ever")); - return overlay; } @@ -648,7 +685,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mUserTracker, mDumpManager, mFeatureFlags, mWakefulnessLifecycle) { @Nullable @Override - protected FabricatedOverlay getOverlay(int color, int type) { + protected FabricatedOverlay getOverlay(int color, int type, Style style) { FabricatedOverlay overlay = mock(FabricatedOverlay.class); when(overlay.getIdentifier()) .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt new file mode 100644 index 000000000000..8076b4eda883 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.unfold + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.FoldStateLoggingProvider.FoldStateLoggingListener +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN +import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING +import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING +import com.android.systemui.unfold.updates.FoldStateProvider +import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate +import com.android.systemui.util.time.FakeSystemClock +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 FoldStateLoggingProviderTest : SysuiTestCase() { + + @Captor + private lateinit var foldUpdatesListener: ArgumentCaptor<FoldStateProvider.FoldUpdatesListener> + + @Mock private lateinit var foldStateProvider: FoldStateProvider + + private val fakeClock = FakeSystemClock() + + private lateinit var foldStateLoggingProvider: FoldStateLoggingProvider + + private val foldLoggingUpdates: MutableList<FoldStateChange> = arrayListOf() + + private val foldStateLoggingListener = + object : FoldStateLoggingListener { + override fun onFoldUpdate(foldStateUpdate: FoldStateChange) { + foldLoggingUpdates.add(foldStateUpdate) + } + } + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + foldStateLoggingProvider = + FoldStateLoggingProviderImpl(foldStateProvider, fakeClock).apply { + addCallback(foldStateLoggingListener) + init() + } + + verify(foldStateProvider).addCallback(foldUpdatesListener.capture()) + } + + @Test + fun onFoldUpdate_noPreviousOne_finishHalfOpen_nothingReported() { + sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) + + assertThat(foldLoggingUpdates).isEmpty() + } + + @Test + fun onFoldUpdate_noPreviousOne_finishFullOpen_nothingReported() { + sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + + assertThat(foldLoggingUpdates).isEmpty() + } + + @Test + fun onFoldUpdate_noPreviousOne_finishClosed_nothingReported() { + sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + + assertThat(foldLoggingUpdates).isEmpty() + } + + @Test + fun onFoldUpdate_startOpening_fullOpen_changeReported() { + val dtTime = 10L + + sendFoldUpdate(FOLD_UPDATE_START_OPENING) + fakeClock.advanceTime(dtTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + + assertThat(foldLoggingUpdates) + .containsExactly(FoldStateChange(FULLY_CLOSED, FULLY_OPENED, dtTime)) + } + + @Test + fun onFoldUpdate_startClosingThenFinishClosed_noInitialState_nothingReported() { + val dtTime = 10L + + sendFoldUpdate(FOLD_UPDATE_START_CLOSING) + fakeClock.advanceTime(dtTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + + assertThat(foldLoggingUpdates).isEmpty() + } + + @Test + fun onFoldUpdate_startClosingThenFinishClosed_initiallyOpened_changeReported() { + val dtTime = 10L + sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + + sendFoldUpdate(FOLD_UPDATE_START_CLOSING) + fakeClock.advanceTime(dtTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + + assertThat(foldLoggingUpdates) + .containsExactly(FoldStateChange(FULLY_OPENED, FULLY_CLOSED, dtTime)) + } + + @Test + fun onFoldUpdate_startOpeningThenHalf_initiallyClosed_changeReported() { + val dtTime = 10L + sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + + sendFoldUpdate(FOLD_UPDATE_START_OPENING) + fakeClock.advanceTime(dtTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) + + assertThat(foldLoggingUpdates) + .containsExactly(FoldStateChange(FULLY_CLOSED, HALF_OPENED, dtTime)) + } + + @Test + fun onFoldUpdate_startClosingThenHalf_initiallyOpened_changeReported() { + val dtTime = 10L + sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + + sendFoldUpdate(FOLD_UPDATE_START_CLOSING) + fakeClock.advanceTime(dtTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) + + assertThat(foldLoggingUpdates) + .containsExactly(FoldStateChange(FULLY_OPENED, HALF_OPENED, dtTime)) + } + + @Test + fun onFoldUpdate_foldThenUnfold_multipleReported() { + val foldTime = 24L + val unfoldTime = 42L + val waitingTime = 424L + sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + + // Fold + sendFoldUpdate(FOLD_UPDATE_START_CLOSING) + fakeClock.advanceTime(foldTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) + fakeClock.advanceTime(waitingTime) + // unfold + sendFoldUpdate(FOLD_UPDATE_START_OPENING) + fakeClock.advanceTime(unfoldTime) + sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) + + assertThat(foldLoggingUpdates) + .containsExactly( + FoldStateChange(FULLY_OPENED, FULLY_CLOSED, foldTime), + FoldStateChange(FULLY_CLOSED, FULLY_OPENED, unfoldTime)) + } + + @Test + fun uninit_removesCallback() { + foldStateLoggingProvider.uninit() + + verify(foldStateProvider).removeCallback(foldUpdatesListener.value) + } + + private fun sendFoldUpdate(@FoldUpdate foldUpdate: Int) { + foldUpdatesListener.value.onFoldUpdate(foldUpdate) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 46e0b1238013..f43dc6c5bc83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.unfold.util.FoldableDeviceStates import com.android.systemui.unfold.util.FoldableTestUtils import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat +import java.lang.Exception import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -39,32 +40,24 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -import java.lang.Exception @RunWith(AndroidTestingRunner::class) @SmallTest class DeviceFoldStateProviderTest : SysuiTestCase() { - @Mock - private lateinit var hingeAngleProvider: HingeAngleProvider + @Mock private lateinit var hingeAngleProvider: HingeAngleProvider - @Mock - private lateinit var screenStatusProvider: ScreenStatusProvider + @Mock private lateinit var screenStatusProvider: ScreenStatusProvider - @Mock - private lateinit var deviceStateManager: DeviceStateManager + @Mock private lateinit var deviceStateManager: DeviceStateManager - @Mock - private lateinit var handler: Handler + @Mock private lateinit var handler: Handler - @Captor - private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> + @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> - @Captor - private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener> + @Captor private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener> - @Captor - private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>> + @Captor private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>> private lateinit var foldStateProvider: DeviceFoldStateProvider @@ -81,24 +74,25 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) deviceStates = FoldableTestUtils.findDeviceStates(context) - foldStateProvider = DeviceFoldStateProvider( - context, - hingeAngleProvider, - screenStatusProvider, - deviceStateManager, - context.mainExecutor, - handler - ) - - foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener { - override fun onHingeAngleUpdate(angle: Float) { - hingeAngleUpdates.add(angle) - } - - override fun onFoldUpdate(update: Int) { - foldUpdates.add(update) - } - }) + foldStateProvider = + DeviceFoldStateProvider( + context, + hingeAngleProvider, + screenStatusProvider, + deviceStateManager, + context.mainExecutor, + handler) + + foldStateProvider.addCallback( + object : FoldStateProvider.FoldUpdatesListener { + override fun onHingeAngleUpdate(angle: Float) { + hingeAngleUpdates.add(angle) + } + + override fun onFoldUpdate(update: Int) { + foldUpdates.add(update) + } + }) foldStateProvider.start() verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) @@ -200,9 +194,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(90) sendHingeAngleEvent(80) - simulateTimeout(ABORT_CLOSING_MILLIS) + simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS) - assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED) + assertThat(foldUpdates) + .containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_FINISH_HALF_OPEN) } @Test @@ -210,7 +205,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(90) sendHingeAngleEvent(80) - simulateTimeout(ABORT_CLOSING_MILLIS - 1) + simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) } @@ -220,7 +215,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(180) sendHingeAngleEvent(90) - simulateTimeout(ABORT_CLOSING_MILLIS - 1) + simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) sendHingeAngleEvent(80) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) @@ -231,11 +226,13 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { sendHingeAngleEvent(180) sendHingeAngleEvent(90) - simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here. + // The timeout should not trigger here. + simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) sendHingeAngleEvent(80) - simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here. + simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS) // The timeout should trigger here. - assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED) + assertThat(foldUpdates) + .containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_FINISH_HALF_OPEN) } @Test @@ -261,7 +258,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { } } - private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) { + private fun simulateTimeout(waitTime: Long = HALF_OPENED_TIMEOUT_MILLIS) { val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.") if (waitTime >= runnableDelay) { scheduledRunnable?.run() diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index e3b07b3e8bd7..01769e52c8d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -21,7 +21,6 @@ import static android.view.View.VISIBLE; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,7 +95,7 @@ public class WalletScreenControllerTest extends SysuiTestCase { @Mock UiEventLogger mUiEventLogger; @Captor - ArgumentCaptor<Intent> mIntentCaptor; + ArgumentCaptor<PendingIntent> mIntentCaptor; @Captor ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor; private WalletScreenController mController; @@ -374,10 +373,12 @@ public class WalletScreenControllerTest extends SysuiTestCase { mController.onCardClicked(walletCardViewInfo); - verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true)); + verify(mActivityStarter).startPendingIntentDismissingKeyguard(mIntentCaptor.capture()); - assertEquals(mWalletIntent.getAction(), mIntentCaptor.getValue().getAction()); - assertEquals(mWalletIntent.getComponent(), mIntentCaptor.getValue().getComponent()); + Intent actualIntent = mIntentCaptor.getValue().getIntent(); + + assertEquals(mWalletIntent.getAction(), actualIntent.getAction()); + assertEquals(mWalletIntent.getComponent(), actualIntent.getComponent()); verify(mUiEventLogger, times(1)).log(WalletUiEvent.QAW_CLICK_CARD); } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index ad3e1d56c51b..e93ac47b1940 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1036,6 +1036,30 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + + @Override + public Region getCurrentMagnificationRegion(int displayId) { + if (svcConnTracingEnabled()) { + logTraceSvcConn("getCurrentMagnificationRegion", "displayId=" + displayId); + } + synchronized (mLock) { + final Region region = Region.obtain(); + if (!hasRightsToCurrentUserLocked()) { + return region; + } + MagnificationProcessor magnificationProcessor = + mSystemSupport.getMagnificationProcessor(); + final long identity = Binder.clearCallingIdentity(); + try { + magnificationProcessor.getCurrentMagnificationRegion(displayId, + region, mSecurityPolicy.canControlMagnification(this)); + return region; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + @Override public float getMagnificationCenterX(int displayId) { if (svcConnTracingEnabled()) { @@ -1103,6 +1127,31 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override + public boolean resetCurrentMagnification(int displayId, boolean animate) { + if (svcConnTracingEnabled()) { + logTraceSvcConn("resetCurrentMagnification", + "displayId=" + displayId + ";animate=" + animate); + } + synchronized (mLock) { + if (!hasRightsToCurrentUserLocked()) { + return false; + } + if (!mSecurityPolicy.canControlMagnification(this)) { + return false; + } + } + final long identity = Binder.clearCallingIdentity(); + try { + MagnificationProcessor magnificationProcessor = + mSystemSupport.getMagnificationProcessor(); + return (magnificationProcessor.resetCurrentMagnification(displayId, animate) + || !magnificationProcessor.isMagnifying(displayId)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, boolean animate) { if (svcConnTracingEnabled()) { @@ -1542,9 +1591,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + @NonNull MagnificationConfig config) { mInvocationHandler - .notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); + .notifyMagnificationChangedLocked(displayId, region, config); } public void notifySoftKeyboardShowModeChangedLocked(int showState) { @@ -1564,15 +1613,15 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * state of magnification has changed. */ private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + @NonNull MagnificationConfig config) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", " - + scale + ", " + centerX + ", " + centerY); + + config.toString()); } - listener.onMagnificationChanged(displayId, region, scale, centerX, centerY); + listener.onMagnificationChanged(displayId, region, config); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re); } @@ -1899,11 +1948,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ case MSG_ON_MAGNIFICATION_CHANGED: { final SomeArgs args = (SomeArgs) message.obj; final Region region = (Region) args.arg1; - final float scale = (float) args.arg2; - final float centerX = (float) args.arg3; - final float centerY = (float) args.arg4; + final MagnificationConfig config = (MagnificationConfig) args.arg2; final int displayId = args.argi1; - notifyMagnificationChangedInternal(displayId, region, scale, centerX, centerY); + notifyMagnificationChangedInternal(displayId, region, config); args.recycle(); } break; @@ -1932,7 +1979,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + @NonNull MagnificationConfig config) { synchronized (mLock) { if (mMagnificationCallbackState.get(displayId) == null) { return; @@ -1941,9 +1988,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final SomeArgs args = SomeArgs.obtain(); args.arg1 = region; - args.arg2 = scale; - args.arg3 = centerX; - args.arg4 = centerY; + args.arg2 = config; args.argi1 = displayId; final Message msg = obtainMessage(MSG_ON_MAGNIFICATION_CHANGED, args); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index e59a3d633cfd..aa69a09034fc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -44,6 +44,7 @@ import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityShortcutInfo; import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.MagnificationConfig; import android.accessibilityservice.TouchInteractionController; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1301,18 +1302,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * Called by the MagnificationController when the state of display * magnification changes. * - * @param displayId The logical display id. + * <p> + * It can notify window magnification change if the service supports controlling all the + * magnification mode. + * </p> + * + * @param displayId The logical display id * @param region the new magnified region, may be empty if * magnification is not enabled (e.g. scale is 1) - * @param scale the new scale - * @param centerX the new screen-relative center X coordinate - * @param centerY the new screen-relative center Y coordinate + * @param config The magnification config. That has magnification mode, the new scale and the + * new screen-relative center position */ public void notifyMagnificationChanged(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + @NonNull MagnificationConfig config) { synchronized (mLock) { notifyClearAccessibilityCacheLocked(); - notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); + notifyMagnificationChangedLocked(displayId, region, config); } } @@ -1613,11 +1618,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region, - float scale, float centerX, float centerY) { + @NonNull MagnificationConfig config) { final AccessibilityUserState state = getCurrentUserStateLocked(); for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { final AccessibilityServiceConnection service = state.mBoundServices.get(i); - service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY); + service.notifyMagnificationChangedLocked(displayId, region, config); } } @@ -2393,6 +2398,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState); somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState); somethingChanged |= readMagnificationCapabilitiesLocked(userState); + somethingChanged |= readMagnificationFollowTypingLocked(userState); return somethingChanged; } @@ -3868,6 +3874,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mMagnificationCapabilityUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY); + private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED); + public AccessibilityContentObserver(Handler handler) { super(handler); } @@ -3906,6 +3915,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mMagnificationModeUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mMagnificationCapabilityUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL); } @Override @@ -3973,6 +3984,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readMagnificationCapabilitiesLocked(userState)) { updateMagnificationCapabilitiesSettingsChangeLocked(userState); } + } else if (mMagnificationFollowTypingUri.equals(uri)) { + readMagnificationFollowTypingLocked(userState); } } } @@ -4069,6 +4082,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } + boolean readMagnificationFollowTypingLocked(AccessibilityUserState userState) { + final boolean followTypeEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, + 1, userState.mUserId) == 1; + if (followTypeEnabled != userState.isMagnificationFollowTypingEnabled()) { + userState.setMagnificationFollowTypingEnabled(followTypeEnabled); + mMagnificationController.setMagnificationFollowTypingEnabled(followTypeEnabled); + return true; + } + return false; + } + @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { mMainHandler.sendMessage( diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 9324e3e0db47..0f354561faaf 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -129,6 +129,8 @@ class AccessibilityUserState { private final SparseIntArray mMagnificationModes = new SparseIntArray(); // The magnification capabilities used to know magnification mode could be switched. private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + // Whether the following typing focus feature for magnification is enabled. + private boolean mMagnificationFollowTypingEnabled = true; /** The stroke width of the focus rectangle in pixels */ private int mFocusStrokeWidth; @@ -210,6 +212,7 @@ class AccessibilityUserState { mMagnificationModes.clear(); mFocusStrokeWidth = mFocusStrokeWidthDefaultValue; mFocusColor = mFocusColorDefaultValue; + mMagnificationFollowTypingEnabled = true; } void addServiceLocked(AccessibilityServiceConnection serviceConnection) { @@ -685,6 +688,14 @@ class AccessibilityUserState { mMagnificationCapabilities = capabilities; } + public void setMagnificationFollowTypingEnabled(boolean enabled) { + mMagnificationFollowTypingEnabled = enabled; + } + + public boolean isMagnificationFollowTypingEnabled() { + return mMagnificationFollowTypingEnabled; + } + /** * Sets the magnification mode to the given display. * diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index 72bc8503b7a5..e39b979643ac 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -17,10 +17,12 @@ package com.android.server.accessibility.magnification; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; +import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID; +import android.accessibilityservice.MagnificationConfig; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.NonNull; @@ -89,6 +91,8 @@ public class FullScreenMagnificationController implements private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0); private final Rect mTempRect = new Rect(); + // Whether the following typing focus feature for magnification is enabled. + private boolean mMagnificationFollowTypingEnabled = true; /** * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds @@ -363,9 +367,16 @@ public class FullScreenMagnificationController implements return mIdOfLastServiceToMagnify; } + @GuardedBy("mLock") void onMagnificationChangedLocked() { - mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, mMagnificationRegion, - getScale(), getCenterX(), getCenterY()); + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(MAGNIFICATION_MODE_FULLSCREEN) + .setScale(getScale()) + .setCenterX(getCenterX()) + .setCenterY(getCenterY()).build(); + mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, + mMagnificationRegion, + config); if (mUnregisterPending && !isMagnifying()) { unregister(mDeleteAfterUnregister); } @@ -735,6 +746,9 @@ public class FullScreenMagnificationController implements public void onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom) { synchronized (mLock) { + if (!mMagnificationFollowTypingEnabled) { + return; + } final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return; @@ -751,6 +765,10 @@ public class FullScreenMagnificationController implements } } + void setMagnificationFollowTypingEnabled(boolean enabled) { + mMagnificationFollowTypingEnabled = enabled; + } + /** * Remove the display magnification with given id. * diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 037dc1f6795c..b70ffb2243e1 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -16,6 +16,7 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; @@ -379,6 +380,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb mAms.changeMagnificationMode(displayId, magnificationMode); } + @Override + public void onSourceBoundsChanged(int displayId, Rect bounds) { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(MAGNIFICATION_MODE_WINDOW) + .setScale(mScaleProvider.getScale(displayId)) + .setCenterX(bounds.exactCenterX()) + .setCenterY(bounds.exactCenterY()).build(); + mAms.notifyMagnificationChanged(displayId, new Region(bounds), config); + } + private void disableFullScreenMagnificationIfNeeded(int displayId) { final FullScreenMagnificationController fullScreenMagnificationController = getFullScreenMagnificationController(); @@ -426,6 +437,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { mImeWindowVisible = shown; } + getWindowMagnificationMgr().onImeWindowVisibilityChanged(shown); logMagnificationModeWithImeOnIfNeeded(); } @@ -518,6 +530,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb mMagnificationCapabilities = capabilities; } + /** + * Called when the following typing focus feature is switched. + * + * @param enabled Enable the following typing focus feature + */ + public void setMagnificationFollowTypingEnabled(boolean enabled) { + getWindowMagnificationMgr().setMagnificationFollowTypingEnabled(enabled); + getFullScreenMagnificationController().setMagnificationFollowTypingEnabled(enabled); + } + private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked( int displayId) { return mMagnificationEndRunnableSparseArray.get(displayId); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java index 40f77b04d5de..8f15d5cf3e3d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -203,6 +203,36 @@ public class MagnificationProcessor { } /** + * Returns the region of the screen currently active for magnification if the + * controlling magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}. + * Returns the region of screen projected on the magnification window if the controlling + * magnification is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}. + * <p> + * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}, + * the returned region will be empty if the magnification is + * not active. And the magnification is active if magnification gestures are enabled + * or if a service is running that can control magnification. + * </p><p> + * If the controlling mode is {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}, + * the returned region will be empty if the magnification is not activated. + * </p> + * + * @param displayId The logical display id + * @param outRegion the region to populate + * @param canControlMagnification Whether the service can control magnification + */ + public void getCurrentMagnificationRegion(int displayId, @NonNull Region outRegion, + boolean canControlMagnification) { + int currentMode = getControllingMode(displayId); + if (currentMode == MAGNIFICATION_MODE_FULLSCREEN) { + getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification); + } else if (currentMode == MAGNIFICATION_MODE_WINDOW) { + mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId, + outRegion); + } + } + + /** * Returns the magnification bounds of full-screen magnification on the given display. * * @param displayId The logical display id @@ -224,8 +254,8 @@ public class MagnificationProcessor { } /** - * Resets the current magnification on the given display. The reset mode could be - * full-screen or window if it is activated. + * Resets the controlling magnifier on the given display. + * For resetting window magnifier, it disables the magnifier by setting the scale to 1. * * @param displayId The logical display id. * @param animate {@code true} to animate the transition, {@code false} diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index c4a577d6e461..95c7d446d75e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -97,6 +97,8 @@ public class WindowMagnificationManager implements private ConnectionCallback mConnectionCallback; @GuardedBy("mLock") private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>(); + // Whether the following typing focus feature for magnification is enabled. + private boolean mMagnificationFollowTypingEnabled = true; private boolean mReceiverRegistered = false; @VisibleForTesting @@ -139,6 +141,14 @@ public class WindowMagnificationManager implements void onWindowMagnificationActivationState(int displayId, boolean activated); /** + * Called when the magnification source bounds are changed. + * + * @param displayId The logical display id. + * @param bounds The magnified source bounds on the display. + */ + void onSourceBoundsChanged(int displayId, Rect bounds); + + /** * Called from {@link IWindowMagnificationConnection} to request changing the magnification * mode on the given display. * @@ -291,13 +301,73 @@ public class WindowMagnificationManager implements @Override public void onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom) { - // TODO(b/194668976): We will implement following typing focus in window mode after - // our refactor. + if (!mMagnificationFollowTypingEnabled) { + return; + } + + float toCenterX = (float) (left + right) / 2; + float toCenterY = (float) (top + bottom) / 2; + + if (!isPositionInSourceBounds(displayId, toCenterX, toCenterY) + && isTrackingTypingFocusEnabled(displayId)) { + enableWindowMagnification(displayId, Float.NaN, toCenterX, toCenterY); + } + } + + void setMagnificationFollowTypingEnabled(boolean enabled) { + mMagnificationFollowTypingEnabled = enabled; + } + + /** + * Enable or disable tracking typing focus for the specific magnification window. + * + * The tracking typing focus should be set to enabled with the following conditions: + * 1. IME is shown. + * + * The tracking typing focus should be set to disabled with the following conditions: + * 1. A user drags the magnification window by 1 finger. + * 2. A user scroll the magnification window by 2 fingers. + * + * @param displayId The logical display id. + * @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus. + */ + private void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return; + } + magnifier.setTrackingTypingFocusEnabled(trackingTypingFocusEnabled); + } + } + + /** + * Enable tracking typing focus function for all magnifications. + */ + private void enableAllTrackingTypingFocus() { + synchronized (mLock) { + for (int i = 0; i < mWindowMagnifiers.size(); i++) { + WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); + magnifier.setTrackingTypingFocusEnabled(true); + } + } + } + + /** + * Called when the IME window visibility changed. + * + * @param shown {@code true} means the IME window shows on the screen. Otherwise, it's hidden. + */ + void onImeWindowVisibilityChanged(boolean shown) { + if (shown) { + enableAllTrackingTypingFocus(); + } } @Override public boolean processScroll(int displayId, float distanceX, float distanceY) { moveWindowMagnification(displayId, -distanceX, -distanceY); + setTrackingTypingFocusEnabled(displayId, false); return /* event consumed: */ true; } @@ -469,6 +539,16 @@ public class WindowMagnificationManager implements } } + boolean isPositionInSourceBounds(int displayId, float x, float y) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return false; + } + return magnifier.isPositionInSourceBounds(x, y); + } + } + /** * Indicates whether window magnification is enabled on specified display. * @@ -598,6 +678,16 @@ public class WindowMagnificationManager implements } } + boolean isTrackingTypingFocusEnabled(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return false; + } + return magnifier.isTrackingTypingFocusEnabled(); + } + } + /** * Populates magnified bounds on the screen. And the populated magnified bounds would be * empty If window magnifier is not activated. @@ -689,6 +779,7 @@ public class WindowMagnificationManager implements } magnifier.onSourceBoundsChanged(sourceBounds); } + mCallback.onSourceBoundsChanged(displayId, sourceBounds); } @Override @@ -714,6 +805,17 @@ public class WindowMagnificationManager implements } @Override + public void onDrag(int displayId) { + if (mTrace.isA11yTracingEnabledForTypes( + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + mTrace.logTrace(TAG + "ConnectionCallback.onDrag", + FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + "displayId=" + displayId); + } + setTrackingTypingFocusEnabled(displayId, false); + } + + @Override public void binderDied() { synchronized (mLock) { Slog.w(TAG, "binderDied DeathRecipient :" + mExpiredDeathRecipient); @@ -749,7 +851,9 @@ public class WindowMagnificationManager implements private int mIdOfLastServiceToControl = INVALID_SERVICE_ID; - private PointF mMagnificationFrameOffsetRatio = new PointF(0f, 0f); + private final PointF mMagnificationFrameOffsetRatio = new PointF(0f, 0f); + + private boolean mTrackingTypingFocusEnabled = true; WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { mDisplayId = displayId; @@ -790,7 +894,6 @@ public class WindowMagnificationManager implements } } - @GuardedBy("mLock") boolean disableWindowMagnificationInternal( @Nullable MagnificationAnimationCallback animationResultCallback) { if (!mEnabled) { @@ -800,6 +903,7 @@ public class WindowMagnificationManager implements mDisplayId, animationResultCallback)) { mEnabled = false; mIdOfLastServiceToControl = INVALID_SERVICE_ID; + mTrackingTypingFocusEnabled = false; return true; } return false; @@ -848,7 +952,18 @@ public class WindowMagnificationManager implements return count; } - @GuardedBy("mLock") + boolean isPositionInSourceBounds(float x, float y) { + return mSourceBounds.contains((int) x, (int) y); + } + + void setTrackingTypingFocusEnabled(boolean trackingTypingFocusEnabled) { + mTrackingTypingFocusEnabled = trackingTypingFocusEnabled; + } + + boolean isTrackingTypingFocusEnabled() { + return mTrackingTypingFocusEnabled; + } + boolean isEnabled() { return mEnabled; } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 5aa1c933ad87..c18f5fa2c782 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -19,7 +19,7 @@ package com.android.server.companion; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; -import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -1169,7 +1169,7 @@ public class CompanionDeviceManagerService extends SystemService } else { scanner.startScan( filters, - new ScanSettings.Builder().setScanMode(SCAN_MODE_BALANCED).build(), + new ScanSettings.Builder().setScanMode(SCAN_MODE_LOW_POWER).build(), mBleScanCallback); } } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 262933dea27f..bc8da8443a7d 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -45,7 +45,6 @@ import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothManagerCallback; import android.bluetooth.IBluetoothProfileServiceConnection; import android.bluetooth.IBluetoothStateChangeCallback; -import android.bluetooth.IBluetoothLeCallControl; import android.content.ActivityNotFoundException; import android.content.AttributionSource; import android.content.BroadcastReceiver; @@ -1324,15 +1323,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { + bluetoothProfile); } - Intent intent; - if (bluetoothProfile == BluetoothProfile.HEADSET) { - intent = new Intent(IBluetoothHeadset.class.getName()); - } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) { - intent = new Intent(IBluetoothLeCallControl.class.getName()); - } else { + if (bluetoothProfile != BluetoothProfile.HEADSET) { return false; } + Intent intent = new Intent(IBluetoothHeadset.class.getName()); psc = new ProfileServiceConnections(intent); if (!psc.bindService()) { return false; diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 780afd86b373..ec018de8f9a4 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3846,7 +3846,7 @@ class StorageManagerService extends IStorageManager.Stub final boolean primary = false; final boolean removable = false; final boolean emulated = true; - final boolean stub = false; + final boolean externallyManaged = false; final boolean allowMassStorage = false; final long maxFileSize = 0; final UserHandle user = new UserHandle(userId); @@ -3854,7 +3854,8 @@ class StorageManagerService extends IStorageManager.Stub final String description = mContext.getString(android.R.string.unknownName); res.add(new StorageVolume(id, path, path, description, primary, removable, emulated, - stub, allowMassStorage, maxFileSize, user, null /*uuid */, id, envState)); + externallyManaged, allowMassStorage, maxFileSize, user, null /*uuid */, id, + envState)); } if (!foundPrimary) { @@ -3869,7 +3870,7 @@ class StorageManagerService extends IStorageManager.Stub final boolean primary = true; final boolean removable = primaryPhysical; final boolean emulated = !primaryPhysical; - final boolean stub = false; + final boolean externallyManaged = false; final boolean allowMassStorage = false; final long maxFileSize = 0L; final UserHandle owner = new UserHandle(userId); @@ -3878,7 +3879,7 @@ class StorageManagerService extends IStorageManager.Stub final String state = Environment.MEDIA_REMOVED; res.add(0, new StorageVolume(id, path, path, - description, primary, removable, emulated, stub, + description, primary, removable, emulated, externallyManaged, allowMassStorage, maxFileSize, owner, uuid, fsUuid, state)); } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 0c990ecfc827..6a7afd90dc96 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -24,7 +24,6 @@ import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; -import static android.telephony.SubscriptionManager.isValidSubscriptionId; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -164,10 +163,6 @@ public class VcnManagementService extends IVcnManagementService.Stub { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String VCN_CONFIG_FILE = "/data/system/vcn/configs.xml"; - // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); - /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; @@ -372,12 +367,15 @@ public class VcnManagementService extends IVcnManagementService.Stub { /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { - mNetworkProvider.register(); - mContext.getSystemService(ConnectivityManager.class) - .registerNetworkCallback( - new NetworkRequest.Builder().clearCapabilities().build(), - mTrackingNetworkCallback); - mTelephonySubscriptionTracker.register(); + // Always run on the handler thread to ensure consistency. + mHandler.post(() -> { + mNetworkProvider.register(); + mContext.getSystemService(ConnectivityManager.class) + .registerNetworkCallback( + new NetworkRequest.Builder().clearCapabilities().build(), + mTrackingNetworkCallback); + mTelephonySubscriptionTracker.register(); + }); } private void enforcePrimaryUser() { @@ -471,22 +469,15 @@ public class VcnManagementService extends IVcnManagementService.Stub { if (!mVcns.containsKey(subGrp)) { startVcnLocked(subGrp, entry.getValue()); } - - // Cancel any scheduled teardowns for active subscriptions - mHandler.removeCallbacksAndMessages(mVcns.get(subGrp)); } } - // Schedule teardown of any VCN instances that have lost carrier privileges (after a - // delay) + // Schedule teardown of any VCN instances that have lost carrier privileges for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final ParcelUuid subGrp = entry.getKey(); final VcnConfig config = mConfigs.get(subGrp); final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot); - final boolean isValidActiveDataSubIdNotInVcnSubGrp = - isValidSubscriptionId(snapshot.getActiveDataSubscriptionId()) - && !isActiveSubGroup(subGrp, snapshot); // TODO(b/193687515): Support multiple VCNs active at the same time if (config == null @@ -496,31 +487,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { final ParcelUuid uuidToTeardown = subGrp; final Vcn instanceToTeardown = entry.getValue(); - // TODO(b/193687515): Support multiple VCNs active at the same time - // If directly switching to a subscription not in the current group, - // teardown immediately to prevent other subscription's network from being - // outscored by the VCN. Otherwise, teardown after a delay to ensure that - // SIM profile switches do not trigger the VCN to cycle. - final long teardownDelayMs = - isValidActiveDataSubIdNotInVcnSubGrp - ? 0 - : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS; - mHandler.postDelayed(() -> { - synchronized (mLock) { - // Guard against case where this is run after a old instance was - // torn down, and a new instance was started. Verify to ensure - // correct instance is torn down. This could happen as a result of a - // Carrier App manually removing/adding a VcnConfig. - if (mVcns.get(uuidToTeardown) == instanceToTeardown) { - stopVcnLocked(uuidToTeardown); - - // TODO(b/181789060): invoke asynchronously after Vcn notifies - // through VcnCallback - notifyAllPermissionedStatusCallbacksLocked( - uuidToTeardown, VCN_STATUS_CODE_INACTIVE); - } - } - }, instanceToTeardown, teardownDelayMs); + stopVcnLocked(uuidToTeardown); + + // TODO(b/181789060): invoke asynchronously after Vcn notifies + // through VcnCallback + notifyAllPermissionedStatusCallbacksLocked( + uuidToTeardown, VCN_STATUS_CODE_INACTIVE); } else { // If this VCN's status has not changed, update it with the new snapshot entry.getValue().updateSubscriptionSnapshot(mLastSnapshot); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9d2b4e7a570f..da8c407a501d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -189,7 +189,6 @@ import android.app.ProfilerInfo; import android.app.PropertyInvalidatedCache; import android.app.SyncNotedAppOp; import android.app.WaitResult; -import android.app.WtfException; import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManager; import android.app.compat.CompatChanges; @@ -12635,7 +12634,6 @@ public class ActivityManagerService extends IActivityManager.Stub int callingUid; int callingPid; boolean instantApp; - boolean throwWtfException = false; synchronized(this) { if (caller != null) { callerApp = getRecordForAppLOSP(caller); @@ -12730,9 +12728,13 @@ public class ActivityManagerService extends IActivityManager.Stub + "RECEIVER_NOT_EXPORTED be specified when registering a " + "receiver"); } else { - // will be removed when enforcement is required + Slog.wtf(TAG, + callerPackage + ": Targeting T+ (version " + + Build.VERSION_CODES.TIRAMISU + + " and above) requires that one of RECEIVER_EXPORTED or " + + "RECEIVER_NOT_EXPORTED be specified when registering a " + + "receiver"); // Assume default behavior-- flag check is not enforced - throwWtfException = true; flags |= Context.RECEIVER_EXPORTED; } } else if (!requireExplicitFlagForDynamicReceivers) { @@ -12863,15 +12865,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - if (throwWtfException) { - throw new WtfException( - callerPackage + ": Targeting T+ (version " - + Build.VERSION_CODES.TIRAMISU - + " and above) requires that one of RECEIVER_EXPORTED or " - + "RECEIVER_NOT_EXPORTED be specified when registering a " - + "receiver"); - } - return sticky; } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 5fc11e8fff7c..8cb20404b3e1 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -56,6 +56,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.WakeLockStats; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.GpsBatteryStats; @@ -2596,6 +2597,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub } /** + * Gets a snapshot of wake lock stats + * @hide + */ + public WakeLockStats getWakeLockStats() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BATTERY_STATS, null); + + // Wait for the completion of pending works if there is any + awaitCompletion(); + synchronized (mStats) { + return mStats.getWakeLockStats(); + } + } + + /** * Gets a snapshot of the system health for a particular uid. */ @Override diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 2ec4a02a1ff2..eb8a4e9508da 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -349,6 +349,11 @@ public final class BroadcastQueue { prr.removeCurReceiver(r); } } + + // if something bad happens here, launch the app and try again + if (app.isKilled()) { + throw new RemoteException("app gets killed during broadcasting"); + } } public boolean sendPendingBroadcastsLocked(ProcessRecord app) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index a27e4b77959c..023a11e9ad0f 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -238,6 +238,9 @@ public class AudioDeviceInventory { //------------------------------------------------------------ /*package*/ void dump(PrintWriter pw, String prefix) { + pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET="); + BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> { + pw.print(" 0x" + Integer.toHexString(device)); }); pw.println("\n" + prefix + "Preferred devices for strategy:"); mPreferredDevices.forEach((strategy, device) -> { pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); @@ -1204,10 +1207,13 @@ public class AudioDeviceInventory { state == AudioService.CONNECTION_STATE_CONNECTED ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED); if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { + Log.i(TAG, "not sending NOISY: state=" + state); mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return return 0; } if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) { + Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device) + + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET); mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return return 0; } @@ -1217,18 +1223,24 @@ public class AudioDeviceInventory { if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0) && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) { devices.add(di.mDeviceType); + Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType)); } } if (musicDevice == AudioSystem.DEVICE_NONE) { musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); + Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x" + + Integer.toHexString(musicDevice)); } // always ignore condition on device being actually used for music when in communication // because music routing is altered in this case. // also checks whether media routing if affected by a dynamic policy or mirroring - if (((device == musicDevice) || mDeviceBroker.isInCommunication()) - && AudioSystem.isSingleAudioDeviceType(devices, device) - && !mDeviceBroker.hasMediaDynamicPolicy() + final boolean inCommunication = mDeviceBroker.isInCommunication(); + final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device); + final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy(); + if (((device == musicDevice) || inCommunication) + && singleAudioDeviceType + && !hasMediaDynamicPolicy && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) { if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/) && !mDeviceBroker.hasAudioFocusUsers()) { @@ -1241,6 +1253,12 @@ public class AudioDeviceInventory { } mDeviceBroker.postBroadcastBecomingNoisy(); delay = AudioService.BECOMING_NOISY_DELAY_MS; + } else { + Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device) + + " musicDevice:0x" + Integer.toHexString(musicDevice) + + " inComm:" + inCommunication + + " mediaPolicy:" + hasMediaDynamicPolicy + + " singleDevice:" + singleAudioDeviceType); } mmi.set(MediaMetrics.Property.DELAY_MS, delay).record(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index b73e91173a43..26bbb403f39f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static com.android.internal.annotations.VisibleForTesting.Visibility; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -48,7 +50,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor * Interface that ClientMonitor holders should use to receive callbacks. */ public interface Callback { - /** * Invoked when the ClientMonitor operation has been started (e.g. reached the head of * the queue and becomes the current operation). @@ -203,7 +204,8 @@ public abstract class BaseClientMonitor extends LoggableMonitor } /** Signals this operation has completed its lifecycle and should no longer be used. */ - void destroy() { + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void destroy() { mAlreadyDone = true; if (mToken != null) { try { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index a358bc2bad55..1f91c4d6803e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -17,15 +17,14 @@ package com.android.server.biometrics.sensors; import android.annotation.IntDef; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricService; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Slog; @@ -55,6 +54,7 @@ import java.util.Locale; * We currently assume (and require) that each biometric sensor have its own instance of a * {@link BiometricScheduler}. See {@link CoexCoordinator}. */ +@MainThread public class BiometricScheduler { private static final String BASE_TAG = "BiometricScheduler"; @@ -110,123 +110,6 @@ public class BiometricScheduler { } } - /** - * Contains all the necessary information for a HAL operation. - */ - @VisibleForTesting - static final class Operation { - - /** - * The operation is added to the list of pending operations and waiting for its turn. - */ - static final int STATE_WAITING_IN_QUEUE = 0; - - /** - * The operation is added to the list of pending operations, but a subsequent operation - * has been added. This state only applies to {@link Interruptable} operations. When this - * operation reaches the head of the queue, it will send ERROR_CANCELED and finish. - */ - static final int STATE_WAITING_IN_QUEUE_CANCELING = 1; - - /** - * The operation has reached the front of the queue and has started. - */ - static final int STATE_STARTED = 2; - - /** - * The operation was started, but is now canceling. Operations should wait for the HAL to - * acknowledge that the operation was canceled, at which point it finishes. - */ - static final int STATE_STARTED_CANCELING = 3; - - /** - * The operation has reached the head of the queue but is waiting for BiometricService - * to acknowledge and start the operation. - */ - static final int STATE_WAITING_FOR_COOKIE = 4; - - /** - * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished. - */ - static final int STATE_FINISHED = 5; - - @IntDef({STATE_WAITING_IN_QUEUE, - STATE_WAITING_IN_QUEUE_CANCELING, - STATE_STARTED, - STATE_STARTED_CANCELING, - STATE_WAITING_FOR_COOKIE, - STATE_FINISHED}) - @Retention(RetentionPolicy.SOURCE) - @interface OperationState {} - - @NonNull final BaseClientMonitor mClientMonitor; - @Nullable final BaseClientMonitor.Callback mClientCallback; - @OperationState int mState; - - Operation( - @NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback callback - ) { - this(clientMonitor, callback, STATE_WAITING_IN_QUEUE); - } - - protected Operation( - @NonNull BaseClientMonitor clientMonitor, - @Nullable BaseClientMonitor.Callback callback, - @OperationState int state - ) { - mClientMonitor = clientMonitor; - mClientCallback = callback; - mState = state; - } - - public boolean isHalOperation() { - return mClientMonitor instanceof HalClientMonitor<?>; - } - - /** - * @return true if the operation requires the HAL, and the HAL is null. - */ - public boolean isUnstartableHalOperation() { - if (isHalOperation()) { - final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor; - if (client.getFreshDaemon() == null) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return mClientMonitor + ", State: " + mState; - } - } - - /** - * Monitors an operation's cancellation. If cancellation takes too long, the watchdog will - * kill the current operation and forcibly start the next. - */ - private static final class CancellationWatchdog implements Runnable { - static final int DELAY_MS = 3000; - - final String tag; - final Operation operation; - CancellationWatchdog(String tag, Operation operation) { - this.tag = tag; - this.operation = operation; - } - - @Override - public void run() { - if (operation.mState != Operation.STATE_FINISHED) { - Slog.e(tag, "[Watchdog Triggered]: " + operation); - operation.mClientMonitor.mCallback - .onClientFinished(operation.mClientMonitor, false /* success */); - } - } - } - private static final class CrashState { static final int NUM_ENTRIES = 10; final String timestamp; @@ -263,10 +146,9 @@ public class BiometricScheduler { private final @SensorType int mSensorType; @Nullable private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @NonNull private final IBiometricService mBiometricService; - @NonNull protected final Handler mHandler = new Handler(Looper.getMainLooper()); - @NonNull private final InternalCallback mInternalCallback; - @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations; - @VisibleForTesting @Nullable Operation mCurrentOperation; + @NonNull protected final Handler mHandler; + @VisibleForTesting @NonNull final Deque<BiometricSchedulerOperation> mPendingOperations; + @VisibleForTesting @Nullable BiometricSchedulerOperation mCurrentOperation; @NonNull private final ArrayDeque<CrashState> mCrashStates; private int mTotalOperationsHandled; @@ -277,7 +159,7 @@ public class BiometricScheduler { // Internal callback, notified when an operation is complete. Notifies the requester // that the operation is complete, before performing internal scheduler work (such as // starting the next client). - public class InternalCallback implements BaseClientMonitor.Callback { + private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { Slog.d(getTag(), "[Started] " + clientMonitor); @@ -286,16 +168,11 @@ public class BiometricScheduler { mCoexCoordinator.addAuthenticationClient(mSensorType, (AuthenticationClient<?>) clientMonitor); } - - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientStarted(clientMonitor); - } } @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { mHandler.post(() -> { - clientMonitor.destroy(); if (mCurrentOperation == null) { Slog.e(getTag(), "[Finishing] " + clientMonitor + " but current operation is null, success: " + success @@ -303,9 +180,9 @@ public class BiometricScheduler { return; } - if (clientMonitor != mCurrentOperation.mClientMonitor) { + if (!mCurrentOperation.isFor(clientMonitor)) { Slog.e(getTag(), "[Ignoring Finish] " + clientMonitor + " does not match" - + " current: " + mCurrentOperation.mClientMonitor); + + " current: " + mCurrentOperation); return; } @@ -315,36 +192,33 @@ public class BiometricScheduler { (AuthenticationClient<?>) clientMonitor); } - mCurrentOperation.mState = Operation.STATE_FINISHED; - - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientFinished(clientMonitor, success); - } - if (mGestureAvailabilityDispatcher != null) { mGestureAvailabilityDispatcher.markSensorActive( - mCurrentOperation.mClientMonitor.getSensorId(), false /* active */); + mCurrentOperation.getSensorId(), false /* active */); } if (mRecentOperations.size() >= mRecentOperationsLimit) { mRecentOperations.remove(0); } - mRecentOperations.add(mCurrentOperation.mClientMonitor.getProtoEnum()); + mRecentOperations.add(mCurrentOperation.getProtoEnum()); mCurrentOperation = null; mTotalOperationsHandled++; startNextOperationIfIdle(); }); } - } + }; @VisibleForTesting - BiometricScheduler(@NonNull String tag, @SensorType int sensorType, + BiometricScheduler(@NonNull String tag, + @NonNull Handler handler, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, - @NonNull IBiometricService biometricService, int recentOperationsLimit, + @NonNull IBiometricService biometricService, + int recentOperationsLimit, @NonNull CoexCoordinator coexCoordinator) { mBiometricTag = tag; + mHandler = handler; mSensorType = sensorType; - mInternalCallback = new InternalCallback(); mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher; mPendingOperations = new ArrayDeque<>(); mBiometricService = biometricService; @@ -356,24 +230,26 @@ public class BiometricScheduler { /** * Creates a new scheduler. + * * @param tag for the specific instance of the scheduler. Should be unique. + * @param handler handler for callbacks (all methods of this class must be called on the + * thread associated with this handler) * @param sensorType the sensorType that this scheduler is handling. * @param gestureAvailabilityDispatcher may be null if the sensor does not support gestures * (such as fingerprint swipe). */ public BiometricScheduler(@NonNull String tag, + @NonNull Handler handler, @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE)), LOG_NUM_RECENT_OPERATIONS, - CoexCoordinator.getInstance()); + this(tag, handler, sensorType, gestureAvailabilityDispatcher, + IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE)), + LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); } - /** - * @return A reference to the internal callback that should be invoked whenever the scheduler - * needs to (e.g. client started, client finished). - */ - @NonNull protected InternalCallback getInternalCallback() { + @VisibleForTesting + public BaseClientMonitor.Callback getInternalCallback() { return mInternalCallback; } @@ -392,72 +268,46 @@ public class BiometricScheduler { } mCurrentOperation = mPendingOperations.poll(); - final BaseClientMonitor currentClient = mCurrentOperation.mClientMonitor; Slog.d(getTag(), "[Polled] " + mCurrentOperation); // If the operation at the front of the queue has been marked for cancellation, send // ERROR_CANCELED. No need to start this client. - if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) { + if (mCurrentOperation.isMarkedCanceling()) { Slog.d(getTag(), "[Now Cancelling] " + mCurrentOperation); - if (!(currentClient instanceof Interruptable)) { - throw new IllegalStateException("Mis-implemented client or scheduler, " - + "trying to cancel non-interruptable operation: " + mCurrentOperation); - } - - final Interruptable interruptable = (Interruptable) currentClient; - interruptable.cancelWithoutStarting(getInternalCallback()); + mCurrentOperation.cancel(mHandler, mInternalCallback); // Now we wait for the client to send its FinishCallback, which kicks off the next // operation. return; } - if (mGestureAvailabilityDispatcher != null - && mCurrentOperation.mClientMonitor instanceof AcquisitionClient) { + if (mGestureAvailabilityDispatcher != null && mCurrentOperation.isAcquisitionOperation()) { mGestureAvailabilityDispatcher.markSensorActive( - mCurrentOperation.mClientMonitor.getSensorId(), - true /* active */); + mCurrentOperation.getSensorId(), true /* active */); } // Not all operations start immediately. BiometricPrompt waits for its operation // to arrive at the head of the queue, before pinging it to start. - final boolean shouldStartNow = currentClient.getCookie() == 0; - if (shouldStartNow) { - if (mCurrentOperation.isUnstartableHalOperation()) { - final HalClientMonitor<?> halClientMonitor = - (HalClientMonitor<?>) mCurrentOperation.mClientMonitor; + final int cookie = mCurrentOperation.isReadyToStart(); + if (cookie == 0) { + if (!mCurrentOperation.start(mInternalCallback)) { // Note down current length of queue final int pendingOperationsLength = mPendingOperations.size(); - final Operation lastOperation = mPendingOperations.peekLast(); + final BiometricSchedulerOperation lastOperation = mPendingOperations.peekLast(); Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation + ". Last pending operation: " + lastOperation); - // For current operations, 1) unableToStart, which notifies the caller-side, then - // 2) notify operation's callback, to notify applicable system service that the - // operation failed. - halClientMonitor.unableToStart(); - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientFinished( - mCurrentOperation.mClientMonitor, false /* success */); - } - // Then for each operation currently in the pending queue at the time of this // failure, do the same as above. Otherwise, it's possible that something like // setActiveUser fails, but then authenticate (for the wrong user) is invoked. for (int i = 0; i < pendingOperationsLength; i++) { - final Operation operation = mPendingOperations.pollFirst(); - if (operation == null) { + final BiometricSchedulerOperation operation = mPendingOperations.pollFirst(); + if (operation != null) { + Slog.w(getTag(), "[Aborting Operation] " + operation); + operation.abort(); + } else { Slog.e(getTag(), "Null operation, index: " + i + ", expected length: " + pendingOperationsLength); - break; - } - if (operation.isHalOperation()) { - ((HalClientMonitor<?>) operation.mClientMonitor).unableToStart(); - } - if (operation.mClientCallback != null) { - operation.mClientCallback.onClientFinished(operation.mClientMonitor, - false /* success */); } - Slog.w(getTag(), "[Aborted Operation] " + operation); } // It's possible that during cleanup a new set of operations came in. We can try to @@ -465,25 +315,20 @@ public class BiometricScheduler { // actually be multiple operations (i.e. updateActiveUser + authenticate). mCurrentOperation = null; startNextOperationIfIdle(); - } else { - Slog.d(getTag(), "[Starting] " + mCurrentOperation); - currentClient.start(getInternalCallback()); - mCurrentOperation.mState = Operation.STATE_STARTED; } } else { try { - mBiometricService.onReadyForAuthentication(currentClient.getCookie()); + mBiometricService.onReadyForAuthentication(cookie); } catch (RemoteException e) { Slog.e(getTag(), "Remote exception when contacting BiometricService", e); } Slog.d(getTag(), "Waiting for cookie before starting: " + mCurrentOperation); - mCurrentOperation.mState = Operation.STATE_WAITING_FOR_COOKIE; } } /** * Starts the {@link #mCurrentOperation} if - * 1) its state is {@link Operation#STATE_WAITING_FOR_COOKIE} and + * 1) its state is {@link BiometricSchedulerOperation#STATE_WAITING_FOR_COOKIE} and * 2) its cookie matches this cookie * * This is currently only used by {@link com.android.server.biometrics.BiometricService}, which @@ -499,45 +344,13 @@ public class BiometricScheduler { Slog.e(getTag(), "Current operation is null"); return; } - if (mCurrentOperation.mState != Operation.STATE_WAITING_FOR_COOKIE) { - if (mCurrentOperation.mState == Operation.STATE_WAITING_IN_QUEUE_CANCELING) { - Slog.d(getTag(), "Operation was marked for cancellation, cancelling now: " - + mCurrentOperation); - // This should trigger the internal onClientFinished callback, which clears the - // operation and starts the next one. - final ErrorConsumer errorConsumer = - (ErrorConsumer) mCurrentOperation.mClientMonitor; - errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, - 0 /* vendorCode */); - return; - } else { - Slog.e(getTag(), "Operation is in the wrong state: " + mCurrentOperation - + ", expected STATE_WAITING_FOR_COOKIE"); - return; - } - } - if (mCurrentOperation.mClientMonitor.getCookie() != cookie) { - Slog.e(getTag(), "Mismatched cookie for operation: " + mCurrentOperation - + ", received: " + cookie); - return; - } - if (mCurrentOperation.isUnstartableHalOperation()) { + if (mCurrentOperation.startWithCookie(mInternalCallback, cookie)) { + Slog.d(getTag(), "[Started] Prepared client: " + mCurrentOperation); + } else { Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation); - // This is BiometricPrompt trying to auth but something's wrong with the HAL. - final HalClientMonitor<?> halClientMonitor = - (HalClientMonitor<?>) mCurrentOperation.mClientMonitor; - halClientMonitor.unableToStart(); - if (mCurrentOperation.mClientCallback != null) { - mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.mClientMonitor, - false /* success */); - } mCurrentOperation = null; startNextOperationIfIdle(); - } else { - Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation); - mCurrentOperation.mState = Operation.STATE_STARTED; - mCurrentOperation.mClientMonitor.start(getInternalCallback()); } } @@ -562,17 +375,13 @@ public class BiometricScheduler { // pending clients as canceling. Once they reach the head of the queue, the scheduler will // send ERROR_CANCELED and skip the operation. if (clientMonitor.interruptsPrecedingClients()) { - for (Operation operation : mPendingOperations) { - if (operation.mClientMonitor instanceof Interruptable - && operation.mState != Operation.STATE_WAITING_IN_QUEUE_CANCELING) { - Slog.d(getTag(), "New client incoming, marking pending client as canceling: " - + operation.mClientMonitor); - operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING; - } + for (BiometricSchedulerOperation operation : mPendingOperations) { + Slog.d(getTag(), "New client, marking pending op as canceling: " + operation); + operation.markCanceling(); } } - mPendingOperations.add(new Operation(clientMonitor, clientCallback)); + mPendingOperations.add(new BiometricSchedulerOperation(clientMonitor, clientCallback)); Slog.d(getTag(), "[Added] " + clientMonitor + ", new queue size: " + mPendingOperations.size()); @@ -580,67 +389,34 @@ public class BiometricScheduler { // cancellable, start the cancellation process. if (clientMonitor.interruptsPrecedingClients() && mCurrentOperation != null - && mCurrentOperation.mClientMonitor instanceof Interruptable - && mCurrentOperation.mState == Operation.STATE_STARTED) { + && mCurrentOperation.isInterruptable() + && mCurrentOperation.isStarted()) { Slog.d(getTag(), "[Cancelling Interruptable]: " + mCurrentOperation); - cancelInternal(mCurrentOperation); - } - - startNextOperationIfIdle(); - } - - private void cancelInternal(Operation operation) { - if (operation != mCurrentOperation) { - Slog.e(getTag(), "cancelInternal invoked on non-current operation: " + operation); - return; - } - if (!(operation.mClientMonitor instanceof Interruptable)) { - Slog.w(getTag(), "Operation not interruptable: " + operation); - return; - } - if (operation.mState == Operation.STATE_STARTED_CANCELING) { - Slog.w(getTag(), "Cancel already invoked for operation: " + operation); - return; - } - if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) { - Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation); - // We can set it to null immediately, since the HAL was never notified to start. - if (mCurrentOperation != null) { - mCurrentOperation.mClientMonitor.destroy(); - } - mCurrentOperation = null; + mCurrentOperation.cancel(mHandler, mInternalCallback); + } else { startNextOperationIfIdle(); - return; } - Slog.d(getTag(), "[Cancelling] Current client: " + operation.mClientMonitor); - final Interruptable interruptable = (Interruptable) operation.mClientMonitor; - interruptable.cancel(); - operation.mState = Operation.STATE_STARTED_CANCELING; - - // Add a watchdog. If the HAL does not acknowledge within the timeout, we will - // forcibly finish this client. - mHandler.postDelayed(new CancellationWatchdog(getTag(), operation), - CancellationWatchdog.DELAY_MS); } /** * Requests to cancel enrollment. * @param token from the caller, should match the token passed in when requesting enrollment */ - public void cancelEnrollment(IBinder token) { - if (mCurrentOperation == null) { - Slog.e(getTag(), "Unable to cancel enrollment, null operation"); - return; - } - final boolean isEnrolling = mCurrentOperation.mClientMonitor instanceof EnrollClient; - final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token; - if (!isEnrolling || !tokenMatches) { - Slog.w(getTag(), "Not cancelling enrollment, isEnrolling: " + isEnrolling - + " tokenMatches: " + tokenMatches); - return; - } + public void cancelEnrollment(IBinder token, long requestId) { + Slog.d(getTag(), "cancelEnrollment, requestId: " + requestId); - cancelInternal(mCurrentOperation); + if (mCurrentOperation != null + && canCancelEnrollOperation(mCurrentOperation, token, requestId)) { + Slog.d(getTag(), "Cancelling enrollment op: " + mCurrentOperation); + mCurrentOperation.cancel(mHandler, mInternalCallback); + } else { + for (BiometricSchedulerOperation operation : mPendingOperations) { + if (canCancelEnrollOperation(operation, token, requestId)) { + Slog.d(getTag(), "Cancelling pending enrollment op: " + operation); + operation.markCanceling(); + } + } + } } /** @@ -649,62 +425,42 @@ public class BiometricScheduler { * @param requestId the id returned when requesting authentication */ public void cancelAuthenticationOrDetection(IBinder token, long requestId) { - Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId - + " current: " + mCurrentOperation - + " stack size: " + mPendingOperations.size()); + Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId); if (mCurrentOperation != null && canCancelAuthOperation(mCurrentOperation, token, requestId)) { - Slog.d(getTag(), "Cancelling: " + mCurrentOperation); - cancelInternal(mCurrentOperation); + Slog.d(getTag(), "Cancelling auth/detect op: " + mCurrentOperation); + mCurrentOperation.cancel(mHandler, mInternalCallback); } else { - // Look through the current queue for all authentication clients for the specified - // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking - // all of them, instead of just the first one, since the API surface currently doesn't - // allow us to distinguish between multiple authentication requests from the same - // process. However, this generally does not happen anyway, and would be a class of - // bugs on its own. - for (Operation operation : mPendingOperations) { + for (BiometricSchedulerOperation operation : mPendingOperations) { if (canCancelAuthOperation(operation, token, requestId)) { - Slog.d(getTag(), "Marking " + operation - + " as STATE_WAITING_IN_QUEUE_CANCELING"); - operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING; + Slog.d(getTag(), "Cancelling pending auth/detect op: " + operation); + operation.markCanceling(); } } } } - private static boolean canCancelAuthOperation(Operation operation, IBinder token, - long requestId) { - // TODO: restrict callers that can cancel without requestId (negative value)? - return isAuthenticationOrDetectionOperation(operation) - && operation.mClientMonitor.getToken() == token - && isMatchingRequestId(operation, requestId); - } - - // By default, monitors are not associated with a request id to retain the original - // behavior (i.e. if no requestId is explicitly set then assume it matches) - private static boolean isMatchingRequestId(Operation operation, long requestId) { - return !operation.mClientMonitor.hasRequestId() - || operation.mClientMonitor.getRequestId() == requestId; + private static boolean canCancelEnrollOperation(BiometricSchedulerOperation operation, + IBinder token, long requestId) { + return operation.isEnrollOperation() + && operation.isMatchingToken(token) + && operation.isMatchingRequestId(requestId); } - private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) { - final boolean isAuthentication = - operation.mClientMonitor instanceof AuthenticationConsumer; - final boolean isDetection = - operation.mClientMonitor instanceof DetectionConsumer; - return isAuthentication || isDetection; + private static boolean canCancelAuthOperation(BiometricSchedulerOperation operation, + IBinder token, long requestId) { + // TODO: restrict callers that can cancel without requestId (negative value)? + return operation.isAuthenticationOrDetectionOperation() + && operation.isMatchingToken(token) + && operation.isMatchingRequestId(requestId); } /** * @return the current operation */ public BaseClientMonitor getCurrentClient() { - if (mCurrentOperation == null) { - return null; - } - return mCurrentOperation.mClientMonitor; + return mCurrentOperation != null ? mCurrentOperation.getClientMonitor() : null; } public int getCurrentPendingCount() { @@ -719,7 +475,7 @@ public class BiometricScheduler { new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); final String timestamp = dateFormat.format(new Date(System.currentTimeMillis())); final List<String> pendingOperations = new ArrayList<>(); - for (Operation operation : mPendingOperations) { + for (BiometricSchedulerOperation operation : mPendingOperations) { pendingOperations.add(operation.toString()); } @@ -735,7 +491,7 @@ public class BiometricScheduler { pw.println("Type: " + mSensorType); pw.println("Current operation: " + mCurrentOperation); pw.println("Pending operations: " + mPendingOperations.size()); - for (Operation operation : mPendingOperations) { + for (BiometricSchedulerOperation operation : mPendingOperations) { pw.println("Pending operation: " + operation); } for (CrashState crashState : mCrashStates) { @@ -746,7 +502,7 @@ public class BiometricScheduler { public byte[] dumpProtoState(boolean clearSchedulerBuffer) { final ProtoOutputStream proto = new ProtoOutputStream(); proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null - ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE); + ? mCurrentOperation.getProtoEnum() : BiometricsProto.CM_NONE); proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled); if (!mRecentOperations.isEmpty()) { @@ -771,6 +527,7 @@ public class BiometricScheduler { * HAL dies. */ public void reset() { + Slog.d(getTag(), "Resetting scheduler"); mPendingOperations.clear(); mCurrentOperation = null; } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java new file mode 100644 index 000000000000..a8cce153dc70 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.BiometricConstants; +import android.os.Handler; +import android.os.IBinder; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Contains all the necessary information for a HAL operation. + */ +public class BiometricSchedulerOperation { + protected static final String TAG = "BiometricSchedulerOperation"; + + /** + * The operation is added to the list of pending operations and waiting for its turn. + */ + protected static final int STATE_WAITING_IN_QUEUE = 0; + + /** + * The operation is added to the list of pending operations, but a subsequent operation + * has been added. This state only applies to {@link Interruptable} operations. When this + * operation reaches the head of the queue, it will send ERROR_CANCELED and finish. + */ + protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1; + + /** + * The operation has reached the front of the queue and has started. + */ + protected static final int STATE_STARTED = 2; + + /** + * The operation was started, but is now canceling. Operations should wait for the HAL to + * acknowledge that the operation was canceled, at which point it finishes. + */ + protected static final int STATE_STARTED_CANCELING = 3; + + /** + * The operation has reached the head of the queue but is waiting for BiometricService + * to acknowledge and start the operation. + */ + protected static final int STATE_WAITING_FOR_COOKIE = 4; + + /** + * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished. + */ + protected static final int STATE_FINISHED = 5; + + @IntDef({STATE_WAITING_IN_QUEUE, + STATE_WAITING_IN_QUEUE_CANCELING, + STATE_STARTED, + STATE_STARTED_CANCELING, + STATE_WAITING_FOR_COOKIE, + STATE_FINISHED}) + @Retention(RetentionPolicy.SOURCE) + protected @interface OperationState {} + + private static final int CANCEL_WATCHDOG_DELAY_MS = 3000; + + @NonNull + private final BaseClientMonitor mClientMonitor; + @Nullable + private final BaseClientMonitor.Callback mClientCallback; + @OperationState + private int mState; + @VisibleForTesting + @NonNull + final Runnable mCancelWatchdog; + + BiometricSchedulerOperation( + @NonNull BaseClientMonitor clientMonitor, + @Nullable BaseClientMonitor.Callback callback + ) { + this(clientMonitor, callback, STATE_WAITING_IN_QUEUE); + } + + protected BiometricSchedulerOperation( + @NonNull BaseClientMonitor clientMonitor, + @Nullable BaseClientMonitor.Callback callback, + @OperationState int state + ) { + mClientMonitor = clientMonitor; + mClientCallback = callback; + mState = state; + mCancelWatchdog = () -> { + if (!isFinished()) { + Slog.e(TAG, "[Watchdog Triggered]: " + this); + getWrappedCallback().onClientFinished(mClientMonitor, false /* success */); + } + }; + } + + /** + * Zero if this operation is ready to start or has already started. A non-zero cookie + * is returned if the operation has not started and is waiting on + * {@link android.hardware.biometrics.IBiometricService#onReadyForAuthentication(int)}. + * + * @return cookie or 0 if ready/started + */ + public int isReadyToStart() { + if (mState == STATE_WAITING_FOR_COOKIE || mState == STATE_WAITING_IN_QUEUE) { + final int cookie = mClientMonitor.getCookie(); + if (cookie != 0) { + mState = STATE_WAITING_FOR_COOKIE; + } + return cookie; + } + + return 0; + } + + /** + * Start this operation without waiting for a cookie + * (i.e. {@link #isReadyToStart() returns zero} + * + * @param callback lifecycle callback + * @return if this operation started + */ + public boolean start(@NonNull BaseClientMonitor.Callback callback) { + checkInState("start", + STATE_WAITING_IN_QUEUE, + STATE_WAITING_FOR_COOKIE, + STATE_WAITING_IN_QUEUE_CANCELING); + + if (mClientMonitor.getCookie() != 0) { + throw new IllegalStateException("operation requires cookie"); + } + + return doStart(callback); + } + + /** + * Start this operation after receiving the given cookie. + * + * @param callback lifecycle callback + * @param cookie cookie indicting the operation should begin + * @return if this operation started + */ + public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) { + checkInState("start", + STATE_WAITING_IN_QUEUE, + STATE_WAITING_FOR_COOKIE, + STATE_WAITING_IN_QUEUE_CANCELING); + + if (mClientMonitor.getCookie() != cookie) { + Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie); + return false; + } + + return doStart(callback); + } + + private boolean doStart(@NonNull BaseClientMonitor.Callback callback) { + final BaseClientMonitor.Callback cb = getWrappedCallback(callback); + + if (mState == STATE_WAITING_IN_QUEUE_CANCELING) { + Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this); + + cb.onClientFinished(mClientMonitor, true /* success */); + if (mClientMonitor instanceof ErrorConsumer) { + final ErrorConsumer errorConsumer = (ErrorConsumer) mClientMonitor; + errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_CANCELED, + 0 /* vendorCode */); + } else { + Slog.w(TAG, "monitor cancelled but does not implement ErrorConsumer"); + } + + return false; + } + + if (isUnstartableHalOperation()) { + Slog.v(TAG, "unable to start: " + this); + ((HalClientMonitor<?>) mClientMonitor).unableToStart(); + cb.onClientFinished(mClientMonitor, false /* success */); + return false; + } + + mState = STATE_STARTED; + mClientMonitor.start(cb); + + Slog.v(TAG, "started: " + this); + return true; + } + + /** + * Abort a pending operation. + * + * This is similar to cancel but the operation must not have been started. It will + * immediately abort the operation and notify the client that it has finished unsuccessfully. + */ + public void abort() { + checkInState("cannot abort a non-pending operation", + STATE_WAITING_IN_QUEUE, + STATE_WAITING_FOR_COOKIE, + STATE_WAITING_IN_QUEUE_CANCELING); + + if (isHalOperation()) { + ((HalClientMonitor<?>) mClientMonitor).unableToStart(); + } + getWrappedCallback().onClientFinished(mClientMonitor, false /* success */); + + Slog.v(TAG, "Aborted: " + this); + } + + /** Flags this operation as canceled, but does not cancel it until started. */ + public void markCanceling() { + if (mState == STATE_WAITING_IN_QUEUE && isInterruptable()) { + mState = STATE_WAITING_IN_QUEUE_CANCELING; + Slog.v(TAG, "Marked cancelling: " + this); + } + } + + /** + * Cancel the operation now. + * + * @param handler handler to use for the cancellation watchdog + * @param callback lifecycle callback (only used if this operation hasn't started, otherwise + * the callback used from {@link #start(BaseClientMonitor.Callback)} is used) + */ + public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) { + checkNotInState("cancel", STATE_FINISHED); + + final int currentState = mState; + if (!isInterruptable()) { + Slog.w(TAG, "Cannot cancel - operation not interruptable: " + this); + return; + } + if (currentState == STATE_STARTED_CANCELING) { + Slog.w(TAG, "Cannot cancel - already invoked for operation: " + this); + return; + } + + mState = STATE_STARTED_CANCELING; + if (currentState == STATE_WAITING_IN_QUEUE + || currentState == STATE_WAITING_IN_QUEUE_CANCELING + || currentState == STATE_WAITING_FOR_COOKIE) { + Slog.d(TAG, "[Cancelling] Current client (without start): " + mClientMonitor); + ((Interruptable) mClientMonitor).cancelWithoutStarting(getWrappedCallback(callback)); + } else { + Slog.d(TAG, "[Cancelling] Current client: " + mClientMonitor); + ((Interruptable) mClientMonitor).cancel(); + } + + // forcibly finish this client if the HAL does not acknowledge within the timeout + handler.postDelayed(mCancelWatchdog, CANCEL_WATCHDOG_DELAY_MS); + } + + @NonNull + private BaseClientMonitor.Callback getWrappedCallback() { + return getWrappedCallback(null); + } + + @NonNull + private BaseClientMonitor.Callback getWrappedCallback( + @Nullable BaseClientMonitor.Callback callback) { + final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, + boolean success) { + mClientMonitor.destroy(); + mState = STATE_FINISHED; + } + }; + return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback); + } + + /** {@link BaseClientMonitor#getSensorId()}. */ + public int getSensorId() { + return mClientMonitor.getSensorId(); + } + + /** {@link BaseClientMonitor#getProtoEnum()}. */ + public int getProtoEnum() { + return mClientMonitor.getProtoEnum(); + } + + /** {@link BaseClientMonitor#getTargetUserId()}. */ + public int getTargetUserId() { + return mClientMonitor.getTargetUserId(); + } + + /** If the given clientMonitor is the same as the one in the constructor. */ + public boolean isFor(@NonNull BaseClientMonitor clientMonitor) { + return mClientMonitor == clientMonitor; + } + + /** If this operation is {@link Interruptable}. */ + public boolean isInterruptable() { + return mClientMonitor instanceof Interruptable; + } + + private boolean isHalOperation() { + return mClientMonitor instanceof HalClientMonitor<?>; + } + + private boolean isUnstartableHalOperation() { + if (isHalOperation()) { + final HalClientMonitor<?> client = (HalClientMonitor<?>) mClientMonitor; + if (client.getFreshDaemon() == null) { + return true; + } + } + return false; + } + + /** If this operation is an enrollment. */ + public boolean isEnrollOperation() { + return mClientMonitor instanceof EnrollClient; + } + + /** If this operation is authentication. */ + public boolean isAuthenticateOperation() { + return mClientMonitor instanceof AuthenticationClient; + } + + /** If this operation is authentication or detection. */ + public boolean isAuthenticationOrDetectionOperation() { + final boolean isAuthentication = mClientMonitor instanceof AuthenticationConsumer; + final boolean isDetection = mClientMonitor instanceof DetectionConsumer; + return isAuthentication || isDetection; + } + + /** If this operation performs acquisition {@link AcquisitionClient}. */ + public boolean isAcquisitionOperation() { + return mClientMonitor instanceof AcquisitionClient; + } + + /** + * If this operation matches the original requestId. + * + * By default, monitors are not associated with a request id to retain the original + * behavior (i.e. if no requestId is explicitly set then assume it matches) + * + * @param requestId a unique id {@link BaseClientMonitor#setRequestId(long)}. + */ + public boolean isMatchingRequestId(long requestId) { + return !mClientMonitor.hasRequestId() + || mClientMonitor.getRequestId() == requestId; + } + + /** If the token matches */ + public boolean isMatchingToken(@Nullable IBinder token) { + return mClientMonitor.getToken() == token; + } + + /** If this operation has started. */ + public boolean isStarted() { + return mState == STATE_STARTED; + } + + /** If this operation is cancelling but has not yet completed. */ + public boolean isCanceling() { + return mState == STATE_STARTED_CANCELING; + } + + /** If this operation has finished and completed its lifecycle. */ + public boolean isFinished() { + return mState == STATE_FINISHED; + } + + /** If {@link #markCanceling()} was called but the operation hasn't been canceled. */ + public boolean isMarkedCanceling() { + return mState == STATE_WAITING_IN_QUEUE_CANCELING; + } + + /** + * The monitor passed to the constructor. + * @deprecated avoid using and move to encapsulate within the operation + */ + @Deprecated + public BaseClientMonitor getClientMonitor() { + return mClientMonitor; + } + + private void checkNotInState(String message, @OperationState int... states) { + for (int state : states) { + if (mState == state) { + throw new IllegalStateException(message + ": illegal state= " + state); + } + } + } + + private void checkInState(String message, @OperationState int... states) { + for (int state : states) { + if (mState == state) { + return; + } + } + throw new IllegalStateException(message + ": illegal state= " + mState); + } + + @Override + public String toString() { + return mClientMonitor + ", State: " + mState; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java index fab98b6581a3..d5093c756415 100644 --- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java +++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java @@ -32,6 +32,11 @@ public interface Interruptable { * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens * if the client is still waiting in the pending queue and got notified that a subsequent * operation is preempting it. + * + * This method must invoke + * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the + * given callback (with success). + * * @param callback invoked when the operation is completed. */ void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback); diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index b056bf897b5c..19eaa178c7c9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -16,10 +16,13 @@ package com.android.server.biometrics.sensors; +import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.IBiometricService; +import android.os.Handler; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Slog; @@ -68,9 +71,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { return; } - Slog.d(getTag(), "[Client finished] " - + clientMonitor + ", success: " + success); - if (mCurrentOperation != null && mCurrentOperation.mClientMonitor == mOwner) { + Slog.d(getTag(), "[Client finished] " + clientMonitor + ", success: " + success); + if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) { mCurrentOperation = null; startNextOperationIfIdle(); } else { @@ -83,26 +85,31 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } @VisibleForTesting - UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType, + UserAwareBiometricScheduler(@NonNull String tag, + @NonNull Handler handler, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull IBiometricService biometricService, @NonNull CurrentUserRetriever currentUserRetriever, @NonNull UserSwitchCallback userSwitchCallback, @NonNull CoexCoordinator coexCoordinator) { - super(tag, sensorType, gestureAvailabilityDispatcher, biometricService, + super(tag, handler, sensorType, gestureAvailabilityDispatcher, biometricService, LOG_NUM_RECENT_OPERATIONS, coexCoordinator); mCurrentUserRetriever = currentUserRetriever; mUserSwitchCallback = userSwitchCallback; } - public UserAwareBiometricScheduler(@NonNull String tag, @SensorType int sensorType, + public UserAwareBiometricScheduler(@NonNull String tag, + @NonNull Handler handler, + @SensorType int sensorType, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher, @NonNull CurrentUserRetriever currentUserRetriever, @NonNull UserSwitchCallback userSwitchCallback) { - this(tag, sensorType, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface( - ServiceManager.getService(Context.BIOMETRIC_SERVICE)), currentUserRetriever, - userSwitchCallback, CoexCoordinator.getInstance()); + this(tag, handler, sensorType, gestureAvailabilityDispatcher, + IBiometricService.Stub.asInterface( + ServiceManager.getService(Context.BIOMETRIC_SERVICE)), + currentUserRetriever, userSwitchCallback, CoexCoordinator.getInstance()); } @Override @@ -122,7 +129,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { } final int currentUserId = mCurrentUserRetriever.getCurrentUserId(); - final int nextUserId = mPendingOperations.getFirst().mClientMonitor.getTargetUserId(); + final int nextUserId = mPendingOperations.getFirst().getTargetUserId(); if (nextUserId == currentUserId) { super.startNextOperationIfIdle(); @@ -133,8 +140,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { new ClientFinishedCallback(startClient); Slog.d(getTag(), "[Starting User] " + startClient); - mCurrentOperation = new Operation( - startClient, finishedCallback, Operation.STATE_STARTED); + mCurrentOperation = new BiometricSchedulerOperation( + startClient, finishedCallback, STATE_STARTED); startClient.start(finishedCallback); } else { if (mStopUserClient != null) { @@ -147,8 +154,8 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { Slog.d(getTag(), "[Stopping User] current: " + currentUserId + ", next: " + nextUserId + ". " + mStopUserClient); - mCurrentOperation = new Operation( - mStopUserClient, finishedCallback, Operation.STATE_STARTED); + mCurrentOperation = new BiometricSchedulerOperation( + mStopUserClient, finishedCallback, STATE_STARTED); mStopUserClient.start(finishedCallback); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 675ee545a14f..039b08e805c1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -213,7 +213,7 @@ public class FaceService extends SystemService { } @Override // Binder call - public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, + public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); @@ -221,23 +221,24 @@ public class FaceService extends SystemService { final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); - return; + return -1; } - provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, + return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, receiver, opPackageName, disabledFeatures, previewSurface, debugConsent); } @Override // Binder call - public void enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, + public long enrollRemotely(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); // TODO(b/145027036): Implement this. + return -1; } @Override // Binder call - public void cancelEnrollment(final IBinder token) { + public void cancelEnrollment(final IBinder token, long requestId) { Utils.checkPermission(getContext(), MANAGE_BIOMETRIC); final Pair<Integer, ServiceProvider> provider = getSingleProvider(); @@ -246,7 +247,7 @@ public class FaceService extends SystemService { return; } - provider.second.cancelEnrollment(provider.first, token); + provider.second.cancelEnrollment(provider.first, token, requestId); } @Override // Binder call @@ -624,7 +625,7 @@ public class FaceService extends SystemService { private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) { for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { mServiceProviders.add( - new Face10(getContext(), hidlSensor, mLockoutResetDispatcher)); + Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher)); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java index e099ba372b05..77e431c81192 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java @@ -94,12 +94,12 @@ public interface ServiceProvider { void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token, @NonNull String opPackageName, long challenge); - void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, + long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, boolean debugConsent); - void cancelEnrollment(int sensorId, @NonNull IBinder token); + void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index a806277ed45e..aae4fbe9b0d7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -82,13 +82,14 @@ public class FaceEnrollClient extends EnrollClient<ISession> { FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, + @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser, boolean debugConsent) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, false /* shouldVibrate */); + setRequestId(requestId); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); mEnrollIgnoreListVendor = getContext().getResources() diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 4bae7756abe0..ae507abea537 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -327,17 +327,18 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, boolean debugConsent) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { final int maxTemplatesPerUser = mSensors.get( sensorId).getSensorProperties().maxEnrollmentsPerUser; final FaceEnrollClient client = new FaceEnrollClient(mContext, mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures, + opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, debugConsent); scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() { @@ -351,11 +352,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> + mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 206b8f0779e8..39270430c21d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -494,7 +494,7 @@ public class Sensor { mToken = new Binder(); mHandler = handler; mSensorProperties = sensorProperties; - mScheduler = new UserAwareBiometricScheduler(tag, + mScheduler = new UserAwareBiometricScheduler(tag, mHandler, BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, new UserAwareBiometricScheduler.UserSwitchCallback() { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index f4dcbbba21d7..493c0a05e379 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -333,12 +333,13 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull Handler handler, @NonNull BiometricScheduler scheduler) { mSensorProperties = sensorProps; mContext = context; mSensorId = sensorProps.sensorId; mScheduler = scheduler; - mHandler = new Handler(Looper.getMainLooper()); + mHandler = handler; mUsageStats = new UsageStats(context); mAuthenticatorIds = new HashMap<>(); mLazyDaemon = Face10.this::getDaemon; @@ -357,10 +358,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } } - public Face10(@NonNull Context context, @NonNull FaceSensorPropertiesInternal sensorProps, + public static Face10 newInstance(@NonNull Context context, + @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { - this(context, sensorProps, lockoutResetDispatcher, - new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, + final Handler handler = new Handler(Looper.getMainLooper()); + return new Face10(context, sensorProps, lockoutResetDispatcher, handler, + new BiometricScheduler(TAG, handler, BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityTracker */)); } @@ -573,10 +576,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, @NonNull int[] disabledFeatures, @Nullable Surface previewSurface, boolean debugConsent) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); @@ -584,7 +588,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, - opPackageName, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, + opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, ENROLL_TIMEOUT_SEC, previewSurface, mSensorId); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @@ -598,13 +602,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> { - mScheduler.cancelEnrollment(token); - }); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 80828cced4e8..31e5c86103fb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -53,12 +53,13 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull byte[] hardwareAuthToken, @NonNull String owner, + @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, @Nullable Surface previewSurface, int sensorId) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, false /* shouldVibrate */); + setRequestId(requestId); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 3e70ee52ff1b..6366e19ef191 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -249,7 +249,7 @@ public class FingerprintService extends SystemService { } @Override // Binder call - public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, + public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); @@ -257,15 +257,15 @@ public class FingerprintService extends SystemService { final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); - return; + return -1; } - provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, + return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId, receiver, opPackageName, enrollReason); } @Override // Binder call - public void cancelEnrollment(final IBinder token) { + public void cancelEnrollment(final IBinder token, long requestId) { Utils.checkPermission(getContext(), MANAGE_FINGERPRINT); final Pair<Integer, ServiceProvider> provider = getSingleProvider(); @@ -274,7 +274,7 @@ public class FingerprintService extends SystemService { return; } - provider.second.cancelEnrollment(provider.first, token); + provider.second.cancelEnrollment(provider.first, token, requestId); } @SuppressWarnings("deprecation") @@ -818,7 +818,7 @@ public class FingerprintService extends SystemService { mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } else { fingerprint21 = Fingerprint21.newInstance(getContext(), - mFingerprintStateCallback, hidlSensor, + mFingerprintStateCallback, hidlSensor, mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher); } mServiceProviders.add(fingerprint21); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index 1772f814dd10..535705c63cab 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -88,11 +88,11 @@ public interface ServiceProvider { /** * Schedules fingerprint enrollment. */ - void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, + long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason); - void cancelEnrollment(int sensorId, @NonNull IBinder token); + void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId); long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId, @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index ccb34aad3198..67507ccbbbfe 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -57,7 +57,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { private boolean mIsPointerDown; FingerprintEnrollClient(@NonNull Context context, - @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, + @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @@ -69,6 +69,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, !sensorProps.isAnyUdfpsType() /* shouldVibrate */); + setRequestId(requestId); mSensorProps = sensorProps; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mMaxTemplatesPerUser = maxTemplatesPerUser; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 734b1737dfbc..eb16c763dea6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -347,15 +347,16 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties() .maxEnrollmentsPerUser; final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mSensors.get(sensorId).getLazySession(), token, + mSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, mSensors.get(sensorId).getSensorProperties(), @@ -378,11 +379,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token)); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> + mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 59e4b582ca84..256761a61a72 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -449,7 +449,7 @@ class Sensor { mHandler = handler; mSensorProperties = sensorProperties; mLockoutCache = new LockoutCache(); - mScheduler = new UserAwareBiometricScheduler(tag, + mScheduler = new UserAwareBiometricScheduler(tag, handler, BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties), gestureAvailabilityDispatcher, () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 5f2f4cf6ef3c..d352cda609e3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -42,7 +42,6 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.IBinder; import android.os.IHwBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -320,7 +319,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider Fingerprint21(@NonNull Context context, @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @NonNull BiometricScheduler scheduler, @NonNull Handler handler, + @NonNull BiometricScheduler scheduler, + @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull HalResultController controller) { mContext = context; @@ -356,16 +356,15 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider public static Fingerprint21 newInstance(@NonNull Context context, @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintSensorPropertiesInternal sensorProps, + @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - final Handler handler = new Handler(Looper.getMainLooper()); final BiometricScheduler scheduler = - new BiometricScheduler(TAG, + new BiometricScheduler(TAG, handler, BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps), gestureAvailabilityDispatcher); final HalResultController controller = new HalResultController(sensorProps.sensorId, - context, handler, - scheduler); + context, handler, scheduler); return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler, lockoutResetDispatcher, controller); } @@ -558,18 +557,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } @Override - public void scheduleEnroll(int sensorId, @NonNull IBinder token, + public long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { + final long id = mRequestCounter.incrementAndGet(); mHandler.post(() -> { scheduleUpdateActiveUserWithoutHandler(userId); final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, - mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController, - mSidefpsController, enrollReason); + mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), + userId, hardwareAuthToken, opPackageName, + FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, + mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController, + enrollReason); mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -588,13 +589,12 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } }); }); + return id; } @Override - public void cancelEnrollment(int sensorId, @NonNull IBinder token) { - mHandler.post(() -> { - mScheduler.cancelEnrollment(token); - }); + public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) { + mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId)); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index dd68b4d37e2a..20dab5552df9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -26,7 +26,6 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.fingerprint.FingerprintStateListener; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Handler; import android.os.IBinder; @@ -135,43 +134,17 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage @NonNull private final RestartAuthRunnable mRestartAuthRunnable; private static class TestableBiometricScheduler extends BiometricScheduler { - @NonNull private final TestableInternalCallback mInternalCallback; @NonNull private Fingerprint21UdfpsMock mFingerprint21; - TestableBiometricScheduler(@NonNull String tag, + TestableBiometricScheduler(@NonNull String tag, @NonNull Handler handler, @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { - super(tag, BiometricScheduler.SENSOR_TYPE_FP_OTHER, + super(tag, handler, BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher); - mInternalCallback = new TestableInternalCallback(); - } - - class TestableInternalCallback extends InternalCallback { - @Override - public void onClientStarted(BaseClientMonitor clientMonitor) { - super.onClientStarted(clientMonitor); - Slog.d(TAG, "Client started: " + clientMonitor); - mFingerprint21.setDebugMessage("Started: " + clientMonitor); - } - - @Override - public void onClientFinished(BaseClientMonitor clientMonitor, boolean success) { - super.onClientFinished(clientMonitor, success); - Slog.d(TAG, "Client finished: " + clientMonitor); - mFingerprint21.setDebugMessage("Finished: " + clientMonitor); - } } void init(@NonNull Fingerprint21UdfpsMock fingerprint21) { mFingerprint21 = fingerprint21; } - - /** - * Expose the internal finish callback so it can be used for testing - */ - @Override - @NonNull protected InternalCallback getInternalCallback() { - return mInternalCallback; - } } /** @@ -280,7 +253,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final Handler handler = new Handler(Looper.getMainLooper()); final TestableBiometricScheduler scheduler = - new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher); + new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher); final MockHalResultController controller = new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index 1ebf44ca707f..cc50bdfb59ae 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -55,7 +55,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint FingerprintEnrollClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, - @NonNull ClientMonitorCallbackConverter listener, int userId, + long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController, @@ -64,6 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, true /* shouldVibrate */); + setRequestId(requestId); mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mEnrollReason = enrollReason; diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index 72e900bbad18..cc9efbc64c02 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -68,7 +68,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; -import com.android.server.net.NetworkStatsManagerInternal; import java.time.Clock; import java.time.ZoneId; @@ -262,8 +261,10 @@ public class MultipathPolicyTracker { private long getNetworkTotalBytes(long start, long end) { try { - return LocalServices.getService(NetworkStatsManagerInternal.class) - .getNetworkTotalBytes(mNetworkTemplate, start, end); + final android.app.usage.NetworkStats.Bucket ret = + mContext.getSystemService(NetworkStatsManager.class) + .querySummaryForDevice(mNetworkTemplate, start, end); + return ret.getRxBytes() + ret.getTxBytes(); } catch (RuntimeException e) { Log.w(TAG, "Failed to get data usage: " + e); return -1; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index c4f2b14e335b..be889e47db01 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1781,6 +1781,14 @@ public final class DisplayManagerService extends SystemService { return mDisplayModeDirector.getModeSwitchingType(); } + private boolean getDisplayDecorationSupportInternal(int displayId) { + final IBinder displayToken = getDisplayToken(displayId); + if (null == displayToken) { + return false; + } + return SurfaceControl.getDisplayDecorationSupport(displayToken); + } + private void setBrightnessConfigurationForDisplayInternal( @Nullable BrightnessConfiguration c, String uniqueId, @UserIdInt int userId, String packageName) { @@ -3441,6 +3449,16 @@ public final class DisplayManagerService extends SystemService { Binder.restoreCallingIdentity(token); } } + + @Override // Binder call + public boolean getDisplayDecorationSupport(int displayId) { + final long token = Binder.clearCallingIdentity(); + try { + return getDisplayDecorationSupportInternal(displayId); + } finally { + Binder.restoreCallingIdentity(token); + } + } } private static boolean isValidBrightness(float brightness) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 2dfaa8bad380..c4d02c7abafb 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -50,6 +50,8 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.MathUtils; +import android.util.MutableFloat; +import android.util.MutableInt; import android.util.Slog; import android.util.TimeUtils; import android.view.Display; @@ -1354,6 +1356,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Animate the screen brightness when the screen is on or dozing. // Skip the animation when the screen is off or suspended or transition to/from VR. + boolean brightnessAdjusted = false; if (!mPendingScreenOff) { if (mSkipScreenOnBrightnessRamp) { if (state == Display.STATE_ON) { @@ -1446,15 +1449,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // slider event so notify as if the system changed the brightness. userInitiatedChange = false; } - notifyBrightnessChanged(brightnessState, userInitiatedChange, + notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange, hadUserBrightnessPoint); } // We save the brightness info *after* the brightness setting has been changed and // adjustments made so that the brightness info reflects the latest value. - saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); + brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue); } else { - saveBrightnessInfo(getScreenBrightnessSetting()); + brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting()); + } + + if (brightnessAdjusted) { + postBrightnessChangeRunnable(); } // Log any changes to what is currently driving the brightness setting. @@ -1570,31 +1577,50 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public BrightnessInfo getBrightnessInfo() { synchronized (mCachedBrightnessInfo) { return new BrightnessInfo( - mCachedBrightnessInfo.brightness, - mCachedBrightnessInfo.adjustedBrightness, - mCachedBrightnessInfo.brightnessMin, - mCachedBrightnessInfo.brightnessMax, - mCachedBrightnessInfo.hbmMode, - mCachedBrightnessInfo.highBrightnessTransitionPoint); + mCachedBrightnessInfo.brightness.value, + mCachedBrightnessInfo.adjustedBrightness.value, + mCachedBrightnessInfo.brightnessMin.value, + mCachedBrightnessInfo.brightnessMax.value, + mCachedBrightnessInfo.hbmMode.value, + mCachedBrightnessInfo.hbmTransitionPoint.value); } } - private void saveBrightnessInfo(float brightness) { - saveBrightnessInfo(brightness, brightness); + private boolean saveBrightnessInfo(float brightness) { + return saveBrightnessInfo(brightness, brightness); } - private void saveBrightnessInfo(float brightness, float adjustedBrightness) { + private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { synchronized (mCachedBrightnessInfo) { - mCachedBrightnessInfo.brightness = brightness; - mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness; - mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin(); - mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax(); - mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode(); - mCachedBrightnessInfo.highBrightnessTransitionPoint = - mHbmController.getTransitionPoint(); + boolean changed = false; + + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness, + brightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness, + adjustedBrightness); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin, + mHbmController.getCurrentBrightnessMin()); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax, + mHbmController.getCurrentBrightnessMax()); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, + mHbmController.getHighBrightnessMode()); + changed |= + mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, + mHbmController.getTransitionPoint()); + + return changed; } } + void postBrightnessChangeRunnable() { + mHandler.post(mOnBrightnessChangeRunnable); + } + private HighBrightnessModeController createHbmControllerLocked() { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); @@ -1607,7 +1633,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, () -> { sendUpdatePowerStateLocked(); - mHandler.post(mOnBrightnessChangeRunnable); + postBrightnessChangeRunnable(); // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern. if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.update(); @@ -2109,7 +2135,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void setCurrentScreenBrightness(float brightnessValue) { if (brightnessValue != mCurrentScreenBrightnessSetting) { mCurrentScreenBrightnessSetting = brightnessValue; - mHandler.post(mOnBrightnessChangeRunnable); + postBrightnessChangeRunnable(); } } @@ -2161,7 +2187,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return true; } - private void notifyBrightnessChanged(float brightness, boolean userInitiated, + private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated, boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f @@ -2271,16 +2297,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); pw.println(" mColorFadeEnabled=" + mColorFadeEnabled); synchronized (mCachedBrightnessInfo) { - pw.println(" mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness); + pw.println(" mCachedBrightnessInfo.brightness=" + + mCachedBrightnessInfo.brightness.value); pw.println(" mCachedBrightnessInfo.adjustedBrightness=" + - mCachedBrightnessInfo.adjustedBrightness); + mCachedBrightnessInfo.adjustedBrightness.value); pw.println(" mCachedBrightnessInfo.brightnessMin=" + - mCachedBrightnessInfo.brightnessMin); + mCachedBrightnessInfo.brightnessMin.value); pw.println(" mCachedBrightnessInfo.brightnessMax=" + - mCachedBrightnessInfo.brightnessMax); - pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode); - pw.println(" mCachedBrightnessInfo.highBrightnessTransitionPoint=" + - mCachedBrightnessInfo.highBrightnessTransitionPoint); + mCachedBrightnessInfo.brightnessMax.value); + pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value); + pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" + + mCachedBrightnessInfo.hbmTransitionPoint.value); } pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); @@ -2698,11 +2725,31 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } static class CachedBrightnessInfo { - public float brightness; - public float adjustedBrightness; - public float brightnessMin; - public float brightnessMax; - public int hbmMode; - public float highBrightnessTransitionPoint; + public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat adjustedBrightness = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat brightnessMin = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableFloat brightnessMax = + new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT); + public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); + public MutableFloat hbmTransitionPoint = + new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); + + public boolean checkAndSetFloat(MutableFloat mf, float f) { + if (mf.value != f) { + mf.value = f; + return true; + } + return false; + } + + public boolean checkAndSetInt(MutableInt mi, int i) { + if (mi.value != i) { + mi.value = i; + return true; + } + return false; + } } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index e40d86a55b77..9fb1d8e744fb 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -896,8 +896,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub info.packageName = mPackage; info.attributionTag = mAttributionTag; info.type = (mUid == Process.SYSTEM_UID) - ? HostEndpointInfo.Type.TYPE_FRAMEWORK - : HostEndpointInfo.Type.TYPE_APP; + ? HostEndpointInfo.Type.FRAMEWORK + : HostEndpointInfo.Type.APP; mContextHubProxy.onHostEndpointConnected(info); } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index 19f7c4d8da37..c199bb30a6d3 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -152,6 +152,14 @@ import java.util.concurrent.atomic.AtomicInteger; @Override /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { + ContextHubStatsLog.write( + ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED, + nanoAppBinary.getNanoAppId(), + nanoAppBinary.getNanoAppVersion(), + ContextHubStatsLog + .CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_TYPE__TYPE_LOAD, + toStatsTransactionResult(result)); + if (result == ContextHubTransaction.RESULT_SUCCESS) { // NOTE: The legacy JNI code used to do a query right after a load success // to synchronize the service cache. Instead store the binary that was @@ -200,6 +208,13 @@ import java.util.concurrent.atomic.AtomicInteger; @Override /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { + ContextHubStatsLog.write( + ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED, nanoAppId, + 0 /* nanoappVersion */, + ContextHubStatsLog + .CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_TYPE__TYPE_UNLOAD, + toStatsTransactionResult(result)); + if (result == ContextHubTransaction.RESULT_SUCCESS) { mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId); } @@ -477,6 +492,30 @@ import java.util.concurrent.atomic.AtomicInteger; } } + private int toStatsTransactionResult(@ContextHubTransaction.Result int result) { + switch (result) { + case ContextHubTransaction.RESULT_SUCCESS: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_SUCCESS; + case ContextHubTransaction.RESULT_FAILED_BAD_PARAMS: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_BAD_PARAMS; + case ContextHubTransaction.RESULT_FAILED_UNINITIALIZED: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_UNINITIALIZED; + case ContextHubTransaction.RESULT_FAILED_BUSY: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_BUSY; + case ContextHubTransaction.RESULT_FAILED_AT_HUB: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_AT_HUB; + case ContextHubTransaction.RESULT_FAILED_TIMEOUT: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_TIMEOUT; + case ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_SERVICE_INTERNAL_FAILURE; + case ContextHubTransaction.RESULT_FAILED_HAL_UNAVAILABLE: + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_HAL_UNAVAILABLE; + case ContextHubTransaction.RESULT_FAILED_UNKNOWN: + default: /* fall through */ + return ContextHubStatsLog.CHRE_CODE_DOWNLOAD_TRANSACTED__TRANSACTION_RESULT__TRANSACTION_RESULT_FAILED_UNKNOWN; + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(100); diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index cc5aaf4f7f45..f84b68e7c556 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -453,8 +453,13 @@ public abstract class IContextHubWrapper { public int sendMessageToContextHub( short hostEndpointId, int contextHubId, NanoAppMessage message) throws RemoteException { - return toTransactionResult(mHub.sendMessageToHub(contextHubId, - ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message))); + try { + mHub.sendMessageToHub(contextHubId, + ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message)); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } } @ContextHubTransaction.Result @@ -462,31 +467,55 @@ public abstract class IContextHubWrapper { int transactionId) throws RemoteException { android.hardware.contexthub.NanoappBinary aidlNanoAppBinary = ContextHubServiceUtil.createAidlNanoAppBinary(binary); - return toTransactionResult( - mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId)); + try { + mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } } @ContextHubTransaction.Result public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId) throws RemoteException { - return toTransactionResult(mHub.unloadNanoapp(contextHubId, nanoappId, transactionId)); + try { + mHub.unloadNanoapp(contextHubId, nanoappId, transactionId); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } } @ContextHubTransaction.Result public int enableNanoapp(int contextHubId, long nanoappId, int transactionId) throws RemoteException { - return toTransactionResult(mHub.enableNanoapp(contextHubId, nanoappId, transactionId)); + try { + mHub.enableNanoapp(contextHubId, nanoappId, transactionId); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } } @ContextHubTransaction.Result public int disableNanoapp(int contextHubId, long nanoappId, int transactionId) throws RemoteException { - return toTransactionResult(mHub.disableNanoapp(contextHubId, nanoappId, transactionId)); + try { + mHub.disableNanoapp(contextHubId, nanoappId, transactionId); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } } @ContextHubTransaction.Result public int queryNanoapps(int contextHubId) throws RemoteException { - return toTransactionResult(mHub.queryNanoapps(contextHubId)); + try { + mHub.queryNanoapps(contextHubId); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } } public void registerCallback(int contextHubId, ICallback callback) throws RemoteException { @@ -494,12 +523,6 @@ public abstract class IContextHubWrapper { mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId)); } - @ContextHubTransaction.Result - private int toTransactionResult(boolean success) { - return success ? ContextHubTransaction.RESULT_SUCCESS - : ContextHubTransaction.RESULT_FAILED_UNKNOWN; - } - private void onSettingChanged(byte setting, boolean enabled) { try { mHub.onSettingChanged(setting, enabled); diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java index a52c9cefb27d..5093f5dee55e 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java @@ -131,8 +131,8 @@ public class GeofenceManager extends return mPermitted; } - boolean onLocationPermissionsChanged(String packageName) { - if (getIdentity().getPackageName().equals(packageName)) { + boolean onLocationPermissionsChanged(@Nullable String packageName) { + if (packageName == null || getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); } @@ -242,7 +242,7 @@ public class GeofenceManager extends mLocationPermissionsListener = new LocationPermissionsHelper.LocationPermissionsListener() { @Override - public void onLocationPermissionsChanged(String packageName) { + public void onLocationPermissionsChanged(@Nullable String packageName) { GeofenceManager.this.onLocationPermissionsChanged(packageName); } @@ -494,7 +494,7 @@ public class GeofenceManager extends updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - void onLocationPermissionsChanged(String packageName) { + void onLocationPermissionsChanged(@Nullable String packageName) { updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 5e6ae68c02f2..a54047665aba 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -119,8 +119,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter */ protected void onGnssListenerUnregister() {} - boolean onLocationPermissionsChanged(String packageName) { - if (getIdentity().getPackageName().equals(packageName)) { + boolean onLocationPermissionsChanged(@Nullable String packageName) { + if (packageName == null || getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); } @@ -197,7 +197,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mLocationPermissionsListener = new LocationPermissionsHelper.LocationPermissionsListener() { @Override - public void onLocationPermissionsChanged(String packageName) { + public void onLocationPermissionsChanged(@Nullable String packageName) { GnssListenerMultiplexer.this.onLocationPermissionsChanged(packageName); } @@ -390,7 +390,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - private void onLocationPermissionsChanged(String packageName) { + private void onLocationPermissionsChanged(@Nullable String packageName) { updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); } diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java index 2df21017156d..557ecda2ef4c 100644 --- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java +++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java @@ -18,6 +18,7 @@ package com.android.server.location.injector; import static com.android.server.location.LocationPermissions.PERMISSION_NONE; +import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; import com.android.server.location.LocationPermissions; @@ -36,9 +37,10 @@ public abstract class LocationPermissionsHelper { public interface LocationPermissionsListener { /** - * Called when something has changed about location permissions for the given package. + * Called when something has changed about location permissions for the given package. A + * null package indicates this affects every package. */ - void onLocationPermissionsChanged(String packageName); + void onLocationPermissionsChanged(@Nullable String packageName); /** * Called when something has changed about location permissions for the given uid. diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 1ba32ac1eec2..d42e2c63e80d 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -509,8 +509,8 @@ public class LocationProviderManager extends } @GuardedBy("mLock") - final boolean onLocationPermissionsChanged(String packageName) { - if (getIdentity().getPackageName().equals(packageName)) { + final boolean onLocationPermissionsChanged(@Nullable String packageName) { + if (packageName == null || getIdentity().getPackageName().equals(packageName)) { return onLocationPermissionsChanged(); } @@ -915,30 +915,19 @@ public class LocationProviderManager extends return null; } + // acquire a wakelock for non-passive requests + boolean useWakeLock = + getRequest().getIntervalMillis() != LocationRequest.PASSIVE_INTERVAL; + // deliver location return new ListenerOperation<LocationTransport>() { - private boolean mUseWakeLock; - @Override public void onPreExecute() { - mUseWakeLock = false; - - // don't acquire a wakelock for passive requests or for mock locations - if (getRequest().getIntervalMillis() != LocationRequest.PASSIVE_INTERVAL) { - final int size = locationResult.size(); - for (int i = 0; i < size; ++i) { - if (!locationResult.get(i).isMock()) { - mUseWakeLock = true; - break; - } - } - } - // update last delivered location setLastDeliveredLocation(locationResult.getLastLocation()); - if (mUseWakeLock) { + if (useWakeLock) { mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); } } @@ -955,14 +944,14 @@ public class LocationProviderManager extends } listener.deliverOnLocationChanged(deliverLocationResult, - mUseWakeLock ? mWakeLockReleaser : null); + useWakeLock ? mWakeLockReleaser : null); EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(), getIdentity()); } @Override public void onPostExecute(boolean success) { - if (!success && mUseWakeLock) { + if (!success && useWakeLock) { mWakeLock.release(); } @@ -1355,7 +1344,7 @@ public class LocationProviderManager extends private final LocationPermissionsListener mLocationPermissionsListener = new LocationPermissionsListener() { @Override - public void onLocationPermissionsChanged(String packageName) { + public void onLocationPermissionsChanged(@Nullable String packageName) { LocationProviderManager.this.onLocationPermissionsChanged(packageName); } @@ -2361,7 +2350,7 @@ public class LocationProviderManager extends } } - private void onLocationPermissionsChanged(String packageName) { + private void onLocationPermissionsChanged(@Nullable String packageName) { synchronized (mLock) { updateRegistrations( registration -> registration.onLocationPermissionsChanged(packageName)); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 20686322fd3d..755c50df1bef 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -595,6 +595,7 @@ public class NotificationManagerService extends SystemService { private ConditionProviders mConditionProviders; private NotificationUsageStats mUsageStats; private boolean mLockScreenAllowSecureNotifications = true; + boolean mAllowFgsDismissal = false; private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); @@ -1137,8 +1138,9 @@ public class NotificationManagerService extends SystemService { id = r.getSbn().getId(); } } + int mustNotHaveFlags = FLAG_ONGOING_EVENT; cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, + mustNotHaveFlags, true, userId, REASON_CANCEL, nv.rank, nv.count,null); nv.recycle(); } @@ -2411,7 +2413,7 @@ public class NotificationManagerService extends SystemService { publishLocalService(NotificationManagerInternal.class, mInternalService); } - private void registerDeviceConfigChange() { + void registerDeviceConfigChange() { mDeviceConfigChangedListener = properties -> { if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) { return; @@ -2440,9 +2442,19 @@ public class NotificationManagerService extends SystemService { } else if ("false".equals(value)) { mAssistants.disallowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION); } + } else if (SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED.equals(name)) { + String value = properties.getString(name, null); + if ("true".equals(value)) { + mAllowFgsDismissal = true; + } else if ("false".equals(value)) { + mAllowFgsDismissal = false; + } } } }; + mAllowFgsDismissal = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, false); DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_SYSTEMUI, new HandlerExecutor(mHandler), @@ -3343,8 +3355,7 @@ public class NotificationManagerService extends SystemService { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); - // Calling from user space, don't allow the canceling of actively - // running foreground services. + // Don't allow the app to cancel active FGS notifications cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(), pkg, null, 0, FLAG_FOREGROUND_SERVICE, true, userId, REASON_APP_CANCEL_ALL, null); @@ -4414,8 +4425,9 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { - cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, + int mustNotHaveFlags = FLAG_ONGOING_EVENT; + cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */, + mustNotHaveFlags, true, userId, REASON_LISTENER_CANCEL, info); } @@ -6132,12 +6144,11 @@ public class NotificationManagerService extends SystemService { return; } StatusBarNotification sbn = r.getSbn(); - // NoMan adds flags FLAG_NO_CLEAR and FLAG_ONGOING_EVENT when it sees + // NoMan adds flags FLAG_ONGOING_EVENT when it sees // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received // initially *and* force remove FLAG_FOREGROUND_SERVICE. - sbn.getNotification().flags = - (r.mOriginalFlags & ~FLAG_FOREGROUND_SERVICE); + sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_FOREGROUND_SERVICE); } }; @@ -6910,7 +6921,6 @@ public class NotificationManagerService extends SystemService { r.getKey(), true /* notifSuppressed */, isBubbleSuppressed); return; } - if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) { return; } @@ -6918,19 +6928,25 @@ public class NotificationManagerService extends SystemService { return; } - // Bubbled children get to stick around if the summary was manually cancelled - // (user removed) from systemui. - FlagChecker childrenFlagChecker = null; - if (mReason == REASON_CANCEL - || mReason == REASON_CLICK - || mReason == REASON_CANCEL_ALL) { - childrenFlagChecker = (flags) -> { - if ((flags & FLAG_BUBBLE) != 0) { + FlagChecker childrenFlagChecker = (flags) -> { + if (mReason == REASON_CANCEL + || mReason == REASON_CLICK + || mReason == REASON_CANCEL_ALL) { + // Bubbled children get to stick around if the summary was manually + // cancelled (user removed) from systemui. + if ((flags & FLAG_BUBBLE) != 0) { + return false; + } + } else if (mReason == REASON_APP_CANCEL) { + if ((flags & FLAG_FOREGROUND_SERVICE) != 0) { + return false; + } + } + if ((flags & mMustNotHaveFlags) != 0) { return false; } return true; }; - } // Cancel the notification. boolean wasPosted = removeFromNotificationListsLocked(r); @@ -7141,8 +7157,10 @@ public class NotificationManagerService extends SystemService { // Ensure if this is a foreground service that the proper additional // flags are set. if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= FLAG_ONGOING_EVENT - | FLAG_NO_CLEAR; + notification.flags |= FLAG_NO_CLEAR; + if (!mAllowFgsDismissal) { + notification.flags |= FLAG_ONGOING_EVENT; + } } mRankingHelper.extractSignals(r); @@ -7417,13 +7435,20 @@ public class NotificationManagerService extends SystemService { mSummaryByGroupKey.put(group, r); } + FlagChecker childrenFlagChecker = (flags) -> { + if ((flags & FLAG_FOREGROUND_SERVICE) != 0) { + return false; + } + return true; + }; + // Clear out group children of the old notification if the update // causes the group summary to go away. This happens when the old // notification was a summary and the new one isn't, or when the old // notification was a summary and its group key changed. if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) { cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */, - null, REASON_APP_CANCEL); + childrenFlagChecker, REASON_APP_CANCEL); } } @@ -9042,7 +9067,6 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification childSbn = childR.getSbn(); if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) && childR.getGroupKey().equals(parentNotification.getGroupKey()) - && (childR.getFlags() & FLAG_FOREGROUND_SERVICE) == 0 && (flagChecker == null || flagChecker.apply(childR.getFlags())) && (!childR.getChannel().isImportantConversation() || reason != REASON_CANCEL)) { diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index dd661306d687..fcf4a0279246 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -49,8 +49,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.utils.WatchedArrayMap; -import com.android.server.utils.WatchedLongSparseArray; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -309,10 +307,6 @@ public interface Computer { @Nullable String getRenamedPackage(@NonNull String packageName); - @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) - @NonNull - WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries(); - /** * @return set of packages to notify */ diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 2f4f2715f2b6..e37aaa5a9509 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -3490,9 +3490,8 @@ public class ComputerEngine implements Computer { return mSettings.getRenamedPackageLPr(packageName); } - @NonNull - @Override - public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() { + private WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> + getSharedLibraries() { return mSharedLibraries.getAll(); } @@ -4788,7 +4787,7 @@ public class ComputerEngine implements Computer { @Override public List<PackageStateInternal> findSharedNonSystemLibraries( @NonNull PackageStateInternal pkgSetting) { - List<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting); + List<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting); if (!deps.isEmpty()) { List<PackageStateInternal> retValue = new ArrayList<>(); for (SharedLibraryInfo info : deps) { diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java index bd730e904f7e..529aca3632ba 100644 --- a/services/core/java/com/android/server/pm/ComputerLocked.java +++ b/services/core/java/com/android/server/pm/ComputerLocked.java @@ -46,8 +46,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.utils.WatchedArrayMap; -import com.android.server.utils.WatchedLongSparseArray; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -269,14 +267,6 @@ public final class ComputerLocked extends ComputerEngine { @NonNull @Override - public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() { - synchronized (mLock) { - return super.getSharedLibraries(); - } - } - - @NonNull - @Override public ArraySet<String> getNotifyPackagesForReplacedReceived(@NonNull String[] packages) { synchronized (mLock) { return super.getNotifyPackagesForReplacedReceived(packages); diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java index e6ff836fce1c..52309ce45e30 100644 --- a/services/core/java/com/android/server/pm/ComputerTracker.java +++ b/services/core/java/com/android/server/pm/ComputerTracker.java @@ -47,8 +47,6 @@ import android.util.SparseArray; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.utils.WatchedArrayMap; -import com.android.server.utils.WatchedLongSparseArray; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -703,14 +701,6 @@ public final class ComputerTracker implements Computer { @NonNull @Override - public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() { - try (ThreadComputer current = snapshot()) { - return current.mComputer.getSharedLibraries(); - } - } - - @NonNull - @Override public ArraySet<String> getNotifyPackagesForReplacedReceived(@NonNull String[] packages) { try (ThreadComputer current = snapshot()) { return current.mComputer.getNotifyPackagesForReplacedReceived(packages); diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index eac38af57784..dcad3ecba6af 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -379,7 +379,7 @@ final class DexOptHelper { // at boot, or background job), the passed 'targetCompilerFilter' stays the same, // and the first package that uses the library will dexopt it. The // others will see that the compiled code for the library is up to date. - Collection<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting); + Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting); final String[] instructionSets = getAppDexInstructionSets( AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting), AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting)); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 27b6282055a2..9302aaddcdb2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -894,8 +894,6 @@ final class InstallPackageHelper { final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size()); final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size()); final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size()); - final Map<String, PackageSetting> lastStaticSharedLibSettings = - new ArrayMap<>(requests.size()); final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size()); boolean success = false; try { @@ -955,35 +953,22 @@ final class InstallPackageHelper { createdAppId.put(packageName, optimisticallyRegisterAppId(result)); versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); - if (result.mStaticSharedLibraryInfo != null) { - final PackageSetting staticSharedLibLatestVersionSetting = - mSharedLibraries.getStaticSharedLibLatestVersionSetting(result); - if (staticSharedLibLatestVersionSetting != null) { - lastStaticSharedLibSettings.put( - result.mPkgSetting.getPkg().getPackageName(), - staticSharedLibLatestVersionSetting); - } - } } catch (PackageManagerException e) { request.mInstallResult.setError("Scanning Failed.", e); return; } } - ReconcileRequest - reconcileRequest = new ReconcileRequest(preparedScans, installArgs, - installResults, - prepareResults, - mSharedLibraries.getAll(), - Collections.unmodifiableMap(mPm.mPackages), versionInfos, - lastStaticSharedLibSettings); + ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs, + installResults, prepareResults, + Collections.unmodifiableMap(mPm.mPackages), versionInfos); CommitRequest commitRequest = null; synchronized (mPm.mLock) { Map<String, ReconciledPackage> reconciledPackages; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages"); reconciledPackages = ReconcilePackageUtils.reconcilePackages( - reconcileRequest, mPm.mSettings.getKeySetManagerService(), - mPm.mInjector); + reconcileRequest, mSharedLibraries, + mPm.mSettings.getKeySetManagerService()); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.mInstallResult.setError("Reconciliation failed...", e); @@ -3586,15 +3571,12 @@ final class InstallPackageHelper { final String pkgName = scanResult.mPkgSetting.getPackageName(); final ReconcileRequest reconcileRequest = new ReconcileRequest( Collections.singletonMap(pkgName, scanResult), - mSharedLibraries.getAll(), mPm.mPackages, - Collections.singletonMap(pkgName, - mPm.getSettingsVersionForPackage(parsedPackage)), + mPm.mPackages, Collections.singletonMap(pkgName, - mSharedLibraries.getStaticSharedLibLatestVersionSetting( - scanResult))); + mPm.getSettingsVersionForPackage(parsedPackage))); final Map<String, ReconciledPackage> reconcileResult = ReconcilePackageUtils.reconcilePackages(reconcileRequest, - mPm.mSettings.getKeySetManagerService(), mPm.mInjector); + mSharedLibraries, mPm.mSettings.getKeySetManagerService()); appIdCreated = optimisticallyRegisterAppId(scanResult); commitReconciledScanResultLocked(reconcileResult.get(pkgName), mPm.mUserManager.getUserIds()); diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 1bdc9f3cf850..c219f80ac9c5 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -40,7 +40,7 @@ per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com per-file KeySetManagerService.java = cbrubaker@google.com, nnk@google.com per-file PackageKeySetData.java = cbrubaker@google.com, nnk@google.com per-file PackageSignatures.java = cbrubaker@google.com, nnk@google.com -per-file SELinuxMMAC* = alanstokes@google.com, cbrubaker@google.com, jeffv@google.com, jgalenson@google.com +per-file SELinuxMMAC* = alanstokes@google.com, cbrubaker@google.com, jeffv@google.com # shortcuts per-file LauncherAppsService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 5a250047da8f..67f6b123d99b 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -42,8 +42,8 @@ import java.util.Map; final class ReconcilePackageUtils { public static Map<String, ReconciledPackage> reconcilePackages( - final ReconcileRequest request, KeySetManagerService ksms, - PackageManagerServiceInjector injector) + final ReconcileRequest request, SharedLibrariesImpl sharedLibraries, + KeySetManagerService ksms) throws ReconcileFailure { final Map<String, ScanResult> scannedPackages = request.mScannedPackages; @@ -67,11 +67,10 @@ final class ReconcilePackageUtils { // in the first pass, we'll build up the set of incoming shared libraries final List<SharedLibraryInfo> allowedSharedLibInfos = - SharedLibraryHelper.getAllowedSharedLibInfos(scanResult, - request.mSharedLibrarySource); + sharedLibraries.getAllowedSharedLibInfos(scanResult); if (allowedSharedLibInfos != null) { for (SharedLibraryInfo info : allowedSharedLibInfos) { - if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap( + if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap( incomingSharedLibraries, info)) { throw new ReconcileFailure("Shared Library " + info.getName() + " is being installed twice in this set!"); @@ -113,7 +112,8 @@ final class ReconcilePackageUtils { final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting; final PackageSetting lastStaticSharedLibSetting = - request.mLastStaticSharedLibSettings.get(installPackageName); + scanResult.mStaticSharedLibraryInfo == null ? null + : sharedLibraries.getStaticSharedLibLatestVersionSetting(scanResult); final PackageSetting signatureCheckPs = (prepareResult != null && lastStaticSharedLibSetting != null) ? lastStaticSharedLibSetting @@ -264,11 +264,9 @@ final class ReconcilePackageUtils { } try { result.get(installPackageName).mCollectedSharedLibraryInfos = - SharedLibraryHelper.collectSharedLibraryInfos( - scanResult.mRequest.mParsedPackage, - combinedPackages, request.mSharedLibrarySource, - incomingSharedLibraries, injector.getCompatibility()); - + sharedLibraries.collectSharedLibraryInfos( + scanResult.mRequest.mParsedPackage, combinedPackages, + incomingSharedLibraries); } catch (PackageManagerException e) { throw new ReconcileFailure(e.error, e.getMessage()); } diff --git a/services/core/java/com/android/server/pm/ReconcileRequest.java b/services/core/java/com/android/server/pm/ReconcileRequest.java index 31881388e6ec..9e4e986d1fb5 100644 --- a/services/core/java/com/android/server/pm/ReconcileRequest.java +++ b/services/core/java/com/android/server/pm/ReconcileRequest.java @@ -16,10 +16,7 @@ package com.android.server.pm; -import android.content.pm.SharedLibraryInfo; - import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.utils.WatchedLongSparseArray; import java.util.Collections; import java.util.Map; @@ -37,38 +34,29 @@ final class ReconcileRequest { public final Map<String, ScanResult> mScannedPackages; public final Map<String, AndroidPackage> mAllPackages; - public final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> mSharedLibrarySource; public final Map<String, InstallArgs> mInstallArgs; public final Map<String, PackageInstalledInfo> mInstallResults; public final Map<String, PrepareResult> mPreparedPackages; public final Map<String, Settings.VersionInfo> mVersionInfos; - public final Map<String, PackageSetting> mLastStaticSharedLibSettings; ReconcileRequest(Map<String, ScanResult> scannedPackages, Map<String, InstallArgs> installArgs, Map<String, PackageInstalledInfo> installResults, Map<String, PrepareResult> preparedPackages, - Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource, Map<String, AndroidPackage> allPackages, - Map<String, Settings.VersionInfo> versionInfos, - Map<String, PackageSetting> lastStaticSharedLibSettings) { + Map<String, Settings.VersionInfo> versionInfos) { mScannedPackages = scannedPackages; mInstallArgs = installArgs; mInstallResults = installResults; mPreparedPackages = preparedPackages; - mSharedLibrarySource = sharedLibrarySource; mAllPackages = allPackages; mVersionInfos = versionInfos; - mLastStaticSharedLibSettings = lastStaticSharedLibSettings; } ReconcileRequest(Map<String, ScanResult> scannedPackages, - Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource, Map<String, AndroidPackage> allPackages, - Map<String, Settings.VersionInfo> versionInfos, - Map<String, PackageSetting> lastStaticSharedLibSettings) { + Map<String, Settings.VersionInfo> versionInfos) { this(scannedPackages, Collections.emptyMap(), Collections.emptyMap(), - Collections.emptyMap(), sharedLibrarySource, allPackages, versionInfos, - lastStaticSharedLibSettings); + Collections.emptyMap(), allPackages, versionInfos); } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 21df5a9c1a45..7085682662e6 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4863,11 +4863,11 @@ public final class Settings implements Watchable, Snappable { pw.print(userState.isInstantApp()); pw.print(" virtual="); pw.println(userState.isVirtualPreload()); - pw.print(" installReason="); + pw.print(" installReason="); pw.println(userState.getInstallReason()); final PackageUserStateInternal pus = ps.readUserState(user.id); - pw.print(" firstInstallTime="); + pw.print(" firstInstallTime="); date.setTime(pus.getFirstInstallTime()); pw.println(sdf.format(date)); diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 0055f4eb6446..aa230508287e 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -16,19 +16,27 @@ package com.android.server.pm; +import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; + import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; +import android.content.pm.Signature; +import android.content.pm.SigningDetails; import android.content.pm.VersionedPackage; +import android.os.Build; import android.os.Process; import android.os.UserHandle; import android.os.storage.StorageManager; import android.service.pm.PackageServiceDumpProto; import android.util.ArraySet; +import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -36,8 +44,10 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; +import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; @@ -47,10 +57,13 @@ import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedLongSparseArray; import com.android.server.utils.Watcher; +import libcore.util.HexEncoding; + import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -62,6 +75,24 @@ import java.util.function.BiConsumer; * Current known shared libraries on the device. */ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable { + private static final boolean DEBUG_SHARED_LIBRARIES = false; + + /** + * Apps targeting Android S and above need to declare dependencies to the public native + * shared libraries that are defined by the device maker using {@code uses-native-library} tag + * in its {@code AndroidManifest.xml}. + * + * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist, + * the package manager rejects to install the app. The dependency can be specified as optional + * using {@code android:required} attribute in the tag, in which case failing to satisfy the + * dependency doesn't stop the installation. + * <p>Once installed, an app is provided with only the native shared libraries that are + * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear + * in the app manifest will fail even if it actually exists on the device. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088; // TODO(b/200588896): remove PMS dependency private final PackageManagerService mPm; @@ -493,10 +524,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting, @NonNull Map<String, AndroidPackage> availablePackages) throws PackageManagerException { - final ArrayList<SharedLibraryInfo> sharedLibraryInfos = - SharedLibraryHelper.collectSharedLibraryInfos( - pkgSetting.getPkg(), availablePackages, mSharedLibraries, - null /* newLibraries */, mInjector.getCompatibility()); + final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos( + pkgSetting.getPkg(), availablePackages, null /* newLibraries */); executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting, sharedLibraryInfos, mPm.mUserManager.getUserIds()); } @@ -735,6 +764,234 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable } /** + * Compare the newly scanned package with current system state to see which of its declared + * shared libraries should be allowed to be added to the system. + */ + List<SharedLibraryInfo> getAllowedSharedLibInfos(ScanResult scanResult) { + // Let's used the parsed package as scanResult.pkgSetting may be null + final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage; + if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null + && scanResult.mDynamicSharedLibraryInfos == null) { + return null; + } + + // Any app can add new SDKs and static shared libraries. + if (scanResult.mSdkSharedLibraryInfo != null) { + return Collections.singletonList(scanResult.mSdkSharedLibraryInfo); + } + if (scanResult.mStaticSharedLibraryInfo != null) { + return Collections.singletonList(scanResult.mStaticSharedLibraryInfo); + } + final boolean hasDynamicLibraries = parsedPackage.isSystem() + && scanResult.mDynamicSharedLibraryInfos != null; + if (!hasDynamicLibraries) { + return null; + } + final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState() + .isUpdatedSystemApp(); + // We may not yet have disabled the updated package yet, so be sure to grab the + // current setting if that's the case. + final PackageSetting updatedSystemPs = isUpdatedSystemApp + ? scanResult.mRequest.mDisabledPkgSetting == null + ? scanResult.mRequest.mOldPkgSetting + : scanResult.mRequest.mDisabledPkgSetting + : null; + if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null + || updatedSystemPs.getPkg().getLibraryNames() == null)) { + Slog.w(TAG, "Package " + parsedPackage.getPackageName() + + " declares libraries that are not declared on the system image; skipping"); + return null; + } + final ArrayList<SharedLibraryInfo> infos = + new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size()); + for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) { + final String name = info.getName(); + if (isUpdatedSystemApp) { + // New library entries can only be added through the + // system image. This is important to get rid of a lot + // of nasty edge cases: for example if we allowed a non- + // system update of the app to add a library, then uninstalling + // the update would make the library go away, and assumptions + // we made such as through app install filtering would now + // have allowed apps on the device which aren't compatible + // with it. Better to just have the restriction here, be + // conservative, and create many fewer cases that can negatively + // impact the user experience. + if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) { + Slog.w(TAG, "Package " + parsedPackage.getPackageName() + + " declares library " + name + + " that is not declared on system image; skipping"); + continue; + } + } + synchronized (mPm.mLock) { + if (getSharedLibraryInfo(name, SharedLibraryInfo.VERSION_UNDEFINED) != null) { + Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library " + + name + " that already exists; skipping"); + continue; + } + } + infos.add(info); + } + return infos; + } + + /** + * Collects shared library infos that are being used by the given package. + * + * @param pkg The package using shared libraries. + * @param availablePackages The available packages which are installed and being installed, + * @param newLibraries Shared libraries defined by packages which are being installed. + * @return A list of shared library infos + */ + ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(@Nullable AndroidPackage pkg, + @NonNull Map<String, AndroidPackage> availablePackages, + @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) + throws PackageManagerException { + if (pkg == null) { + return null; + } + final PlatformCompat platformCompat = mInjector.getCompatibility(); + // The collection used here must maintain the order of addition (so + // that libraries are searched in the correct order) and must have no + // duplicates. + ArrayList<SharedLibraryInfo> usesLibraryInfos = null; + if (!pkg.getUsesLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, + pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null, + availablePackages, newLibraries); + } + if (!pkg.getUsesStaticLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(), + pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(), + pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, newLibraries); + } + if (!pkg.getUsesOptionalLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, + pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, newLibraries); + } + if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES, + pkg.getPackageName(), pkg.getTargetSdkVersion())) { + if (!pkg.getUsesNativeLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, + null, pkg.getPackageName(), "native shared", true, + pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, + newLibraries); + } + if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), + null, null, pkg.getPackageName(), "native shared", false, + pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, + newLibraries); + } + } + if (!pkg.getUsesSdkLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(), + pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(), + pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos, + availablePackages, newLibraries); + } + return usesLibraryInfos; + } + + private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos( + @NonNull List<String> requestedLibraries, + @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests, + @NonNull String packageName, @NonNull String libraryType, boolean required, + int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries, + @NonNull final Map<String, AndroidPackage> availablePackages, + @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) + throws PackageManagerException { + final int libCount = requestedLibraries.size(); + for (int i = 0; i < libCount; i++) { + final String libName = requestedLibraries.get(i); + final long libVersion = requiredVersions != null ? requiredVersions[i] + : SharedLibraryInfo.VERSION_UNDEFINED; + final SharedLibraryInfo libraryInfo; + synchronized (mPm.mLock) { + libraryInfo = SharedLibraryUtils.getSharedLibraryInfo( + libName, libVersion, mSharedLibraries, newLibraries); + } + if (libraryInfo == null) { + if (required) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires unavailable " + libraryType + + " library " + libName + "; failing!"); + } else if (DEBUG_SHARED_LIBRARIES) { + Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType + + " library " + libName + "; ignoring!"); + } + } else { + if (requiredVersions != null && requiredCertDigests != null) { + if (libraryInfo.getLongVersion() != requiredVersions[i]) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires unavailable " + libraryType + + " library " + libName + " version " + + libraryInfo.getLongVersion() + "; failing!"); + } + AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName()); + SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails(); + if (libPkg == null) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires unavailable " + libraryType + + " library; failing!"); + } + final String[] expectedCertDigests = requiredCertDigests[i]; + if (expectedCertDigests.length > 1) { + // For apps targeting O MR1 we require explicit enumeration of all certs. + final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1) + ? PackageUtils.computeSignaturesSha256Digests( + libPkg.getSignatures()) + : PackageUtils.computeSignaturesSha256Digests( + new Signature[]{libPkg.getSignatures()[0]}); + + // Take a shortcut if sizes don't match. Note that if an app doesn't + // target O we don't parse the "additional-certificate" tags similarly + // how we only consider all certs only for apps targeting O (see above). + // Therefore, the size check is safe to make. + if (expectedCertDigests.length != libCertDigests.length) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed " + + libraryType + " library; failing!"); + } + + // Use a predictable order as signature order may vary + Arrays.sort(libCertDigests); + Arrays.sort(expectedCertDigests); + + final int certCount = libCertDigests.length; + for (int j = 0; j < certCount; j++) { + if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { + throw new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed " + + libraryType + " library; failing!"); + } + } + } else { + // lib signing cert could have rotated beyond the one expected, check to see + // if the new one has been blessed by the old + byte[] digestBytes = HexEncoding.decode( + expectedCertDigests[0], false /* allowSingleChar */); + if (!libPkg.hasSha256Certificate(digestBytes)) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed " + + libraryType + " library; failing!"); + } + } + } + if (outUsedLibraries == null) { + outUsedLibraries = new ArrayList<>(); + } + outUsedLibraries.add(libraryInfo); + } + } + return outUsedLibraries; + } + + /** * Dump all shared libraries. */ @GuardedBy("mPm.mLock") diff --git a/services/core/java/com/android/server/pm/SharedLibraryHelper.java b/services/core/java/com/android/server/pm/SharedLibraryHelper.java deleted file mode 100644 index dd8fad0970fa..000000000000 --- a/services/core/java/com/android/server/pm/SharedLibraryHelper.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; - -import static com.android.server.pm.PackageManagerService.TAG; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; -import android.content.pm.SharedLibraryInfo; -import android.content.pm.Signature; -import android.content.pm.SigningDetails; -import android.os.Build; -import android.util.PackageUtils; -import android.util.Slog; - -import com.android.server.compat.PlatformCompat; -import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.utils.WatchedLongSparseArray; - -import libcore.util.HexEncoding; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -final class SharedLibraryHelper { - private static final boolean DEBUG_SHARED_LIBRARIES = false; - - /** - * Apps targeting Android S and above need to declare dependencies to the public native - * shared libraries that are defined by the device maker using {@code uses-native-library} tag - * in its {@code AndroidManifest.xml}. - * - * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist, - * the package manager rejects to install the app. The dependency can be specified as optional - * using {@code android:required} attribute in the tag, in which case failing to satisfy the - * dependency doesn't stop the installation. - * <p>Once installed, an app is provided with only the native shared libraries that are - * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear - * in the app manifest will fail even if it actually exists on the device. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) - private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088; - - /** - * Compare the newly scanned package with current system state to see which of its declared - * shared libraries should be allowed to be added to the system. - */ - public static List<SharedLibraryInfo> getAllowedSharedLibInfos( - ScanResult scanResult, - Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) { - // Let's used the parsed package as scanResult.pkgSetting may be null - final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage; - if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null - && scanResult.mDynamicSharedLibraryInfos == null) { - return null; - } - - // Any app can add new SDKs and static shared libraries. - if (scanResult.mSdkSharedLibraryInfo != null) { - return Collections.singletonList(scanResult.mSdkSharedLibraryInfo); - } - if (scanResult.mStaticSharedLibraryInfo != null) { - return Collections.singletonList(scanResult.mStaticSharedLibraryInfo); - } - final boolean hasDynamicLibraries = parsedPackage.isSystem() - && scanResult.mDynamicSharedLibraryInfos != null; - if (!hasDynamicLibraries) { - return null; - } - final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState() - .isUpdatedSystemApp(); - // We may not yet have disabled the updated package yet, so be sure to grab the - // current setting if that's the case. - final PackageSetting updatedSystemPs = isUpdatedSystemApp - ? scanResult.mRequest.mDisabledPkgSetting == null - ? scanResult.mRequest.mOldPkgSetting - : scanResult.mRequest.mDisabledPkgSetting - : null; - if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null - || updatedSystemPs.getPkg().getLibraryNames() == null)) { - Slog.w(TAG, "Package " + parsedPackage.getPackageName() - + " declares libraries that are not declared on the system image; skipping"); - return null; - } - final ArrayList<SharedLibraryInfo> infos = - new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size()); - for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) { - final String name = info.getName(); - if (isUpdatedSystemApp) { - // New library entries can only be added through the - // system image. This is important to get rid of a lot - // of nasty edge cases: for example if we allowed a non- - // system update of the app to add a library, then uninstalling - // the update would make the library go away, and assumptions - // we made such as through app install filtering would now - // have allowed apps on the device which aren't compatible - // with it. Better to just have the restriction here, be - // conservative, and create many fewer cases that can negatively - // impact the user experience. - if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) { - Slog.w(TAG, "Package " + parsedPackage.getPackageName() - + " declares library " + name - + " that is not declared on system image; skipping"); - continue; - } - } - if (sharedLibExists( - name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) { - Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library " - + name + " that already exists; skipping"); - continue; - } - infos.add(info); - } - return infos; - } - - public static boolean sharedLibExists(final String name, final long version, - Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) { - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name); - return versionedLib != null && versionedLib.indexOfKey(version) >= 0; - } - - /** - * Returns false if the adding shared library already exists in the map and so could not be - * added. - */ - public static boolean addSharedLibraryToPackageVersionMap( - Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target, - SharedLibraryInfo library) { - final String name = library.getName(); - if (target.containsKey(name)) { - if (library.getType() != SharedLibraryInfo.TYPE_STATIC) { - // We've already added this non-version-specific library to the map. - return false; - } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) { - // We've already added this version of a version-specific library to the map. - return false; - } - } else { - target.put(name, new WatchedLongSparseArray<>()); - } - target.get(name).put(library.getLongVersion(), library); - return true; - } - - public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg, - Map<String, AndroidPackage> availablePackages, - @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries, - @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries, - PlatformCompat platformCompat) throws PackageManagerException { - if (pkg == null) { - return null; - } - // The collection used here must maintain the order of addition (so - // that libraries are searched in the correct order) and must have no - // duplicates. - ArrayList<SharedLibraryInfo> usesLibraryInfos = null; - if (!pkg.getUsesLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, - pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null, - availablePackages, existingLibraries, newLibraries); - } - if (!pkg.getUsesStaticLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(), - pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(), - pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(), - usesLibraryInfos, availablePackages, existingLibraries, newLibraries); - } - if (!pkg.getUsesOptionalLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, - pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), - usesLibraryInfos, availablePackages, existingLibraries, newLibraries); - } - if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES, - pkg.getPackageName(), pkg.getTargetSdkVersion())) { - if (!pkg.getUsesNativeLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, - null, pkg.getPackageName(), "native shared", true, - pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, - existingLibraries, newLibraries); - } - if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), - null, null, pkg.getPackageName(), "native shared", false, - pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, - existingLibraries, newLibraries); - } - } - if (!pkg.getUsesSdkLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(), - pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(), - pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos, - availablePackages, existingLibraries, newLibraries); - } - return usesLibraryInfos; - } - - public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos( - @NonNull List<String> requestedLibraries, - @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests, - @NonNull String packageName, @NonNull String libraryType, boolean required, - int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries, - @NonNull final Map<String, AndroidPackage> availablePackages, - @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries, - @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) - throws PackageManagerException { - final int libCount = requestedLibraries.size(); - for (int i = 0; i < libCount; i++) { - final String libName = requestedLibraries.get(i); - final long libVersion = requiredVersions != null ? requiredVersions[i] - : SharedLibraryInfo.VERSION_UNDEFINED; - final SharedLibraryInfo libraryInfo = - getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries); - if (libraryInfo == null) { - if (required) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires unavailable " + libraryType - + " library " + libName + "; failing!"); - } else if (DEBUG_SHARED_LIBRARIES) { - Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType - + " library " + libName + "; ignoring!"); - } - } else { - if (requiredVersions != null && requiredCertDigests != null) { - if (libraryInfo.getLongVersion() != requiredVersions[i]) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires unavailable " + libraryType - + " library " + libName + " version " - + libraryInfo.getLongVersion() + "; failing!"); - } - AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName()); - SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails(); - if (libPkg == null) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires unavailable " + libraryType - + " library; failing!"); - } - final String[] expectedCertDigests = requiredCertDigests[i]; - if (expectedCertDigests.length > 1) { - // For apps targeting O MR1 we require explicit enumeration of all certs. - final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1) - ? PackageUtils.computeSignaturesSha256Digests( - libPkg.getSignatures()) - : PackageUtils.computeSignaturesSha256Digests( - new Signature[]{libPkg.getSignatures()[0]}); - - // Take a shortcut if sizes don't match. Note that if an app doesn't - // target O we don't parse the "additional-certificate" tags similarly - // how we only consider all certs only for apps targeting O (see above). - // Therefore, the size check is safe to make. - if (expectedCertDigests.length != libCertDigests.length) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed " - + libraryType + " library; failing!"); - } - - // Use a predictable order as signature order may vary - Arrays.sort(libCertDigests); - Arrays.sort(expectedCertDigests); - - final int certCount = libCertDigests.length; - for (int j = 0; j < certCount; j++) { - if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { - throw new PackageManagerException( - INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed " - + libraryType + " library; failing!"); - } - } - } else { - // lib signing cert could have rotated beyond the one expected, check to see - // if the new one has been blessed by the old - byte[] digestBytes = HexEncoding.decode( - expectedCertDigests[0], false /* allowSingleChar */); - if (!libPkg.hasSha256Certificate(digestBytes)) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed " - + libraryType + " library; failing!"); - } - } - } - if (outUsedLibraries == null) { - outUsedLibraries = new ArrayList<>(); - } - outUsedLibraries.add(libraryInfo); - } - } - return outUsedLibraries; - } - - @Nullable - public static SharedLibraryInfo getSharedLibraryInfo(String name, long version, - Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries, - @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) { - if (newLibraries != null) { - final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name); - SharedLibraryInfo info = null; - if (versionedLib != null) { - info = versionedLib.get(version); - } - if (info != null) { - return info; - } - } - final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name); - if (versionedLib == null) { - return null; - } - return versionedLib.get(version); - } - - public static List<SharedLibraryInfo> findSharedLibraries(PackageStateInternal pkgSetting) { - if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) { - ArrayList<SharedLibraryInfo> retValue = new ArrayList<>(); - Set<String> collectedNames = new HashSet<>(); - for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) { - findSharedLibrariesRecursive(info, retValue, collectedNames); - } - return retValue; - } else { - return Collections.emptyList(); - } - } - - private static void findSharedLibrariesRecursive(SharedLibraryInfo info, - ArrayList<SharedLibraryInfo> collected, Set<String> collectedNames) { - if (!collectedNames.contains(info.getName())) { - collectedNames.add(info.getName()); - collected.add(info); - - if (info.getDependencies() != null) { - for (SharedLibraryInfo dep : info.getDependencies()) { - findSharedLibrariesRecursive(dep, collected, collectedNames); - } - } - } - } - -} diff --git a/services/core/java/com/android/server/pm/SharedLibraryUtils.java b/services/core/java/com/android/server/pm/SharedLibraryUtils.java new file mode 100644 index 000000000000..274870dad6f0 --- /dev/null +++ b/services/core/java/com/android/server/pm/SharedLibraryUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.annotation.Nullable; +import android.content.pm.SharedLibraryInfo; + +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.utils.WatchedLongSparseArray; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +final class SharedLibraryUtils { + + /** + * Returns false if the adding shared library already exists in the map and so could not be + * added. + */ + public static boolean addSharedLibraryToPackageVersionMap( + Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target, + SharedLibraryInfo library) { + final String name = library.getName(); + if (target.containsKey(name)) { + if (library.getType() != SharedLibraryInfo.TYPE_STATIC) { + // We've already added this non-version-specific library to the map. + return false; + } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) { + // We've already added this version of a version-specific library to the map. + return false; + } + } else { + target.put(name, new WatchedLongSparseArray<>()); + } + target.get(name).put(library.getLongVersion(), library); + return true; + } + + @Nullable + public static SharedLibraryInfo getSharedLibraryInfo(String name, long version, + Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries, + @Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) { + if (newLibraries != null) { + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name); + SharedLibraryInfo info = null; + if (versionedLib != null) { + info = versionedLib.get(version); + } + if (info != null) { + return info; + } + } + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name); + if (versionedLib == null) { + return null; + } + return versionedLib.get(version); + } + + public static List<SharedLibraryInfo> findSharedLibraries(PackageStateInternal pkgSetting) { + if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) { + ArrayList<SharedLibraryInfo> retValue = new ArrayList<>(); + Set<String> collectedNames = new HashSet<>(); + for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) { + findSharedLibrariesRecursive(info, retValue, collectedNames); + } + return retValue; + } else { + return Collections.emptyList(); + } + } + + private static void findSharedLibrariesRecursive(SharedLibraryInfo info, + ArrayList<SharedLibraryInfo> collected, Set<String> collectedNames) { + if (!collectedNames.contains(info.getName())) { + collectedNames.add(info.getName()); + collected.add(info); + + if (info.getDependencies() != null) { + for (SharedLibraryInfo dep : info.getDependencies()) { + findSharedLibrariesRecursive(dep, collected, collectedNames); + } + } + } + } + +} diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index b15e495a3714..af524db74341 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -217,6 +217,11 @@ final class DefaultPermissionGrantPolicy { NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.NEARBY_WIFI_DEVICES); } + private static final Set<String> NOTIFICATION_PERMISSIONS = new ArraySet<>(); + static { + NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS); + } + private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1; private static final String ACTION_TRACK = "com.android.fitness.TRACK"; @@ -378,18 +383,43 @@ final class DefaultPermissionGrantPolicy { grantPermissionsToSysComponentsAndPrivApps(pm, userId); grantDefaultSystemHandlerPermissions(pm, userId); + grantSignatureAppsNotificationPermissions(pm, userId); grantDefaultPermissionExceptions(pm, userId); // Apply delayed state pm.apply(); } + private void grantSignatureAppsNotificationPermissions(PackageManagerWrapper pm, int userId) { + Log.i(TAG, "Granting Notification permissions to platform signature apps for user " + + userId); + List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser( + DEFAULT_PACKAGE_INFO_QUERY_FLAGS, UserHandle.USER_SYSTEM); + for (PackageInfo pkg : packages) { + if (pkg == null || !pkg.applicationInfo.isSystemApp() + || !pkg.applicationInfo.isSignedWithPlatformKey()) { + continue; + } + grantRuntimePermissionsForSystemPackage(pm, userId, pkg, NOTIFICATION_PERMISSIONS); + } + + } + private void grantRuntimePermissionsForSystemPackage(PackageManagerWrapper pm, int userId, PackageInfo pkg) { + grantRuntimePermissionsForSystemPackage(pm, userId, pkg, null); + } + + private void grantRuntimePermissionsForSystemPackage(PackageManagerWrapper pm, + int userId, PackageInfo pkg, Set<String> filterPermissions) { + if (ArrayUtils.isEmpty(pkg.requestedPermissions)) { + return; + } Set<String> permissions = new ArraySet<>(); for (String permission : pkg.requestedPermissions) { final PermissionInfo perm = pm.getPermissionInfo(permission); - if (perm == null) { + if (perm == null + || (filterPermissions != null && !filterPermissions.contains(permission))) { continue; } if (perm.isRuntime()) { @@ -547,23 +577,31 @@ final class DefaultPermissionGrantPolicy { String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ? syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null; + // PermissionController + grantSystemFixedPermissionsToSystemPackage(pm, + mContext.getPackageManager().getPermissionControllerPackageName(), userId, + NOTIFICATION_PERMISSIONS); + // Installer grantSystemFixedPermissionsToSystemPackage(pm, ArrayUtils.firstOrNull(getKnownPackages( PackageManagerInternal.PACKAGE_INSTALLER, userId)), - userId, STORAGE_PERMISSIONS); + userId, STORAGE_PERMISSIONS, NOTIFICATION_PERMISSIONS); // Verifier final String verifier = ArrayUtils.firstOrNull(getKnownPackages( PackageManagerInternal.PACKAGE_VERIFIER, userId)); grantSystemFixedPermissionsToSystemPackage(pm, verifier, userId, STORAGE_PERMISSIONS); - grantPermissionsToSystemPackage(pm, verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS); + grantPermissionsToSystemPackage(pm, verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS, + NOTIFICATION_PERMISSIONS); // SetupWizard final String setupWizardPackage = ArrayUtils.firstOrNull(getKnownPackages( PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)); grantPermissionsToSystemPackage(pm, setupWizardPackage, userId, PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, CAMERA_PERMISSIONS); + grantSystemFixedPermissionsToSystemPackage(pm, setupWizardPackage, userId, + NOTIFICATION_PERMISSIONS); if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0) || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) { @@ -585,12 +623,12 @@ final class DefaultPermissionGrantPolicy { // Media provider grantSystemFixedPermissionsToSystemPackage(pm, getDefaultProviderAuthorityPackage(MediaStore.AUTHORITY, userId), userId, - STORAGE_PERMISSIONS); + STORAGE_PERMISSIONS, NOTIFICATION_PERMISSIONS); // Downloads provider grantSystemFixedPermissionsToSystemPackage(pm, getDefaultProviderAuthorityPackage("downloads", userId), userId, - STORAGE_PERMISSIONS); + STORAGE_PERMISSIONS, NOTIFICATION_PERMISSIONS); // Downloads UI grantSystemFixedPermissionsToSystemPackage(pm, @@ -649,7 +687,7 @@ final class DefaultPermissionGrantPolicy { // Cell Broadcast Receiver grantSystemFixedPermissionsToSystemPackage(pm, getDefaultSystemHandlerActivityPackage(pm, Intents.SMS_CB_RECEIVED_ACTION, userId), - userId, SMS_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS); + userId, SMS_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS); // Carrier Provisioning Service grantPermissionsToSystemPackage(pm, @@ -661,7 +699,7 @@ final class DefaultPermissionGrantPolicy { grantPermissionsToSystemPackage(pm, getDefaultSystemHandlerActivityPackageForCategory(pm, Intent.CATEGORY_APP_CALENDAR, userId), - userId, CALENDAR_PERMISSIONS, CONTACTS_PERMISSIONS); + userId, CALENDAR_PERMISSIONS, CONTACTS_PERMISSIONS, NOTIFICATION_PERMISSIONS); // Calendar provider String calendarProvider = @@ -762,7 +800,8 @@ final class DefaultPermissionGrantPolicy { grantPermissionsToSystemPackage(pm, packageName, userId, CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS, PHONE_PERMISSIONS, SMS_PERMISSIONS, CAMERA_PERMISSIONS, - SENSORS_PERMISSIONS, STORAGE_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS); + SENSORS_PERMISSIONS, STORAGE_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS, + NOTIFICATION_PERMISSIONS); grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId, ALWAYS_LOCATION_PERMISSIONS, ACTIVITY_RECOGNITION_PERMISSIONS); } @@ -791,7 +830,7 @@ final class DefaultPermissionGrantPolicy { .addCategory(Intent.CATEGORY_LAUNCHER_APP); grantPermissionsToSystemPackage(pm, getDefaultSystemHandlerActivityPackage(pm, homeIntent, userId), userId, - ALWAYS_LOCATION_PERMISSIONS); + ALWAYS_LOCATION_PERMISSIONS, NOTIFICATION_PERMISSIONS); // Watches if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) { @@ -816,7 +855,7 @@ final class DefaultPermissionGrantPolicy { // Print Spooler grantSystemFixedPermissionsToSystemPackage(pm, PrintManager.PRINT_SPOOLER_PACKAGE_NAME, - userId, ALWAYS_LOCATION_PERMISSIONS); + userId, ALWAYS_LOCATION_PERMISSIONS, NOTIFICATION_PERMISSIONS); // EmergencyInfo grantSystemFixedPermissionsToSystemPackage(pm, @@ -920,12 +959,13 @@ final class DefaultPermissionGrantPolicy { mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0); if (isPhonePermFixed) { grantSystemFixedPermissionsToSystemPackage(pm, dialerPackage, userId, - PHONE_PERMISSIONS); + PHONE_PERMISSIONS, NOTIFICATION_PERMISSIONS); } else { grantPermissionsToSystemPackage(pm, dialerPackage, userId, PHONE_PERMISSIONS); } grantPermissionsToSystemPackage(pm, dialerPackage, userId, - CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS); + CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS, + NOTIFICATION_PERMISSIONS); boolean isAndroidAutomotive = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0); if (isAndroidAutomotive) { @@ -937,7 +977,8 @@ final class DefaultPermissionGrantPolicy { String smsPackage, int userId) { grantPermissionsToSystemPackage(pm, smsPackage, userId, PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, SMS_PERMISSIONS, - STORAGE_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS); + STORAGE_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS, + NOTIFICATION_PERMISSIONS); } private void grantDefaultPermissionsToDefaultSystemUseOpenWifiApp(PackageManagerWrapper pm, diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index cdab91bbaef8..411f3dcc1eb6 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -136,7 +136,8 @@ public interface StatusBarManagerInternal { @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName); /** @see com.android.internal.statusbar.IStatusBar#showTransient */ - void showTransient(int displayId, @InternalInsetsType int[] types); + void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar); /** @see com.android.internal.statusbar.IStatusBar#abortTransient */ void abortTransient(int displayId, @InternalInsetsType int[] types); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 54ec8845b24f..17f5566a9864 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -567,11 +567,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void showTransient(int displayId, @InternalInsetsType int[] types) { + public void showTransient(int displayId, @InternalInsetsType int[] types, + boolean isGestureOnSystemBar) { getUiState(displayId).showTransient(types); if (mBar != null) { try { - mBar.showTransient(displayId, types); + mBar.showTransient(displayId, types, isGestureOnSystemBar); } catch (RemoteException ex) { } } } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index e508260746da..63f4c68b11f6 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; +import android.content.pm.PackageManager; import android.media.IResourceManagerService; import android.media.tv.TvInputManager; import android.media.tv.tunerresourcemanager.CasSessionRequest; @@ -543,6 +544,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump!"); + return; + } + synchronized (mLock) { if (mClientProfiles != null) { pw.println("ClientProfiles:"); diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index a31c56a3b737..a17e79273e41 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -21,6 +21,7 @@ import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX; import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; import android.annotation.NonNull; import android.annotation.Nullable; @@ -38,15 +39,19 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.CarrierPrivilegesListener; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -92,6 +97,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>(); @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener; + @NonNull + private final List<CarrierPrivilegesListener> mCarrierPrivilegesChangedListeners = + new ArrayList<>(); + @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot; public TelephonySubscriptionTracker( @@ -126,22 +135,71 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { }; } - /** Registers the receivers, and starts tracking subscriptions. */ + /** + * Registers the receivers, and starts tracking subscriptions. + * + * <p>Must always be run on the VcnManagementService thread. + */ public void register() { final HandlerExecutor executor = new HandlerExecutor(mHandler); + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); + filter.addAction(ACTION_MULTI_SIM_CONFIG_CHANGED); - mContext.registerReceiver( - this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED), null, mHandler); + mContext.registerReceiver(this, filter, null, mHandler); mSubscriptionManager.addOnSubscriptionsChangedListener( executor, mSubscriptionChangedListener); mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener); + + registerCarrierPrivilegesListeners(); + } + + private void registerCarrierPrivilegesListeners() { + final HandlerExecutor executor = new HandlerExecutor(mHandler); + final int modemCount = mTelephonyManager.getActiveModemCount(); + try { + for (int i = 0; i < modemCount; i++) { + CarrierPrivilegesListener carrierPrivilegesListener = + new CarrierPrivilegesListener() { + @Override + public void onCarrierPrivilegesChanged( + @NonNull List<String> privilegedPackageNames, + @NonNull int[] privilegedUids) { + // Re-trigger the synchronous check (which is also very cheap due + // to caching in CarrierPrivilegesTracker). This allows consistency + // with the onSubscriptionsChangedListener and broadcasts. + handleSubscriptionsChanged(); + } + }; + + mTelephonyManager.addCarrierPrivilegesListener( + i, executor, carrierPrivilegesListener); + mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener); + } + } catch (IllegalArgumentException e) { + Slog.wtf(TAG, "Encounted exception registering carrier privileges listeners", e); + } } - /** Unregisters the receivers, and stops tracking subscriptions. */ + /** + * Unregisters the receivers, and stops tracking subscriptions. + * + * <p>Must always be run on the VcnManagementService thread. + */ public void unregister() { mContext.unregisterReceiver(this); mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener); mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener); + + unregisterCarrierPrivilegesListeners(); + } + + private void unregisterCarrierPrivilegesListeners() { + for (CarrierPrivilegesListener carrierPrivilegesListener : + mCarrierPrivilegesChangedListeners) { + mTelephonyManager.removeCarrierPrivilegesListener(carrierPrivilegesListener); + } + mCarrierPrivilegesChangedListeners.clear(); } /** @@ -178,8 +236,6 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { // group. if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { - // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker - final TelephonyManager subIdSpecificTelephonyManager = mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); @@ -214,12 +270,39 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { */ @Override public void onReceive(Context context, Intent intent) { - // Accept sticky broadcasts; if CARRIER_CONFIG_CHANGED was previously broadcast and it - // already was for an identified carrier, we can stop waiting for initial load to complete - if (!ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { - return; + switch (intent.getAction()) { + case ACTION_CARRIER_CONFIG_CHANGED: + handleActionCarrierConfigChanged(context, intent); + break; + case ACTION_MULTI_SIM_CONFIG_CHANGED: + handleActionMultiSimConfigChanged(context, intent); + break; + default: + Slog.v(TAG, "Unknown intent received with action: " + intent.getAction()); } + } + + private void handleActionMultiSimConfigChanged(Context context, Intent intent) { + unregisterCarrierPrivilegesListeners(); + + // Clear invalid slotIds from the mReadySubIdsBySlotId map. + final int modemCount = mTelephonyManager.getActiveModemCount(); + final Iterator<Integer> slotIdIterator = mReadySubIdsBySlotId.keySet().iterator(); + while (slotIdIterator.hasNext()) { + final int slotId = slotIdIterator.next(); + if (slotId >= modemCount) { + slotIdIterator.remove(); + } + } + + registerCarrierPrivilegesListeners(); + handleSubscriptionsChanged(); + } + + private void handleActionCarrierConfigChanged(Context context, Intent intent) { + // Accept sticky broadcasts; if CARRIER_CONFIG_CHANGED was previously broadcast and it + // already was for an identified carrier, we can stop waiting for initial load to complete final int subId = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID); final int slotId = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX); diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java index 45769575111f..bd8d13b87125 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -20,8 +20,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static com.android.server.VcnManagementService.LOCAL_LOG; @@ -29,10 +29,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; -import android.net.vcn.VcnCellUnderlyingNetworkPriority; +import android.net.vcn.VcnCellUnderlyingNetworkTemplate; import android.net.vcn.VcnManager; -import android.net.vcn.VcnUnderlyingNetworkPriority; -import android.net.vcn.VcnWifiUnderlyingNetworkPriority; +import android.net.vcn.VcnUnderlyingNetworkTemplate; +import android.net.vcn.VcnWifiUnderlyingNetworkTemplate; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.telephony.SubscriptionManager; @@ -76,7 +76,7 @@ class NetworkPriorityClassifier { public static int calculatePriorityClass( VcnContext vcnContext, UnderlyingNetworkRecord networkRecord, - LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities, + LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -94,7 +94,7 @@ class NetworkPriorityClassifier { } int priorityIndex = 0; - for (VcnUnderlyingNetworkPriority nwPriority : underlyingNetworkPriorities) { + for (VcnUnderlyingNetworkTemplate nwPriority : underlyingNetworkPriorities) { if (checkMatchesPriorityRule( vcnContext, nwPriority, @@ -113,7 +113,7 @@ class NetworkPriorityClassifier { @VisibleForTesting(visibility = Visibility.PRIVATE) public static boolean checkMatchesPriorityRule( VcnContext vcnContext, - VcnUnderlyingNetworkPriority networkPriority, + VcnUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, @@ -130,32 +130,32 @@ class NetworkPriorityClassifier { return true; } - if (networkPriority instanceof VcnWifiUnderlyingNetworkPriority) { + if (networkPriority instanceof VcnWifiUnderlyingNetworkTemplate) { return checkMatchesWifiPriorityRule( - (VcnWifiUnderlyingNetworkPriority) networkPriority, + (VcnWifiUnderlyingNetworkTemplate) networkPriority, networkRecord, currentlySelected, carrierConfig); } - if (networkPriority instanceof VcnCellUnderlyingNetworkPriority) { + if (networkPriority instanceof VcnCellUnderlyingNetworkTemplate) { return checkMatchesCellPriorityRule( vcnContext, - (VcnCellUnderlyingNetworkPriority) networkPriority, + (VcnCellUnderlyingNetworkTemplate) networkPriority, networkRecord, subscriptionGroup, snapshot); } logWtf( - "Got unknown VcnUnderlyingNetworkPriority class: " + "Got unknown VcnUnderlyingNetworkTemplate class: " + networkPriority.getClass().getSimpleName()); return false; } @VisibleForTesting(visibility = Visibility.PRIVATE) public static boolean checkMatchesWifiPriorityRule( - VcnWifiUnderlyingNetworkPriority networkPriority, + VcnWifiUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, UnderlyingNetworkRecord currentlySelected, PersistableBundle carrierConfig) { @@ -202,7 +202,7 @@ class NetworkPriorityClassifier { @VisibleForTesting(visibility = Visibility.PRIVATE) public static boolean checkMatchesCellPriorityRule( VcnContext vcnContext, - VcnCellUnderlyingNetworkPriority networkPriority, + VcnCellUnderlyingNetworkTemplate networkPriority, UnderlyingNetworkRecord networkRecord, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot) { diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index cd124ee6a9fa..ca2e449ffc25 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -33,7 +33,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnGatewayConnectionConfig; -import android.net.vcn.VcnUnderlyingNetworkPriority; +import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; @@ -498,10 +498,10 @@ public class UnderlyingNetworkController { pw.println( "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network)); - pw.println("VcnUnderlyingNetworkPriority list:"); + pw.println("VcnUnderlyingNetworkTemplate list:"); pw.increaseIndent(); int index = 0; - for (VcnUnderlyingNetworkPriority priority : + for (VcnUnderlyingNetworkTemplate priority : mConnectionConfig.getVcnUnderlyingNetworkPriorities()) { pw.println("Priority index: " + index); priority.dump(pw); diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java index 27ba854ab197..df2f0d58565e 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java @@ -21,7 +21,7 @@ import android.annotation.Nullable; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.vcn.VcnUnderlyingNetworkPriority; +import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.ParcelUuid; import android.os.PersistableBundle; @@ -77,7 +77,7 @@ public class UnderlyingNetworkRecord { static Comparator<UnderlyingNetworkRecord> getComparator( VcnContext vcnContext, - LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities, + LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, @@ -133,7 +133,7 @@ public class UnderlyingNetworkRecord { void dump( VcnContext vcnContext, IndentingPrintWriter pw, - LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities, + LinkedHashSet<VcnUnderlyingNetworkTemplate> underlyingNetworkPriorities, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, UnderlyingNetworkRecord currentlySelected, diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 49a51d528422..f0e8b8f54e50 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -405,7 +405,7 @@ public class DisplayPolicy { WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS) ? getStatusBar() : getNavigationBar(); if (targetBar != null) { - requestTransientBars(targetBar); + requestTransientBars(targetBar, true /* isGestureOnSystemBar */); } } break; @@ -448,26 +448,25 @@ public class DisplayPolicy { // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context. mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler, new SystemGesturesPointerEventListener.Callbacks() { + @Override public void onSwipeFromTop() { synchronized (mLock) { - if (mStatusBar != null) { - requestTransientBars(mStatusBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_TOP, - false /* allowForAllPositions */); + final WindowState bar = mStatusBar != null + ? mStatusBar + : findAltBarMatchingPosition(ALT_BAR_TOP); + requestTransientBars(bar, true /* isGestureOnSystemBar */); } } @Override public void onSwipeFromBottom() { synchronized (mLock) { - if (mNavigationBar != null - && mNavigationBarPosition == NAV_BAR_BOTTOM) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM, - false /* allowForAllPositions */); + final WindowState bar = mNavigationBar != null + && mNavigationBarPosition == NAV_BAR_BOTTOM + ? mNavigationBar + : findAltBarMatchingPosition(ALT_BAR_BOTTOM); + requestTransientBars(bar, true /* isGestureOnSystemBar */); } } @@ -477,13 +476,8 @@ public class DisplayPolicy { synchronized (mLock) { mDisplayContent.calculateSystemGestureExclusion( excludedRegion, null /* outUnrestricted */); - final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture && - !mSystemGestures.currentGestureStartedInRegion(excludedRegion); - if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_RIGHT - || allowSideSwipe)) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT, allowSideSwipe); + requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_RIGHT, + ALT_BAR_RIGHT); } excludedRegion.recycle(); } @@ -494,17 +488,33 @@ public class DisplayPolicy { synchronized (mLock) { mDisplayContent.calculateSystemGestureExclusion( excludedRegion, null /* outUnrestricted */); - final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture && - !mSystemGestures.currentGestureStartedInRegion(excludedRegion); - if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_LEFT - || allowSideSwipe)) { - requestTransientBars(mNavigationBar); - } - checkAltBarSwipeForTransientBars(ALT_BAR_LEFT, allowSideSwipe); + requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_LEFT, + ALT_BAR_LEFT); } excludedRegion.recycle(); } + private void requestTransientBarsForSideSwipe(Region excludedRegion, + int navBarSide, int altBarSide) { + final WindowState barMatchingSide = mNavigationBar != null + && mNavigationBarPosition == navBarSide + ? mNavigationBar + : findAltBarMatchingPosition(altBarSide); + final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture && + !mSystemGestures.currentGestureStartedInRegion(excludedRegion); + if (barMatchingSide == null && !allowSideSwipe) { + return; + } + + // Request transient bars on the matching bar, or any bar if we always allow + // side swipes to show the bars + final boolean isGestureOnSystemBar = barMatchingSide != null; + final WindowState bar = barMatchingSide != null + ? barMatchingSide + : findTransientNavOrAltBar(); + requestTransientBars(bar, isGestureOnSystemBar); + } + @Override public void onFling(int duration) { if (mService.mPowerManagerInternal != null) { @@ -654,21 +664,39 @@ public class DisplayPolicy { mHandler.post(mGestureNavigationSettingsObserver::register); } - private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos, - boolean allowForAllPositions) { - if (mStatusBarAlt != null && (mStatusBarAltPosition == pos || allowForAllPositions)) { - requestTransientBars(mStatusBarAlt); + /** + * Returns the first non-null alt bar window matching the given position. + */ + private WindowState findAltBarMatchingPosition(@WindowManagerPolicy.AltBarPosition int pos) { + if (mStatusBarAlt != null && mStatusBarAltPosition == pos) { + return mStatusBarAlt; } - if (mNavigationBarAlt != null - && (mNavigationBarAltPosition == pos || allowForAllPositions)) { - requestTransientBars(mNavigationBarAlt); + if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) { + return mNavigationBarAlt; } - if (mClimateBarAlt != null && (mClimateBarAltPosition == pos || allowForAllPositions)) { - requestTransientBars(mClimateBarAlt); + if (mClimateBarAlt != null && mClimateBarAltPosition == pos) { + return mClimateBarAlt; } - if (mExtraNavBarAlt != null && (mExtraNavBarAltPosition == pos || allowForAllPositions)) { - requestTransientBars(mExtraNavBarAlt); + if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) { + return mExtraNavBarAlt; + } + return null; + } + + /** + * Finds the first non-null nav bar to request transient for. + */ + private WindowState findTransientNavOrAltBar() { + if (mNavigationBar != null) { + return mNavigationBar; + } + if (mNavigationBarAlt != null) { + return mNavigationBarAlt; + } + if (mExtraNavBarAlt != null) { + return mExtraNavBarAlt; } + return null; } void systemReady() { @@ -2170,8 +2198,8 @@ public class DisplayPolicy { updateSystemBarAttributes(); } - private void requestTransientBars(WindowState swipeTarget) { - if (!mService.mPolicy.isUserSetupComplete()) { + private void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) { + if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) { // Swipe-up for navigation bar is disabled during setup return; } @@ -2207,7 +2235,8 @@ public class DisplayPolicy { if (controlTarget.canShowTransient()) { // Show transient bars if they are hidden; restore position if they are visible. - mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE); + mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE, + isGestureOnSystemBar); controlTarget.showInsets(restorePositionTypes, false); } else { // Restore visibilities and positions of system bars. @@ -2423,7 +2452,8 @@ public class DisplayPolicy { // we're no longer on the Keyguard and the screen is ready. We can now request the bars. mPendingPanicGestureUptime = 0; if (!isNavBarEmpty(disableFlags)) { - mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC); + mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC, + true /* isGestureOnSystemBar */); } } @@ -2821,6 +2851,7 @@ public class DisplayPolicy { void release() { mHandler.post(mGestureNavigationSettingsObserver::unregister); + mImmersiveModeConfirmation.release(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java index f3b9cdfd39e0..93bdf16a99ea 100644 --- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java +++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java @@ -149,6 +149,11 @@ public class ImmersiveModeConfirmation { } } + void release() { + mHandler.removeMessages(H.SHOW); + mHandler.removeMessages(H.HIDE); + } + boolean onSettingChanged(int currentUserId) { final boolean changed = loadSetting(currentUserId, mContext); // Remove the window if the setting changes to be confirmed. @@ -204,7 +209,12 @@ public class ImmersiveModeConfirmation { if (mClingWindow != null) { if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation"); // We don't care which root display area the window manager is specifying for removal. - getWindowManager(FEATURE_UNDEFINED).removeView(mClingWindow); + try { + getWindowManager(FEATURE_UNDEFINED).removeView(mClingWindow); + } catch (WindowManager.InvalidDisplayException e) { + Slog.w(TAG, "Fail to hide the immersive confirmation window because of " + e); + return; + } mClingWindow = null; } } @@ -432,7 +442,11 @@ public class ImmersiveModeConfirmation { // show the confirmation WindowManager.LayoutParams lp = getClingWindowLayoutParams(); - getWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); + try { + getWindowManager(rootDisplayAreaId).addView(mClingWindow, lp); + } catch (WindowManager.InvalidDisplayException e) { + Slog.w(TAG, "Fail to show the immersive confirmation window because of " + e); + } } private final Runnable mConfirm = new Runnable() { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index dff7ff9931d7..9326a2ebb331 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -160,7 +160,7 @@ class InsetsPolicy { return provider != null && provider.hasWindow() && !provider.getSource().isVisible(); } - void showTransient(@InternalInsetsType int[] types) { + void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) { boolean changed = false; for (int i = types.length - 1; i >= 0; i--) { final @InternalInsetsType int type = types[i]; @@ -177,8 +177,8 @@ class InsetsPolicy { StatusBarManagerInternal statusBarManagerInternal = mPolicy.getStatusBarManagerInternal(); if (statusBarManagerInternal != null) { - statusBarManagerInternal.showTransient( - mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray()); + statusBarManagerInternal.showTransient(mDisplayContent.getDisplayId(), + mShowingTransientTypes.toArray(), isGestureOnSystemBar); } updateBarControlTarget(mFocusedWin); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index e33c4403a98b..1a1101e45f45 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -187,7 +187,6 @@ class InsetsStateController { // Navigation bar doesn't get influenced by anything else if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) { - state.removeSource(ITYPE_IME); state.removeSource(ITYPE_STATUS_BAR); state.removeSource(ITYPE_CLIMATE_BAR); state.removeSource(ITYPE_CAPTION_BAR); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 535bbb74d1e3..3f2e97505765 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2051,19 +2051,25 @@ class RootWindowContainer extends WindowContainer<DisplayContent> try { final Task task = r.getTask(); - final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask(); - // This will change the root pinned task's windowing mode to its original mode, ensuring - // we only have one root task that is in pinned mode. + // If the activity in current PIP task needs to be moved back to the parent Task of next + // PIP activity, we can't use that parent Task as the next PIP Task. + // Because we need to start the Shell transition from the root Task, we delay to dismiss + // the current PIP task until root Task is ready. + boolean origPipWillBeMovedToTask = false; + final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask(); if (rootPinnedTask != null) { - rootPinnedTask.dismissPip(); + final ActivityRecord topPipActivity = rootPinnedTask.getTopMostActivity(); + if (topPipActivity != null && topPipActivity.getLastParentBeforePip() == task) { + origPipWillBeMovedToTask = true; + } } // Set a transition to ensure that we don't immediately try and update the visibility // of the activity entering PIP r.getDisplayContent().prepareAppTransition(TRANSIT_NONE); - final boolean singleActivity = task.getChildCount() == 1; + final boolean singleActivity = task.getChildCount() == 1 && !origPipWillBeMovedToTask; final Task rootTask; if (singleActivity) { rootTask = task; @@ -2117,7 +2123,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final ActivityRecord oldTopActivity = task.getTopMostActivity(); if (oldTopActivity != null && oldTopActivity.isState(STOPPED) && task.getDisplayContent().mAppTransition.containsTransitRequest( - TRANSIT_TO_BACK)) { + TRANSIT_TO_BACK) && !origPipWillBeMovedToTask) { task.getDisplayContent().mClosingApps.add(oldTopActivity); oldTopActivity.mRequestForceTransition = true; } @@ -2133,6 +2139,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_PIP, rootTask); + // This will change the root pinned task's windowing mode to its original mode, ensuring + // we only have one root task that is in pinned mode. + if (rootPinnedTask != null) { + rootTask.mTransitionController.collect(rootPinnedTask); + rootPinnedTask.dismissPip(); + } + // Defer the windowing mode change until after the transition to prevent the activity // from doing work and changing the activity visuals while animating // TODO(task-org): Figure-out more structured way to do this long term. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 659c7713a613..7617726e1fcd 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -478,7 +478,6 @@ class Task extends TaskFragment { // to layout without loading all the task snapshots final PersistedTaskSnapshotData mLastTaskSnapshotData; - private Dimmer mDimmer = new Dimmer(this); private final Rect mTmpDimBoundsRect = new Rect(); /** @see #setCanAffectSystemUiFlags */ diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 8299ceae57fd..d133ca96b45e 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -161,6 +161,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ int mMinHeight; + Dimmer mDimmer = new Dimmer(this); + /** This task fragment will be removed when the cleanup of its children are done. */ private boolean mIsRemovalRequested; @@ -2349,6 +2351,34 @@ class TaskFragment extends WindowContainer<WindowContainer> { } @Override + Dimmer getDimmer() { + // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment. + if (asTask() == null) { + return mDimmer; + } + + return super.getDimmer(); + } + + @Override + void prepareSurfaces() { + if (asTask() != null) { + super.prepareSurfaces(); + return; + } + + mDimmer.resetDimStates(); + super.prepareSurfaces(); + + // Bounds need to be relative, as the dim layer is a child. + final Rect dimBounds = getBounds(); + dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */); + if (mDimmer.updateDims(getPendingTransaction(), dimBounds)) { + scheduleAnimation(); + } + } + + @Override boolean canBeAnimationTarget() { return true; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index df8953c318dc..db8da1146f1d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -258,6 +258,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManager.UserRestrictionSource; import android.os.storage.StorageManager; import android.permission.AdminPermissionControlParams; import android.permission.IPermissionManager; @@ -286,6 +287,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.DebugUtils; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Pair; @@ -13271,14 +13273,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { try { List<UserManager.EnforcingUser> sources = mUserManager .getUserRestrictionSources(restriction, UserHandle.of(userId)); - if (sources == null || sources.isEmpty()) { + if (sources == null) { // The restriction is not enforced. return null; - } else if (sources.size() > 1) { + } + int sizeBefore = sources.size(); + if (sizeBefore > 1) { + Slogf.d(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): " + + "%d sources found, excluding those set by UserManager", + userId, restriction, sizeBefore); + sources = getDevicePolicySources(sources); + } + if (sources.isEmpty()) { + // The restriction is not enforced (or is just enforced by the system) + return null; + } + + if (sources.size() > 1) { // In this case, we'll show an admin support dialog that does not // specify the admin. // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return // the admin for the calling user. + Slogf.w(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): multiple " + + "sources for restriction %s on user %d", restriction, userId); result = new Bundle(); result.putInt(Intent.EXTRA_USER_ID, userId); return result; @@ -13324,6 +13341,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * Excludes restrictions imposed by UserManager. + */ + private List<UserManager.EnforcingUser> getDevicePolicySources( + List<UserManager.EnforcingUser> sources) { + int sizeBefore = sources.size(); + List<UserManager.EnforcingUser> realSources = new ArrayList<>(sizeBefore); + for (int i = 0; i < sizeBefore; i++) { + UserManager.EnforcingUser source = sources.get(i); + int type = source.getUserRestrictionSource(); + if (type != UserManager.RESTRICTION_SOURCE_PROFILE_OWNER + && type != UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) { + // TODO(b/128928355): add unit test + Slogf.d(LOG_TAG, "excluding source of type %s at index %d", + userRestrictionSourceToString(type), i); + continue; + } + realSources.add(source); + } + return realSources; + } + + private static String userRestrictionSourceToString(@UserRestrictionSource int source) { + return DebugUtils.flagsToString(UserManager.class, "RESTRICTION_", source); + } + + /** * @param restriction The restriction enforced by admin. It could be any user restriction or * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 44a8b3001a71..04a6eeecb320 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -74,6 +74,7 @@ import com.android.server.testutils.mock import com.android.server.testutils.nullable import com.android.server.testutils.whenever import com.android.server.utils.WatchedArrayMap +import libcore.util.HexEncoding import org.junit.Assert import org.junit.rules.TestRule import org.junit.runner.Description @@ -140,6 +141,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .mockStatic(EventLog::class.java) .mockStatic(LocalServices::class.java) .mockStatic(DeviceConfig::class.java) + .mockStatic(HexEncoding::class.java) .apply(withSession) session = apply.startMocking() whenever(mocks.settings.insertPackageSettingLPw( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt new file mode 100644 index 000000000000..6de12cb98719 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.SharedLibraryInfo +import android.content.pm.VersionedPackage +import android.os.Build +import android.os.storage.StorageManager +import android.util.ArrayMap +import android.util.PackageUtils +import com.android.server.SystemConfig.SharedLibraryEntry +import com.android.server.compat.PlatformCompat +import com.android.server.extendedtestutils.wheneverStatic +import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.parsing.pkg.ParsedPackage +import com.android.server.testutils.any +import com.android.server.testutils.eq +import com.android.server.testutils.mock +import com.android.server.testutils.nullable +import com.android.server.testutils.spy +import com.android.server.testutils.whenever +import com.android.server.utils.WatchedLongSparseArray +import com.google.common.truth.Truth.assertThat +import libcore.util.HexEncoding +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.File +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(JUnit4::class) +class SharedLibrariesImplTest { + + companion object { + const val TEST_LIB_NAME = "test.lib" + const val TEST_LIB_PACKAGE_NAME = "com.android.lib.test" + const val BUILTIN_LIB_NAME = "builtin.lib" + const val STATIC_LIB_NAME = "static.lib" + const val STATIC_LIB_VERSION = 7L + const val STATIC_LIB_PACKAGE_NAME = "com.android.lib.static.provider" + const val DYNAMIC_LIB_NAME = "dynamic.lib" + const val DYNAMIC_LIB_PACKAGE_NAME = "com.android.lib.dynamic.provider" + const val CONSUMER_PACKAGE_NAME = "com.android.lib.consumer" + const val VERSION_UNDEFINED = SharedLibraryInfo.VERSION_UNDEFINED.toLong() + } + + @Rule + @JvmField + val mRule = MockSystemRule() + + private val mExistingPackages: ArrayMap<String, AndroidPackage> = ArrayMap() + private val mExistingSettings: MutableMap<String, PackageSetting> = mutableMapOf() + + private lateinit var mSharedLibrariesImpl: SharedLibrariesImpl + private lateinit var mPms: PackageManagerService + private lateinit var mSettings: Settings + + @Mock + private lateinit var mDeletePackageHelper: DeletePackageHelper + @Mock + private lateinit var mStorageManager: StorageManager + @Mock + private lateinit var mFile: File + @Mock + private lateinit var mPlatformCompat: PlatformCompat + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + mRule.system().stageNominalSystemState() + addExistingPackages() + + val testParams = PackageManagerServiceTestParams().apply { + packages = mExistingPackages + } + mPms = spy(PackageManagerService(mRule.mocks().injector, testParams)) + mSettings = mRule.mocks().injector.settings + mSharedLibrariesImpl = SharedLibrariesImpl(mPms, mRule.mocks().injector) + mSharedLibrariesImpl.setDeletePackageHelper(mDeletePackageHelper) + addExistingSharedLibraries() + + whenever(mSettings.getPackageLPr(any())) { mExistingSettings[arguments[0]] } + whenever(mRule.mocks().injector.getSystemService(StorageManager::class.java)) + .thenReturn(mStorageManager) + whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile) + doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageNameLPr(any(), any()) + whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any())) + .thenReturn(PackageManager.DELETE_SUCCEEDED) + whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat) + wheneverStatic { HexEncoding.decode(STATIC_LIB_NAME, false) } + .thenReturn(PackageUtils.computeSha256DigestBytes( + mExistingSettings[STATIC_LIB_PACKAGE_NAME]!! + .pkg.signingDetails.signatures!![0].toByteArray())) + } + + @Test + fun snapshot_shouldSealed() { + val builtinLibs = mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME] + assertThat(builtinLibs).isNotNull() + + assertFailsWith(IllegalStateException::class) { + mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME] = WatchedLongSparseArray() + } + assertFailsWith(IllegalStateException::class) { + builtinLibs!!.put(VERSION_UNDEFINED, libOfBuiltin(BUILTIN_LIB_NAME)) + } + } + + @Test + fun addBuiltInSharedLibrary() { + mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(TEST_LIB_NAME)) + + assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME)).isNotNull() + assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(TEST_LIB_NAME, VERSION_UNDEFINED)) + .isNotNull() + } + + @Test + fun addBuiltInSharedLibrary_withDuplicateLibName() { + val duplicate = libEntry(BUILTIN_LIB_NAME, "duplicate.path") + mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(duplicate) + val sharedLibInfo = mSharedLibrariesImpl + .getSharedLibraryInfo(BUILTIN_LIB_NAME, VERSION_UNDEFINED) + + assertThat(sharedLibInfo).isNotNull() + assertThat(sharedLibInfo!!.path).isNotEqualTo(duplicate.filename) + } + + @Test + fun commitSharedLibraryInfo_withStaticSharedLib() { + val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L) + mSharedLibrariesImpl.commitSharedLibraryInfoLPw(testInfo) + val sharedLibInfos = mSharedLibrariesImpl + .getStaticLibraryInfos(testInfo.declaringPackage.packageName) + + assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME)) + .isNotNull() + assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(testInfo.name, testInfo.longVersion)) + .isNotNull() + assertThat(sharedLibInfos).isNotNull() + assertThat(sharedLibInfos.get(testInfo.longVersion)).isNotNull() + } + + @Test + fun removeSharedLibrary() { + doAnswer { mutableListOf(VersionedPackage(CONSUMER_PACKAGE_NAME, 1L)) }.`when`(mPms) + .getPackagesUsingSharedLibrary(any(), any(), any(), any()) + val staticInfo = mSharedLibrariesImpl + .getSharedLibraryInfo(STATIC_LIB_NAME, STATIC_LIB_VERSION)!! + + mSharedLibrariesImpl.removeSharedLibraryLPw(STATIC_LIB_NAME, STATIC_LIB_VERSION) + + assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(STATIC_LIB_NAME)).isNull() + assertThat(mSharedLibrariesImpl + .getStaticLibraryInfos(staticInfo.declaringPackage.packageName)).isNull() + verify(mExistingSettings[CONSUMER_PACKAGE_NAME]!!) + .setOverlayPathsForLibrary(any(), nullable(), any()) + } + + @Test + fun pruneUnusedStaticSharedLibraries() { + mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, 0) + + verify(mDeletePackageHelper) + .deletePackageX(eq(STATIC_LIB_PACKAGE_NAME), any(), any(), any(), any()) + } + + @Test + fun getLatestSharedLibraVersion() { + val newLibSetting = addPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, + staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) + + val latestInfo = mSharedLibrariesImpl.getLatestSharedLibraVersionLPr(newLibSetting.pkg)!! + + assertThat(latestInfo).isNotNull() + assertThat(latestInfo.name).isEqualTo(STATIC_LIB_NAME) + assertThat(latestInfo.longVersion).isEqualTo(STATIC_LIB_VERSION) + } + + @Test + fun getStaticSharedLibLatestVersionSetting() { + val pair = createBasicAndroidPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, + staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) + val parsedPackage = pair.second as ParsedPackage + val scanRequest = ScanRequest(parsedPackage, null, null, null, + null, null, null, 0, 0, false, null, null) + val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null) + + val latestInfoSetting = + mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(scanResult)!! + + assertThat(latestInfoSetting).isNotNull() + assertThat(latestInfoSetting.packageName).isEqualTo(STATIC_LIB_PACKAGE_NAME) + } + + @Test + fun updateSharedLibraries_withDynamicLibPackage() { + val testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).isEmpty() + + mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting, + null /* changingLib */, null /* changingLibSetting */, mExistingPackages) + + assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + } + + @Test + fun updateSharedLibraries_withStaticLibPackage() { + val testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).isEmpty() + + mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting, + null /* changingLib */, null /* changingLibSetting */, mExistingPackages) + + assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + } + + @Test + fun updateSharedLibraries_withConsumerPackage() { + val testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).isEmpty() + + mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting, + null /* changingLib */, null /* changingLibSetting */, mExistingPackages) + + assertThat(testPackageSetting.usesLibraryFiles).hasSize(2) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME)) + } + + @Test + fun updateAllSharedLibraries() { + mExistingSettings.forEach { + assertThat(it.value.usesLibraryFiles).isEmpty() + } + + mSharedLibrariesImpl.updateAllSharedLibrariesLPw( + null /* updatedPkg */, null /* updatedPkgSetting */, mExistingPackages) + + var testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + + testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).hasSize(2) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + + testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).hasSize(3) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME)) + } + + @Test + fun getAllowedSharedLibInfos_withStaticSharedLibInfo() { + val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L) + val scanResult = ScanResult(mock(), true, null, null, + false, 0, null, testInfo, null) + + val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult) + + assertThat(allowedInfos).hasSize(1) + assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME) + } + + @Test + fun getAllowedSharedLibInfos_withDynamicSharedLibInfo() { + val testInfo = libOfDynamic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME) + val pair = createBasicAndroidPackage( + TEST_LIB_PACKAGE_NAME, 10L, libraries = arrayOf(TEST_LIB_NAME)) + val parsedPackage = pair.second.apply { + isSystem = true + } as ParsedPackage + val packageSetting = mRule.system() + .createBasicSettingBuilder(pair.first.parentFile, parsedPackage.hideAsFinal()) + .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build() + val scanRequest = ScanRequest(parsedPackage, null, null, null, + null, null, null, 0, 0, false, null, null) + val scanResult = ScanResult(scanRequest, true, packageSetting, null, + false, 0, null, null, listOf(testInfo)) + + val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult) + + assertThat(allowedInfos).hasSize(1) + assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME) + } + + private fun addExistingPackages() { + // add a dynamic shared library that is using the builtin library + addPackage(DYNAMIC_LIB_PACKAGE_NAME, 1L, + libraries = arrayOf(DYNAMIC_LIB_NAME), + usesLibraries = arrayOf(BUILTIN_LIB_NAME)) + + // add a static shared library v7 that is using the dynamic shared library + addPackage(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_VERSION, + staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = STATIC_LIB_VERSION, + usesLibraries = arrayOf(DYNAMIC_LIB_NAME)) + + // add a consumer package that is using the dynamic and static shared library + addPackage(CONSUMER_PACKAGE_NAME, 1L, + usesLibraries = arrayOf(DYNAMIC_LIB_NAME), + usesStaticLibraries = arrayOf(STATIC_LIB_NAME), + usesStaticLibraryVersions = arrayOf(STATIC_LIB_VERSION)) + } + + private fun addExistingSharedLibraries() { + mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(BUILTIN_LIB_NAME)) + mSharedLibrariesImpl.commitSharedLibraryInfoLPw( + libOfDynamic(DYNAMIC_LIB_PACKAGE_NAME, DYNAMIC_LIB_NAME)) + mSharedLibrariesImpl.commitSharedLibraryInfoLPw( + libOfStatic(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_NAME, STATIC_LIB_VERSION)) + } + + private fun addPackage( + packageName: String, + version: Long, + libraries: Array<String>? = null, + staticLibrary: String? = null, + staticLibraryVersion: Long = 0L, + usesLibraries: Array<String>? = null, + usesStaticLibraries: Array<String>? = null, + usesStaticLibraryVersions: Array<Long>? = null + ): PackageSetting { + val pair = createBasicAndroidPackage(packageName, version, libraries, staticLibrary, + staticLibraryVersion, usesLibraries, usesStaticLibraries, usesStaticLibraryVersions) + val apkPath = pair.first + val parsingPackage = pair.second + val spyPkg = spy((parsingPackage as ParsedPackage).hideAsFinal()) + mExistingPackages[packageName] = spyPkg + + val spyPackageSetting = spy(mRule.system() + .createBasicSettingBuilder(apkPath.parentFile, spyPkg).build()) + mExistingSettings[spyPackageSetting.packageName] = spyPackageSetting + + return spyPackageSetting + } + + private fun createBasicAndroidPackage( + packageName: String, + version: Long, + libraries: Array<String>? = null, + staticLibrary: String? = null, + staticLibraryVersion: Long = 0L, + usesLibraries: Array<String>? = null, + usesStaticLibraries: Array<String>? = null, + usesStaticLibraryVersions: Array<Long>? = null + ): Pair<File, PackageImpl> { + assertFalse { libraries != null && staticLibrary != null } + assertTrue { (usesStaticLibraries?.size ?: -1) == (usesStaticLibraryVersions?.size ?: -1) } + + val pair = mRule.system() + .createBasicAndroidPackage(mRule.system().dataAppDirectory, packageName, version) + pair.second.apply { + setTargetSdkVersion(Build.VERSION_CODES.S) + libraries?.forEach { addLibraryName(it) } + staticLibrary?.let { + setStaticSharedLibName(it) + setStaticSharedLibVersion(staticLibraryVersion) + setStaticSharedLibrary(true) + } + usesLibraries?.forEach { addUsesLibrary(it) } + usesStaticLibraries?.forEachIndexed { index, s -> + addUsesStaticLibrary(s, + usesStaticLibraryVersions?.get(index) ?: 0L, + arrayOf(s)) + } + } + return pair + } + + private fun libEntry(libName: String, path: String? = null): SharedLibraryEntry = + SharedLibraryEntry(libName, path ?: builtinLibPath(libName), + arrayOfNulls(0), false /* isNative */) + + private fun libOfBuiltin(libName: String): SharedLibraryInfo = + SharedLibraryInfo(builtinLibPath(libName), + null /* packageName */, + null /* codePaths */, + libName, + VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_BUILTIN, + VersionedPackage(PLATFORM_PACKAGE_NAME, 0L /* versionCode */), + null /* dependentPackages */, + null /* dependencies */, + false /* isNative */) + + private fun libOfStatic( + packageName: String, + libName: String, + version: Long + ): SharedLibraryInfo = + SharedLibraryInfo(null /* path */, + packageName, + listOf(apkPath(packageName)), + libName, + version, + SharedLibraryInfo.TYPE_STATIC, + VersionedPackage(packageName, version /* versionCode */), + null /* dependentPackages */, + null /* dependencies */, + false /* isNative */) + + private fun libOfDynamic(packageName: String, libName: String): SharedLibraryInfo = + SharedLibraryInfo(null /* path */, + packageName, + listOf(apkPath(packageName)), + libName, + VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_DYNAMIC, + VersionedPackage(packageName, 1L /* versionCode */), + null /* dependentPackages */, + null /* dependencies */, + false /* isNative */) + + private fun builtinLibPath(libName: String): String = "/system/app/$libName/$libName.jar" + + private fun apkPath(packageName: String): String = + File(mRule.system().dataAppDirectory, packageName).path +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index d18030f10603..97ebdd4ad7fa 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -595,6 +595,20 @@ public class AbstractAccessibilityServiceConnectionTest { } @Test + public void getCurrentMagnificationRegion_returnRegion() { + final int displayId = 1; + final Region region = new Region(10, 20, 100, 200); + doAnswer((invocation) -> { + ((Region) invocation.getArguments()[1]).set(region); + return null; + }).when(mMockMagnificationProcessor).getCurrentMagnificationRegion(eq(displayId), any(), + anyBoolean()); + + final Region result = mServiceConnection.getCurrentMagnificationRegion(displayId); + assertEquals(result, region); + } + + @Test public void getMagnificationCenterX_serviceNotBelongCurrentUser_returnZero() { final int displayId = 1; final float centerX = 480.0f; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 3ade9ff61735..953b5368c86f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -343,6 +343,20 @@ public class AccessibilityManagerServiceTest { eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), ArgumentMatchers.isNotNull()); } + @Test + public void testFollowTypingEnabled_defaultEnabledAndThenDisable_propagateToController() { + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + Settings.Secure.putIntForUser( + mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, + 0, mA11yms.getCurrentUserIdLocked()); + + mA11yms.readMagnificationFollowTypingLocked(userState); + + verify(mMockMagnificationController).setMagnificationFollowTypingEnabled(false); + } + @SmallTest @Test public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index b9d94edc5981..27637c2ba5d5 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -172,6 +172,7 @@ public class AccessibilityUserStateTest { mUserState.getMagnificationModeLocked(TEST_DISPLAY)); assertEquals(mFocusStrokeWidthDefaultValue, mUserState.getFocusStrokeWidthLocked()); assertEquals(mFocusColorDefaultValue, mUserState.getFocusColorLocked()); + assertTrue(mUserState.isMagnificationFollowTypingEnabled()); } @Test @@ -374,6 +375,15 @@ public class AccessibilityUserStateTest { } @Test + public void setMagnificationFollowTypingEnabled_defaultTrueAndThenDisable_returnFalse() { + assertTrue(mUserState.isMagnificationFollowTypingEnabled()); + + mUserState.setMagnificationFollowTypingEnabled(false); + + assertFalse(mUserState.isMagnificationFollowTypingEnabled()); + } + + @Test public void setFocusAppearanceData_returnExpectedFocusAppearanceData() { final int focusStrokeWidthValue = 100; final int focusColorValue = Color.BLUE; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java index 99d6c2af6f75..c4040b405d19 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java @@ -184,6 +184,38 @@ public class MagnificationProcessorTest { } @Test + public void getCurrentMagnificationRegion_windowModeActivated_returnRegion() { + final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW); + doAnswer((invocation) -> { + ((Region) invocation.getArguments()[1]).set(region); + return null; + }).when(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY), + any()); + + final Region result = new Region(); + mMagnificationProcessor.getCurrentMagnificationRegion(TEST_DISPLAY, + result, /* canControlMagnification= */true); + assertEquals(region, result); + } + + @Test + public void getCurrentMagnificationRegion_fullscreenModeActivated_returnRegion() { + final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN); + doAnswer((invocation) -> { + ((Region) invocation.getArguments()[1]).set(region); + return null; + }).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY), + any()); + + final Region result = new Region(); + mMagnificationProcessor.getCurrentMagnificationRegion(TEST_DISPLAY, + result, /* canControlMagnification= */true); + assertEquals(region, result); + } + + @Test public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { final MagnificationConfig config = new MagnificationConfig.Builder() .setMode(MAGNIFICATION_MODE_FULLSCREEN) @@ -222,7 +254,7 @@ public class MagnificationProcessorTest { } @Test - public void reset_fullscreenMagnificationActivated() { + public void resetFullscreenMagnification_fullscreenMagnificationActivated() { setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN); mMagnificationProcessor.resetFullscreenMagnification(TEST_DISPLAY, /* animate= */false); @@ -231,7 +263,7 @@ public class MagnificationProcessorTest { } @Test - public void reset_windowMagnificationActivated() { + public void resetCurrentMagnification_windowMagnificationActivated() { setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW); mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index 96af61737bff..a9b7cfb5e338 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN; + import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback; import static org.junit.Assert.assertEquals; @@ -23,10 +25,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; +import android.accessibilityservice.MagnificationConfig; import android.animation.ValueAnimator; import android.content.BroadcastReceiver; import android.content.Context; @@ -105,6 +106,8 @@ public class FullScreenMagnificationControllerTest { private final MagnificationScaleProvider mScaleProvider = mock( MagnificationScaleProvider.class); + private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); ValueAnimator mMockValueAnimator; ValueAnimator.AnimatorUpdateListener mTargetAnimationListener; @@ -142,13 +145,13 @@ public class FullScreenMagnificationControllerTest { register(DISPLAY_1); register(INVALID_DISPLAY); verify(mMockContext).registerReceiver( - (BroadcastReceiver) anyObject(), (IntentFilter) anyObject()); + any(BroadcastReceiver.class), any(IntentFilter.class)); verify(mMockWindowManager).setMagnificationCallbacks( - eq(DISPLAY_0), (MagnificationCallbacks) anyObject()); + eq(DISPLAY_0), any(MagnificationCallbacks.class)); verify(mMockWindowManager).setMagnificationCallbacks( - eq(DISPLAY_1), (MagnificationCallbacks) anyObject()); + eq(DISPLAY_1), any(MagnificationCallbacks.class)); verify(mMockWindowManager).setMagnificationCallbacks( - eq(INVALID_DISPLAY), (MagnificationCallbacks) anyObject()); + eq(INVALID_DISPLAY), any(MagnificationCallbacks.class)); assertTrue(mFullScreenMagnificationController.isRegistered(DISPLAY_0)); assertTrue(mFullScreenMagnificationController.isRegistered(DISPLAY_1)); assertFalse(mFullScreenMagnificationController.isRegistered(INVALID_DISPLAY)); @@ -159,9 +162,9 @@ public class FullScreenMagnificationControllerTest { register(DISPLAY_0); register(DISPLAY_1); mFullScreenMagnificationController.unregister(DISPLAY_0); - verify(mMockContext, times(0)).unregisterReceiver((BroadcastReceiver) anyObject()); + verify(mMockContext, times(0)).unregisterReceiver(any(BroadcastReceiver.class)); mFullScreenMagnificationController.unregister(DISPLAY_1); - verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject()); + verify(mMockContext).unregisterReceiver(any(BroadcastReceiver.class)); verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_0), eq(null)); verify(mMockWindowManager).setMagnificationCallbacks(eq(DISPLAY_1), eq(null)); assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0)); @@ -343,6 +346,7 @@ public class FullScreenMagnificationControllerTest { MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); float scale = 2.5f; PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; + final MagnificationConfig config = buildConfig(scale, newCenter.x, newCenter.y); PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale); MagnificationSpec endSpec = getMagnificationSpec(scale, offsets); @@ -353,8 +357,9 @@ public class FullScreenMagnificationControllerTest { assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5); assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5); assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec)); - verify(mMockAms).notifyMagnificationChanged(displayId, - INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y); + verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), + mConfigCaptor.capture()); + assertConfigEquals(config, mConfigCaptor.getValue()); verify(mMockValueAnimator).start(); verify(mRequestObserver).onRequestMagnificationSpec(displayId, SERVICE_ID_1); @@ -494,8 +499,11 @@ public class FullScreenMagnificationControllerTest { MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId); callbacks.onMagnificationRegionChanged(OTHER_REGION); mMessageCapturingHandler.sendAllMessages(); - verify(mMockAms).notifyMagnificationChanged(displayId, OTHER_REGION, 1.0f, - OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY()); + MagnificationConfig config = buildConfig(1.0f, OTHER_MAGNIFICATION_BOUNDS.centerX(), + OTHER_MAGNIFICATION_BOUNDS.centerY()); + verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(OTHER_REGION), + mConfigCaptor.capture()); + assertConfigEquals(config, mConfigCaptor.getValue()); } @Test @@ -650,7 +658,7 @@ public class FullScreenMagnificationControllerTest { reset(mMockAms); assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false)); verify(mMockAms).notifyMagnificationChanged(eq(displayId), - eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyFloat(), anyFloat()); + eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class)); assertFalse(mFullScreenMagnificationController.isMagnifying(displayId)); assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false)); } @@ -668,8 +676,8 @@ public class FullScreenMagnificationControllerTest { assertFalse(mFullScreenMagnificationController.reset(displayId, mAnimationCallback)); mMessageCapturingHandler.sendAllMessages(); - verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), - any(Region.class), anyFloat(), anyFloat(), anyFloat()); + verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), any(Region.class), + any(MagnificationConfig.class)); verify(mAnimationCallback).onResult(true); } @@ -726,7 +734,7 @@ public class FullScreenMagnificationControllerTest { ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext).registerReceiver( - broadcastReceiverCaptor.capture(), (IntentFilter) anyObject()); + broadcastReceiverCaptor.capture(), any(IntentFilter.class)); BroadcastReceiver br = broadcastReceiverCaptor.getValue(); zoomIn2xToMiddle(DISPLAY_0); zoomIn2xToMiddle(DISPLAY_1); @@ -913,6 +921,22 @@ public class FullScreenMagnificationControllerTest { } @Test + public void requestRectOnScreen_disabledByPrefSetting_doesNothing() { + register(DISPLAY_0); + zoomIn2xToMiddle(DISPLAY_0); + Mockito.reset(mMockWindowManager); + MagnificationSpec startSpec = getCurrentMagnificationSpec(DISPLAY_0); + MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0); + mFullScreenMagnificationController.setMagnificationFollowTypingEnabled(false); + + mFullScreenMagnificationController.onRectangleOnScreenRequested(DISPLAY_0, 0, 0, 1, 1); + + assertThat(getCurrentMagnificationSpec(DISPLAY_0), closeTo(startSpec)); + verify(mMockWindowManager, never()).setMagnificationSpec(eq(DISPLAY_0), + argThat(closeTo(expectedEndSpec))); + } + + @Test public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() { for (int i = 0; i < DISPLAY_COUNT; i++) { requestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen(i); @@ -1031,6 +1055,7 @@ public class FullScreenMagnificationControllerTest { MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId); float scale = 2.5f; PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; + final MagnificationConfig config = buildConfig(scale, firstCenter.x, firstCenter.y); MagnificationSpec firstEndSpec = getMagnificationSpec( scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale)); @@ -1047,8 +1072,9 @@ public class FullScreenMagnificationControllerTest { when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec)); - verify(mMockAms).notifyMagnificationChanged(displayId, - INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y); + verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), + mConfigCaptor.capture()); + assertConfigEquals(config, mConfigCaptor.getValue()); Mockito.reset(mMockWindowManager); // Intermediate point @@ -1062,6 +1088,7 @@ public class FullScreenMagnificationControllerTest { Mockito.reset(mMockWindowManager); PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER; + final MagnificationConfig newConfig = buildConfig(scale, newCenter.x, newCenter.y); MagnificationSpec newEndSpec = getMagnificationSpec( scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale)); assertTrue(mFullScreenMagnificationController.setCenter(displayId, @@ -1070,8 +1097,9 @@ public class FullScreenMagnificationControllerTest { // Animation should have been restarted verify(mMockValueAnimator, times(2)).start(); - verify(mMockAms).notifyMagnificationChanged(displayId, - INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y); + verify(mMockAms, times(2)).notifyMagnificationChanged(eq(displayId), + eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture()); + assertConfigEquals(newConfig, mConfigCaptor.getValue()); // New starting point should be where we left off when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); @@ -1155,7 +1183,7 @@ public class FullScreenMagnificationControllerTest { Region regionArg = (Region) args[1]; regionArg.set(INITIAL_MAGNIFICATION_REGION); return null; - }).when(mMockWindowManager).getMagnificationRegion(anyInt(), (Region) anyObject()); + }).when(mMockWindowManager).getMagnificationRegion(anyInt(), any(Region.class)); } private void resetMockWindowManager() { @@ -1201,6 +1229,19 @@ public class FullScreenMagnificationControllerTest { magnifiedBounds.centerY() - scale * center.y); } + private MagnificationConfig buildConfig(float scale, float centerX, float centerY) { + return new MagnificationConfig.Builder().setMode( + MAGNIFICATION_MODE_FULLSCREEN).setScale(scale).setCenterX(centerX).setCenterY( + centerY).build(); + } + + private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig result) { + assertEquals(expected.getMode(), result.getMode()); + assertEquals(expected.getScale(), result.getScale(), 0f); + assertEquals(expected.getCenterX(), result.getCenterX(), 0f); + assertEquals(expected.getCenterY(), result.getCenterY(), 0f); + } + private MagnificationSpec getInterpolatedMagSpec(MagnificationSpec start, MagnificationSpec end, float fraction) { MagnificationSpec interpolatedSpec = new MagnificationSpec(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 0054fc328932..064b76243057 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -430,6 +430,21 @@ public class MagnificationControllerTest { } @Test + public void onSourceBoundsChanged_notifyMagnificationChanged() { + Rect rect = new Rect(0, 0, 100, 120); + Region region = new Region(rect); + + mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, rect); + + final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + verify(mService).notifyMagnificationChanged(eq(TEST_DISPLAY), eq(region), + configCaptor.capture()); + assertEquals(rect.exactCenterX(), configCaptor.getValue().getCenterX(), 0); + assertEquals(rect.exactCenterY(), configCaptor.getValue().getCenterY(), 0); + } + + @Test public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); @@ -464,6 +479,14 @@ public class MagnificationControllerTest { } @Test + public void setPreferenceMagnificationFollowTypingEnabled_setPrefDisabled_disableAll() { + mMagnificationController.setMagnificationFollowTypingEnabled(false); + + verify(mWindowMagnificationManager).setMagnificationFollowTypingEnabled(eq(false)); + verify(mScreenMagnificationController).setMagnificationFollowTypingEnabled(eq(false)); + } + + @Test public void onRectangleOnScreenRequested_fullScreenIsActivated_fullScreenDispatchEvent() { mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index a62c0d5e1eaf..8da513b50d65 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -274,6 +274,123 @@ public class WindowMagnificationManagerTest { } @Test + public void onRectangleOnScreenRequested_trackingDisabledByOnDrag_withoutMovingMagnification() + throws RemoteException { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + final Region outRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + final Rect requestedRect = outRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY), + eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), + eq(0f), eq(0f), notNull()); + } + + + @Test + public void onRectangleOnScreenRequested_trackingDisabledByScroll_withoutMovingMagnification() + throws RemoteException { + final float distanceX = 10f; + final float distanceY = 10f; + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + final Region outRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + final Rect requestedRect = outRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + mWindowMagnificationManager.processScroll(TEST_DISPLAY, distanceX, distanceY); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY), + eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), + eq(0f), eq(0f), notNull()); + } + + @Test + public void onRectangleOnScreenRequested_requestRectangleInBound_withoutMovingMagnification() + throws RemoteException { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + final Region outRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + final Rect requestedRect = outRegion.getBounds(); + requestedRect.inset(-10, -10); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + verify(mMockConnection.getConnection(), never()).enableWindowMagnification(eq(TEST_DISPLAY), + eq(3f), eq(500f), eq(500f), eq(0f), eq(0f), notNull()); + } + + @Test + public void onRectangleOnScreenRequested_trackingEnabledByDefault_movingMagnification() + throws RemoteException { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + final Region outRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + final Rect requestedRect = outRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f), + eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), + eq(0f), eq(0f), notNull()); + } + + @Test + public void onRectangleOnScreenRequested_trackingEnabledByDragAndReset_movingMagnification() + throws RemoteException { + final PointF initialPoint = new PointF(50f, 50f); + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, + initialPoint.x, initialPoint.y); + mMockConnection.getConnectionCallback().onDrag(TEST_DISPLAY); + mWindowMagnificationManager.onImeWindowVisibilityChanged(true); + final Region outRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, outRegion); + final Rect requestedRect = outRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), + eq(3f), eq(requestedRect.exactCenterX()), eq(requestedRect.exactCenterY()), + eq(0f), eq(0f), notNull()); + } + + @Test + public void onRectangleOnScreenRequested_followTypingIsDisabled_withoutMovingMagnification() + throws RemoteException { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, 50f, 50f); + final Region beforeRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, beforeRegion); + final Rect requestedRect = beforeRegion.getBounds(); + requestedRect.offsetTo(requestedRect.right + 10, requestedRect.bottom + 10); + mWindowMagnificationManager.setMagnificationFollowTypingEnabled(false); + + mWindowMagnificationManager.onRectangleOnScreenRequested(TEST_DISPLAY, + requestedRect.left, requestedRect.top, requestedRect.right, requestedRect.bottom); + + final Region afterRegion = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, afterRegion); + assertEquals(afterRegion, beforeRegion); + } + + @Test public void moveWindowMagnifier_enabled_invokeConnectionMethod() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, NaN, NaN); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java new file mode 100644 index 000000000000..d4bac2c0402d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import static android.testing.TestableLooper.RunWithLooper; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; + +import android.os.Handler; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +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.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +public class BiometricSchedulerOperationTest { + + public interface FakeHal {} + public abstract static class InterruptableMonitor<T> + extends HalClientMonitor<T> implements Interruptable { + public InterruptableMonitor() { + super(null, null, null, null, 0, null, 0, 0, 0, 0, 0); + } + } + + @Mock + private InterruptableMonitor<FakeHal> mClientMonitor; + @Mock + private BaseClientMonitor.Callback mClientCallback; + @Mock + private FakeHal mHal; + @Captor + ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback; + + private Handler mHandler; + private BiometricSchedulerOperation mOperation; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHandler = new Handler(TestableLooper.get(this).getLooper()); + mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback); + } + + @Test + public void testStartWithCookie() { + final int cookie = 200; + when(mClientMonitor.getCookie()).thenReturn(cookie); + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + assertThat(mOperation.isReadyToStart()).isEqualTo(cookie); + assertThat(mOperation.isStarted()).isFalse(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + + final boolean started = mOperation.startWithCookie( + mock(BaseClientMonitor.Callback.class), cookie); + + assertThat(started).isTrue(); + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + assertThat(mOperation.isStarted()).isTrue(); + } + + @Test + public void testNoStartWithoutCookie() { + final int goodCookie = 20; + final int badCookie = 22; + when(mClientMonitor.getCookie()).thenReturn(goodCookie); + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie); + final boolean started = mOperation.startWithCookie( + mock(BaseClientMonitor.Callback.class), badCookie); + + assertThat(started).isFalse(); + assertThat(mOperation.isStarted()).isFalse(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + } + + @Test + public void startsWhenReadyAndHalAvailable() { + when(mClientMonitor.getCookie()).thenReturn(0); + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + mOperation.start(cb); + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + + assertThat(mOperation.isStarted()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + + verify(mClientCallback).onClientStarted(eq(mClientMonitor)); + verify(cb).onClientStarted(eq(mClientMonitor)); + verify(mClientCallback, never()).onClientFinished(any(), anyBoolean()); + verify(cb, never()).onClientFinished(any(), anyBoolean()); + + mStartCallback.getValue().onClientFinished(mClientMonitor, true); + + assertThat(mOperation.isFinished()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + verify(mClientMonitor).destroy(); + verify(cb).onClientFinished(eq(mClientMonitor), eq(true)); + } + + @Test + public void startFailsWhenReadyButHalNotAvailable() { + when(mClientMonitor.getCookie()).thenReturn(0); + when(mClientMonitor.getFreshDaemon()).thenReturn(null); + + final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + mOperation.start(cb); + verify(mClientMonitor, never()).start(any()); + + assertThat(mOperation.isStarted()).isFalse(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isTrue(); + + verify(mClientCallback, never()).onClientStarted(eq(mClientMonitor)); + verify(cb, never()).onClientStarted(eq(mClientMonitor)); + verify(mClientCallback).onClientFinished(eq(mClientMonitor), eq(false)); + verify(cb).onClientFinished(eq(mClientMonitor), eq(false)); + } + + @Test + public void doesNotStartWithCookie() { + when(mClientMonitor.getCookie()).thenReturn(9); + assertThrows(IllegalStateException.class, + () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + } + + @Test + public void cannotRestart() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.start(mock(BaseClientMonitor.Callback.class)); + + assertThrows(IllegalStateException.class, + () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + } + + @Test + public void abortsNotRunning() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.abort(); + + assertThat(mOperation.isFinished()).isTrue(); + verify(mClientMonitor).unableToStart(); + verify(mClientMonitor).destroy(); + assertThrows(IllegalStateException.class, + () -> mOperation.start(mock(BaseClientMonitor.Callback.class))); + } + + @Test + public void cannotAbortRunning() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.start(mock(BaseClientMonitor.Callback.class)); + + assertThrows(IllegalStateException.class, () -> mOperation.abort()); + } + + @Test + public void cancel() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class); + final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class); + mOperation.start(startCb); + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + mOperation.cancel(mHandler, cancelCb); + + assertThat(mOperation.isCanceling()).isTrue(); + verify(mClientMonitor).cancel(); + verify(mClientMonitor, never()).cancelWithoutStarting(any()); + verify(mClientMonitor, never()).destroy(); + + mStartCallback.getValue().onClientFinished(mClientMonitor, true); + + assertThat(mOperation.isFinished()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + verify(mClientMonitor).destroy(); + + // should be unused since the operation was started + verify(cancelCb, never()).onClientStarted(any()); + verify(cancelCb, never()).onClientFinished(any(), anyBoolean()); + } + + @Test + public void cancelWithoutStarting() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class); + mOperation.cancel(mHandler, cancelCb); + + assertThat(mOperation.isCanceling()).isTrue(); + ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor = + ArgumentCaptor.forClass(BaseClientMonitor.Callback.class); + verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture()); + + cbCaptor.getValue().onClientFinished(mClientMonitor, true); + verify(cancelCb).onClientFinished(eq(mClientMonitor), eq(true)); + verify(mClientMonitor, never()).start(any()); + verify(mClientMonitor, never()).cancel(); + verify(mClientMonitor).destroy(); + } + + @Test + public void markCanceling() { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.markCanceling(); + + assertThat(mOperation.isMarkedCanceling()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + assertThat(mOperation.isFinished()).isFalse(); + verify(mClientMonitor, never()).start(any()); + verify(mClientMonitor, never()).cancel(); + verify(mClientMonitor, never()).cancelWithoutStarting(any()); + verify(mClientMonitor, never()).unableToStart(); + verify(mClientMonitor, never()).destroy(); + } + + @Test + public void cancelPendingWithCookie() { + markCancellingAndStart(2); + } + + @Test + public void cancelPendingWithoutCookie() { + markCancellingAndStart(null); + } + + private void markCancellingAndStart(Integer withCookie) { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + if (withCookie != null) { + when(mClientMonitor.getCookie()).thenReturn(withCookie); + } + + mOperation.markCanceling(); + final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class); + if (withCookie != null) { + mOperation.startWithCookie(cb, withCookie); + } else { + mOperation.start(cb); + } + + assertThat(mOperation.isFinished()).isTrue(); + verify(cb).onClientFinished(eq(mClientMonitor), eq(true)); + verify(mClientMonitor, never()).start(any()); + verify(mClientMonitor, never()).cancel(); + verify(mClientMonitor, never()).cancelWithoutStarting(any()); + verify(mClientMonitor, never()).unableToStart(); + verify(mClientMonitor).destroy(); + } + + @Test + public void cancelWatchdogWhenStarted() { + cancelWatchdog(true); + } + + @Test + public void cancelWatchdogWithoutStarting() { + cancelWatchdog(false); + } + + private void cancelWatchdog(boolean start) { + when(mClientMonitor.getFreshDaemon()).thenReturn(mHal); + + mOperation.start(mock(BaseClientMonitor.Callback.class)); + if (start) { + verify(mClientMonitor).start(mStartCallback.capture()); + mStartCallback.getValue().onClientStarted(mClientMonitor); + } + mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class)); + + assertThat(mOperation.isCanceling()).isTrue(); + + // omit call to onClientFinished and trigger watchdog + mOperation.mCancelWatchdog.run(); + + assertThat(mOperation.isFinished()).isTrue(); + assertThat(mOperation.isCanceling()).isFalse(); + verify(mClientMonitor).destroy(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index d192697827f6..ac0831983262 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -16,10 +16,14 @@ package com.android.server.biometrics.sensors; +import static android.testing.TestableLooper.RunWithLooper; + import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -34,10 +38,13 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricService; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; import android.testing.TestableContext; +import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -46,16 +53,18 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.nano.BiometricSchedulerProto; import com.android.server.biometrics.nano.BiometricsProto; -import com.android.server.biometrics.sensors.BiometricScheduler.Operation; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @Presubmit @SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper(setAsMainLooper = true) public class BiometricSchedulerTest { private static final String TAG = "BiometricSchedulerTest"; @@ -76,8 +85,9 @@ public class BiometricSchedulerTest { public void setUp() { MockitoAnnotations.initMocks(this); mToken = new Binder(); - mScheduler = new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_UNKNOWN, - null /* gestureAvailabilityTracker */, mBiometricService, LOG_NUM_RECENT_OPERATIONS, + mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()), + BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */, + mBiometricService, LOG_NUM_RECENT_OPERATIONS, CoexCoordinator.getInstance()); } @@ -86,9 +96,9 @@ public class BiometricSchedulerTest { final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); final HalClientMonitor<Object> client1 = - new TestClientMonitor(mContext, mToken, nonNullDaemon); + new TestHalClientMonitor(mContext, mToken, nonNullDaemon); final HalClientMonitor<Object> client2 = - new TestClientMonitor(mContext, mToken, nonNullDaemon); + new TestHalClientMonitor(mContext, mToken, nonNullDaemon); mScheduler.scheduleClientMonitor(client1); mScheduler.scheduleClientMonitor(client2); @@ -99,20 +109,17 @@ public class BiometricSchedulerTest { @Test public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() { // Even if second client has a non-null daemon, it needs to be canceled. - Object daemon2 = mock(Object.class); - - final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null; - final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2; - - final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon1); - final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); + final TestHalClientMonitor client1 = new TestHalClientMonitor( + mContext, mToken, () -> null); + final TestHalClientMonitor client2 = new TestHalClientMonitor( + mContext, mToken, () -> mock(Object.class)); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); // Pretend the scheduler is busy so the first operation doesn't start right away. We want // to pretend like there are two operations in the queue before kicking things off - mScheduler.mCurrentOperation = new BiometricScheduler.Operation( + mScheduler.mCurrentOperation = new BiometricSchedulerOperation( mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); mScheduler.scheduleClientMonitor(client1, callback1); @@ -122,11 +129,11 @@ public class BiometricSchedulerTest { mScheduler.scheduleClientMonitor(client2, callback2); waitForIdle(); - assertTrue(client1.wasUnableToStart()); + assertTrue(client1.mUnableToStart); verify(callback1).onClientFinished(eq(client1), eq(false) /* success */); verify(callback1, never()).onClientStarted(any()); - assertTrue(client2.wasUnableToStart()); + assertTrue(client2.mUnableToStart); verify(callback2).onClientFinished(eq(client2), eq(false) /* success */); verify(callback2, never()).onClientStarted(any()); @@ -138,21 +145,19 @@ public class BiometricSchedulerTest { // Second non-BiometricPrompt client has a valid daemon final Object daemon2 = mock(Object.class); - final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null; - final HalClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2; - final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class); final TestAuthenticationClient client1 = - new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1); - final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); + new TestAuthenticationClient(mContext, () -> null, mToken, listener1); + final TestHalClientMonitor client2 = + new TestHalClientMonitor(mContext, mToken, () -> daemon2); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class); // Pretend the scheduler is busy so the first operation doesn't start right away. We want // to pretend like there are two operations in the queue before kicking things off - mScheduler.mCurrentOperation = new BiometricScheduler.Operation( + mScheduler.mCurrentOperation = new BiometricSchedulerOperation( mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class)); mScheduler.scheduleClientMonitor(client1, callback1); @@ -172,8 +177,8 @@ public class BiometricSchedulerTest { verify(callback1, never()).onClientStarted(any()); // Client 2 was able to start - assertFalse(client2.wasUnableToStart()); - assertTrue(client2.hasStarted()); + assertFalse(client2.mUnableToStart); + assertTrue(client2.mStarted); verify(callback2).onClientStarted(eq(client2)); } @@ -187,16 +192,18 @@ public class BiometricSchedulerTest { // Schedule a BiometricPrompt authentication request mScheduler.scheduleClientMonitor(client1, callback1); - assertEquals(Operation.STATE_WAITING_FOR_COOKIE, mScheduler.mCurrentOperation.mState); - assertEquals(client1, mScheduler.mCurrentOperation.mClientMonitor); + assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart()); + assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor()); assertEquals(0, mScheduler.mPendingOperations.size()); // Request it to be canceled. The operation can be canceled immediately, and the scheduler // should go back to idle, since in this case the framework has not even requested the HAL // to authenticate yet. mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); + waitForIdle(); assertTrue(client1.isAlreadyDone()); assertTrue(client1.mDestroyed); + assertFalse(client1.mStartedHal); assertNull(mScheduler.mCurrentOperation); } @@ -210,8 +217,8 @@ public class BiometricSchedulerTest { // assertEquals(0, bsp.recentOperations.length); // Pretend the scheduler is busy enrolling, and check the proto dump again. - final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_ENROLL); + final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL); mScheduler.scheduleClientMonitor(client); waitForIdle(); bsp = getDump(true /* clearSchedulerBuffer */); @@ -230,8 +237,8 @@ public class BiometricSchedulerTest { @Test public void testProtoDump_fifo() throws Exception { // Add the first operation - final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_ENROLL); + final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL); mScheduler.scheduleClientMonitor(client); waitForIdle(); BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */); @@ -244,8 +251,8 @@ public class BiometricSchedulerTest { client.getCallback().onClientFinished(client, true); // Add another operation - final TestClientMonitor2 client2 = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_REMOVE); + final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE); mScheduler.scheduleClientMonitor(client2); waitForIdle(); bsp = getDump(false /* clearSchedulerBuffer */); @@ -256,8 +263,8 @@ public class BiometricSchedulerTest { client2.getCallback().onClientFinished(client2, true); // And another operation - final TestClientMonitor2 client3 = new TestClientMonitor2(mContext, mToken, - () -> mock(Object.class), BiometricsProto.CM_AUTHENTICATE); + final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken, + () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE); mScheduler.scheduleClientMonitor(client3); waitForIdle(); bsp = getDump(false /* clearSchedulerBuffer */); @@ -290,8 +297,7 @@ public class BiometricSchedulerTest { @Test public void testCancelPendingAuth() throws RemoteException { final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); - - final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon); + final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon); final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback); @@ -302,14 +308,12 @@ public class BiometricSchedulerTest { waitForIdle(); assertEquals(mScheduler.getCurrentClient(), client1); - assertEquals(Operation.STATE_WAITING_IN_QUEUE, - mScheduler.mPendingOperations.getFirst().mState); + assertFalse(mScheduler.mPendingOperations.getFirst().isStarted()); // Request cancel before the authentication client has started mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); waitForIdle(); - assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING, - mScheduler.mPendingOperations.getFirst().mState); + assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling()); // Finish the blocking client. The authentication client should send ERROR_CANCELED client1.getCallback().onClientFinished(client1, true /* success */); @@ -326,67 +330,109 @@ public class BiometricSchedulerTest { @Test public void testCancels_whenAuthRequestIdNotSet() { - testCancelsWhenRequestId(null /* requestId */, 2, true /* started */); + testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */); } @Test public void testCancels_whenAuthRequestIdNotSet_notStarted() { - testCancelsWhenRequestId(null /* requestId */, 2, false /* started */); + testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */); } @Test public void testCancels_whenAuthRequestIdMatches() { - testCancelsWhenRequestId(200L, 200, true /* started */); + testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */); } @Test public void testCancels_whenAuthRequestIdMatches_noStarted() { - testCancelsWhenRequestId(200L, 200, false /* started */); + testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */); } @Test public void testDoesNotCancel_whenAuthRequestIdMismatched() { - testCancelsWhenRequestId(10L, 20, true /* started */); + testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */); } @Test public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() { - testCancelsWhenRequestId(10L, 20, false /* started */); + testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */); } - private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId, + private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId, boolean started) { - final boolean matches = requestId == null || requestId == cancelRequestId; final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); - final TestAuthenticationClient client = new TestAuthenticationClient( - mContext, lazyDaemon, mToken, callback); + testCancelsWhenRequestId(requestId, cancelRequestId, started, + new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback)); + } + + @Test + public void testCancels_whenEnrollRequestIdNotSet() { + testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */); + } + + @Test + public void testCancels_whenEnrollRequestIdMatches() { + testCancelsEnrollWhenRequestId(200L, 200, false /* started */); + } + + @Test + public void testDoesNotCancel_whenEnrollRequestIdMismatched() { + testCancelsEnrollWhenRequestId(10L, 20, false /* started */); + } + + private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId, + boolean started) { + final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); + final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); + testCancelsWhenRequestId(requestId, cancelRequestId, started, + new TestEnrollClient(mContext, lazyDaemon, mToken, callback)); + } + + private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId, + boolean started, HalClientMonitor<?> client) { + final boolean matches = requestId == null || requestId == cancelRequestId; if (requestId != null) { client.setRequestId(requestId); } + final boolean isAuth = client instanceof TestAuthenticationClient; + final boolean isEnroll = client instanceof TestEnrollClient; + mScheduler.scheduleClientMonitor(client); if (started) { mScheduler.startPreparedClient(client.getCookie()); } waitForIdle(); - mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId); + if (isAuth) { + mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId); + } else if (isEnroll) { + mScheduler.cancelEnrollment(mToken, cancelRequestId); + } else { + fail("unexpected operation type"); + } waitForIdle(); - assertEquals(matches && started ? 1 : 0, client.mNumCancels); + if (isAuth) { + // auth clients that were waiting for cookie when canceled should never invoke the hal + final TestAuthenticationClient authClient = (TestAuthenticationClient) client; + assertEquals(matches && started ? 1 : 0, authClient.mNumCancels); + assertEquals(started, authClient.mStartedHal); + } else if (isEnroll) { + final TestEnrollClient enrollClient = (TestEnrollClient) client; + assertEquals(matches ? 1 : 0, enrollClient.mNumCancels); + assertTrue(enrollClient.mStartedHal); + } if (matches) { - if (started) { - assertEquals(Operation.STATE_STARTED_CANCELING, - mScheduler.mCurrentOperation.mState); + if (started || isEnroll) { // prep'd auth clients and enroll clients + assertTrue(mScheduler.mCurrentOperation.isCanceling()); } } else { - if (started) { - assertEquals(Operation.STATE_STARTED, - mScheduler.mCurrentOperation.mState); + if (started || isEnroll) { // prep'd auth clients and enroll clients + assertTrue(mScheduler.mCurrentOperation.isStarted()); } else { - assertEquals(Operation.STATE_WAITING_FOR_COOKIE, - mScheduler.mCurrentOperation.mState); + assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart()); } } } @@ -411,18 +457,14 @@ public class BiometricSchedulerTest { mScheduler.cancelAuthenticationOrDetection(mToken, 9999); waitForIdle(); - assertEquals(Operation.STATE_STARTED, - mScheduler.mCurrentOperation.mState); - assertEquals(Operation.STATE_WAITING_IN_QUEUE, - mScheduler.mPendingOperations.getFirst().mState); + assertTrue(mScheduler.mCurrentOperation.isStarted()); + assertFalse(mScheduler.mPendingOperations.getFirst().isStarted()); mScheduler.cancelAuthenticationOrDetection(mToken, requestId2); waitForIdle(); - assertEquals(Operation.STATE_STARTED, - mScheduler.mCurrentOperation.mState); - assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING, - mScheduler.mPendingOperations.getFirst().mState); + assertTrue(mScheduler.mCurrentOperation.isStarted()); + assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling()); } @Test @@ -459,12 +501,12 @@ public class BiometricSchedulerTest { @Test public void testClientDestroyed_afterFinish() { final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class); - final TestClientMonitor client = - new TestClientMonitor(mContext, mToken, nonNullDaemon); + final TestHalClientMonitor client = + new TestHalClientMonitor(mContext, mToken, nonNullDaemon); mScheduler.scheduleClientMonitor(client); client.mCallback.onClientFinished(client, true /* success */); waitForIdle(); - assertTrue(client.wasDestroyed()); + assertTrue(client.mDestroyed); } private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { @@ -472,8 +514,10 @@ public class BiometricSchedulerTest { } private static class TestAuthenticationClient extends AuthenticationClient<Object> { - int mNumCancels = 0; + boolean mStartedHal = false; + boolean mStoppedHal = false; boolean mDestroyed = false; + int mNumCancels = 0; public TestAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @@ -488,18 +532,16 @@ public class BiometricSchedulerTest { @Override protected void stopHalOperation() { - + mStoppedHal = true; } @Override protected void startHalOperation() { - + mStartedHal = true; } @Override - protected void handleLifecycleAfterAuth(boolean authenticated) { - - } + protected void handleLifecycleAfterAuth(boolean authenticated) {} @Override public boolean wasUserDetected() { @@ -519,36 +561,59 @@ public class BiometricSchedulerTest { } } - private static class TestClientMonitor2 extends TestClientMonitor { - private final int mProtoEnum; + private static class TestEnrollClient extends EnrollClient<Object> { + boolean mStartedHal = false; + boolean mStoppedHal = false; + int mNumCancels = 0; - public TestClientMonitor2(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, int protoEnum) { - super(context, token, lazyDaemon); - mProtoEnum = protoEnum; + TestEnrollClient(@NonNull Context context, + @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener) { + super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69], + "test" /* owner */, mock(BiometricUtils.class), + 5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID, + true /* shouldVibrate */); } @Override - public int getProtoEnum() { - return mProtoEnum; + protected void stopHalOperation() { + mStoppedHal = true; + } + + @Override + protected void startHalOperation() { + mStartedHal = true; + } + + @Override + protected boolean hasReachedEnrollmentLimit() { + return false; + } + + @Override + public void cancel() { + mNumCancels++; + super.cancel(); } } - private static class TestClientMonitor extends HalClientMonitor<Object> { + private static class TestHalClientMonitor extends HalClientMonitor<Object> { + private final int mProtoEnum; private boolean mUnableToStart; private boolean mStarted; private boolean mDestroyed; - public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, + TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull LazyDaemon<Object> lazyDaemon) { - this(context, token, lazyDaemon, 0 /* cookie */); + this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER); } - public TestClientMonitor(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, int cookie) { + TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, + @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) { super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */, 0 /* statsAction */, 0 /* statsClient */); + mProtoEnum = protoEnum; } @Override @@ -559,9 +624,7 @@ public class BiometricSchedulerTest { @Override public int getProtoEnum() { - // Anything other than CM_NONE, which is used to represent "idle". Tests that need - // real proto enums should use TestClientMonitor2 - return BiometricsProto.CM_UPDATE_ACTIVE_USER; + return mProtoEnum; } @Override @@ -573,7 +636,7 @@ public class BiometricSchedulerTest { @Override protected void startHalOperation() { - + mStarted = true; } @Override @@ -581,22 +644,9 @@ public class BiometricSchedulerTest { super.destroy(); mDestroyed = true; } - - public boolean wasUnableToStart() { - return mUnableToStart; - } - - public boolean hasStarted() { - return mStarted; - } - - public boolean wasDestroyed() { - return mDestroyed; - } - } - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + private void waitForIdle() { + TestableLooper.get(this).processAllMessages(); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java index 7fccd49db04b..407f5fb04adf 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors; +import static android.testing.TestableLooper.RunWithLooper; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -28,52 +30,53 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.biometrics.IBiometricService; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @Presubmit +@RunWith(AndroidTestingRunner.class) +@RunWithLooper @SmallTest public class UserAwareBiometricSchedulerTest { - private static final String TAG = "BiometricSchedulerTest"; + private static final String TAG = "UserAwareBiometricSchedulerTest"; private static final int TEST_SENSOR_ID = 0; + private Handler mHandler; private UserAwareBiometricScheduler mScheduler; - private IBinder mToken; + private IBinder mToken = new Binder(); @Mock private Context mContext; @Mock private IBiometricService mBiometricService; - private TestUserStartedCallback mUserStartedCallback; - private TestUserStoppedCallback mUserStoppedCallback; + private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback(); + private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback(); private int mCurrentUserId = UserHandle.USER_NULL; - private boolean mStartOperationsFinish; - private int mStartUserClientCount; + private boolean mStartOperationsFinish = true; + private int mStartUserClientCount = 0; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mToken = new Binder(); - mStartOperationsFinish = true; - mStartUserClientCount = 0; - mUserStartedCallback = new TestUserStartedCallback(); - mUserStoppedCallback = new TestUserStoppedCallback(); - + mHandler = new Handler(TestableLooper.get(this).getLooper()); mScheduler = new UserAwareBiometricScheduler(TAG, + mHandler, BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityDispatcher */, mBiometricService, @@ -117,7 +120,7 @@ public class UserAwareBiometricSchedulerTest { mCurrentUserId = UserHandle.USER_NULL; mStartOperationsFinish = false; - final BaseClientMonitor[] nextClients = new BaseClientMonitor[] { + final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{ mock(BaseClientMonitor.class), mock(BaseClientMonitor.class), mock(BaseClientMonitor.class) @@ -147,11 +150,11 @@ public class UserAwareBiometricSchedulerTest { waitForIdle(); final TestStartUserClient startUserClient = - (TestStartUserClient) mScheduler.mCurrentOperation.mClientMonitor; + (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor(); mScheduler.reset(); assertNull(mScheduler.mCurrentOperation); - final BiometricScheduler.Operation fakeOperation = new BiometricScheduler.Operation( + final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation( mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {}); mScheduler.mCurrentOperation = fakeOperation; startUserClient.mCallback.onClientFinished(startUserClient, true); @@ -194,8 +197,8 @@ public class UserAwareBiometricSchedulerTest { verify(nextClient).start(any()); } - private static void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + private void waitForIdle() { + TestableLooper.get(this).processAllMessages(); } private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index a13dff21439d..0891eca9f61c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -79,6 +79,7 @@ public class SensorTest { when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); mScheduler = new UserAwareBiometricScheduler(TAG, + new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */, () -> USER_ID, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java index 39c51d5f5e5e..21a7a8ae65b9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java @@ -32,7 +32,9 @@ import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceServiceReceiver; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -69,6 +71,7 @@ public class Face10Test { @Mock private BiometricScheduler mScheduler; + private final Handler mHandler = new Handler(Looper.getMainLooper()); private LockoutResetDispatcher mLockoutResetDispatcher; private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10; private IBinder mBinder; @@ -97,7 +100,7 @@ public class Face10Test { resetLockoutRequiresChallenge); Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST")); - mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mScheduler); + mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler); mBinder = new Binder(); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 0d520ca9a4e4..a012b8b06c7f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -79,6 +79,7 @@ public class SensorTest { when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService); mScheduler = new UserAwareBiometricScheduler(TAG, + new Handler(mLooper.getLooper()), BiometricScheduler.SENSOR_TYPE_FP_OTHER, null /* gestureAvailabilityDispatcher */, () -> USER_ID, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 419dda57c568..5fcb8029af31 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -22,7 +22,10 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.Notification.FLAG_AUTO_CANCEL; import static android.app.Notification.FLAG_BUBBLE; +import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; @@ -179,6 +182,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -466,6 +470,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Return first true for RoleObserver main-thread check when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper); + verify(mHistoryManager, never()).onBootPhaseAppsCanStart(); + mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper); + verify(mHistoryManager).onBootPhaseAppsCanStart(); mService.setAudioManager(mAudioManager); @@ -1437,6 +1444,62 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testEnqueueNotificationWithTag_FgsAddsFlags_dismissalAllowed() throws Exception { + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, + "true", + false); + Thread.sleep(300); + + final String tag = "testEnqueueNotificationWithTag_FgsAddsFlags_dismissalAllowed"; + + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFlag(FLAG_FOREGROUND_SERVICE, true) + .build(); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, tag, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(PKG); + assertThat(notifs[0].getNotification().flags).isEqualTo( + FLAG_FOREGROUND_SERVICE | FLAG_CAN_COLORIZE | FLAG_NO_CLEAR); + } + + @Test + public void testEnqueueNotificationWithTag_FGSaddsFlags_dismissalNotAllowed() throws Exception { + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, + "false", + false); + Thread.sleep(300); + + final String tag = "testEnqueueNotificationWithTag_FGSaddsNoClear"; + + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFlag(FLAG_FOREGROUND_SERVICE, true) + .build(); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(PKG); + assertThat(notifs[0].getNotification().flags).isEqualTo( + FLAG_FOREGROUND_SERVICE | FLAG_CAN_COLORIZE | FLAG_NO_CLEAR | FLAG_ONGOING_EVENT); + } + + @Test public void testCancelNonexistentNotification() throws Exception { mBinderService.cancelNotificationWithTag(PKG, PKG, "testCancelNonexistentNotification", 0, 0); @@ -1757,21 +1820,152 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelAllCancelNotificationsFromListener_NoClearFlag() throws Exception { + public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception { + Notification n = + new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mInternalService.removeForegroundServiceFlagFromNotification(PKG, sbn.getId(), + sbn.getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertEquals(0, notifs[0].getNotification().flags & FLAG_FOREGROUND_SERVICE); + } + + @Test + public void testCancelAfterSecondEnqueueDoesNotSpecifyForegroundFlag() throws Exception { + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); + sbn.getNotification().flags = + Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE; + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT; + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), + sbn.getUserId()); + waitForIdle(); + assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length); + assertEquals(0, mService.getNotificationRecordCount()); + } + + @Test + public void testCancelNotificationWithTag_fromApp_cannotCancelFgsChild() + throws Exception { + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.getBinderService().cancelNotificationWithTag( + parent.getSbn().getPackageName(), parent.getSbn().getPackageName(), + parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationWithTag_fromApp_cannotCancelFgsParent() + throws Exception { + mService.isSystemUid = false; final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_FOREGROUND_SERVICE; final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); final NotificationRecord child2 = generateNotificationRecord( mTestNotificationChannel, 3, "group", false); - child2.getNotification().flags |= Notification.FLAG_NO_CLEAR; + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.getBinderService().cancelNotificationWithTag( + parent.getSbn().getPackageName(), parent.getSbn().getPackageName(), + parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(3, notifs.length); + } + + @Test + public void testCancelNotificationWithTag_fromApp_canCancelOngoingNoClearChild() + throws Exception { + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.getBinderService().cancelNotificationWithTag( + parent.getSbn().getPackageName(), parent.getSbn().getPackageName(), + parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationWithTag_fromApp_canCancelOngoingNoClearParent() + throws Exception { + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.getBinderService().cancelNotificationWithTag( + parent.getSbn().getPackageName(), parent.getSbn().getPackageName(), + parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelAllNotificationsFromApp_cannotCancelFgsChild() + throws Exception { + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE; final NotificationRecord newGroup = generateNotificationRecord( mTestNotificationChannel, 4, "group2", false); mService.addNotification(parent); mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - mService.getBinderService().cancelNotificationsFromListener(null, null); + mService.getBinderService().cancelAllNotifications( + parent.getSbn().getPackageName(), parent.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); @@ -1779,22 +1973,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testUserInitiatedCancelAllWithGroup_NoClearFlag() throws Exception { + public void testCancelAllNotifications_fromApp_cannotCancelFgsParent() + throws Exception { + mService.isSystemUid = false; final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_FOREGROUND_SERVICE; final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); final NotificationRecord child2 = generateNotificationRecord( mTestNotificationChannel, 3, "group", false); - child2.getNotification().flags |= Notification.FLAG_NO_CLEAR; final NotificationRecord newGroup = generateNotificationRecord( mTestNotificationChannel, 4, "group2", false); mService.addNotification(parent); mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), - parent.getUserId()); + mService.getBinderService().cancelAllNotifications( + parent.getSbn().getPackageName(), parent.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); @@ -1802,43 +1998,126 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception { - Notification n = - new Notification.Builder(mContext, mTestNotificationChannel.getId()) - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .build(); - StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0, - n, UserHandle.getUserHandleForUid(mUid), null, 0); - sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - mBinderService.enqueueNotificationWithTag(PKG, PKG, null, - sbn.getId(), sbn.getNotification(), sbn.getUserId()); - mInternalService.removeForegroundServiceFlagFromNotification(PKG, sbn.getId(), - sbn.getUserId()); + public void testCancelAllNotifications_fromApp_canCancelOngoingNoClearChild() + throws Exception { + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelAllNotifications( + parent.getSbn().getPackageName(), parent.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(sbn.getPackageName()); - assertEquals(0, notifs[0].getNotification().flags & FLAG_FOREGROUND_SERVICE); + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); } @Test - public void testCancelAfterSecondEnqueueDoesNotSpecifyForegroundFlag() throws Exception { - final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); - sbn.getNotification().flags = - Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE; - mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - sbn.getId(), sbn.getNotification(), sbn.getUserId()); - sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT; - mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - sbn.getId(), sbn.getNotification(), sbn.getUserId()); - mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), - sbn.getUserId()); + public void testCancelAllNotifications_fromApp_canCancelOngoingNoClearParent() + throws Exception { + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelAllNotifications( + parent.getSbn().getPackageName(), parent.getSbn().getUserId()); waitForIdle(); - assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length); - assertEquals(0, mService.getNotificationRecordCount()); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_GroupWithOngoingParent() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_ONGOING_EVENT; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_GroupWithOngoingChild() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_ONGOING_EVENT; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_GroupWithFgsParent() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); } @Test - public void testCancelAllCancelNotificationsFromListener_ForegroundServiceFlag() + public void testCancelNotificationsFromListener_clearAll_GroupWithFgsChild() throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); @@ -1861,15 +2140,104 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelAllCancelNotificationsFromListener_ForegroundServiceFlagWithParameter() + public void testCancelNotificationsFromListener_clearAll_GroupWithNoClearParent() throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_NO_CLEAR; final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); final NotificationRecord child2 = generateNotificationRecord( mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_GroupWithNoClearChild() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_NO_CLEAR; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_Ongoing() + throws Exception { + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child2.getNotification().flags |= FLAG_ONGOING_EVENT; + mService.addNotification(child2); + String[] keys = {child2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_NoClear() + throws Exception { + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child2.getNotification().flags |= FLAG_NO_CLEAR; + mService.addNotification(child2); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_Fgs() + throws Exception { + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + mService.addNotification(child2); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_GroupWithOngoingParent() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_ONGOING_EVENT; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); final NotificationRecord newGroup = generateNotificationRecord( mTestNotificationChannel, 4, "group2", false); mService.addNotification(parent); @@ -1886,7 +2254,58 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testUserInitiatedCancelAllWithGroup_ForegroundServiceFlag() throws Exception { + public void testCancelNotificationsFromListener_byKey_GroupWithOngoingChild() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_ONGOING_EVENT; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_GroupWithFgsParent() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_GroupWithFgsChild() + throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); final NotificationRecord child = generateNotificationRecord( @@ -1900,8 +2319,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), - parent.getUserId()); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_GroupWithNoClearParent() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_NO_CLEAR; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); @@ -1909,6 +2354,76 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testCancelNotificationsFromListener_byKey_GroupWithNoClearChild() + throws Exception { + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_NO_CLEAR; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_Ongoing() + throws Exception { + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child2.getNotification().flags |= FLAG_ONGOING_EVENT; + mService.addNotification(child2); + String[] keys = {child2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_NoClear() + throws Exception { + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child2.getNotification().flags |= FLAG_NO_CLEAR; + mService.addNotification(child2); + String[] keys = {child2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_Fgs() + throws Exception { + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE; + mService.addNotification(child2); + String[] keys = {child2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test public void testGroupInstanceIds() throws Exception { final NotificationRecord group1 = generateNotificationRecord( mTestNotificationChannel, 1, "group1", true); @@ -1973,7 +2488,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelAllNotifications_CancelsNoClearFlagOnGoing() throws Exception { + public void testCancelAllNotificationsInt_CancelsNoClearFlagOnGoing() throws Exception { final NotificationRecord notif = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); notif.getNotification().flags |= Notification.FLAG_NO_CLEAR; @@ -1987,32 +2502,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelAllCancelNotificationsFromListener_NoClearFlagWithParameter() - throws Exception { - final NotificationRecord parent = generateNotificationRecord( - mTestNotificationChannel, 1, "group", true); - final NotificationRecord child = generateNotificationRecord( - mTestNotificationChannel, 2, "group", false); - final NotificationRecord child2 = generateNotificationRecord( - mTestNotificationChannel, 3, "group", false); - child2.getNotification().flags |= Notification.FLAG_NO_CLEAR; - final NotificationRecord newGroup = generateNotificationRecord( - mTestNotificationChannel, 4, "group2", false); - mService.addNotification(parent); - mService.addNotification(child); - mService.addNotification(child2); - mService.addNotification(newGroup); - String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), - child2.getSbn().getKey(), newGroup.getSbn().getKey()}; - mService.getBinderService().cancelNotificationsFromListener(null, keys); - waitForIdle(); - StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); - assertEquals(0, notifs.length); - } - - @Test - public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception { + public void testAppInitiatedCancelAllNotifications_CancelsOngoingFlag() throws Exception { final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; mBinderService.enqueueNotificationWithTag(PKG, PKG, @@ -2026,7 +2516,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelAllNotifications_CancelsOnGoingFlag() throws Exception { + public void testCancelAllNotificationsInt_CancelsOngoingFlag() throws Exception { final NotificationRecord notif = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; @@ -2040,22 +2530,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testUserInitiatedCancelAllOnClearAll_OnGoingFlag() throws Exception { - final NotificationRecord notif = generateNotificationRecord( - mTestNotificationChannel, 1, "group", true); - notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - mService.addNotification(notif); - - mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), - notif.getUserId()); - waitForIdle(); - StatusBarNotification[] notifs = - mBinderService.getActiveNotifications(notif.getSbn().getPackageName()); - assertEquals(1, notifs.length); - } - - @Test - public void testCancelAllCancelNotificationsFromListener_OnGoingFlag() throws Exception { + public void testUserInitiatedCancelAllWithGroup_OngoingFlag() throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); final NotificationRecord child = generateNotificationRecord( @@ -2069,7 +2544,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - mService.getBinderService().cancelNotificationsFromListener(null, null); + mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), + parent.getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); @@ -2077,39 +2553,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelAllCancelNotificationsFromListener_OnGoingFlagWithParameter() - throws Exception { + public void testUserInitiatedCancelAllWithGroup_NoClearFlag() throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); final NotificationRecord child2 = generateNotificationRecord( mTestNotificationChannel, 3, "group", false); - child2.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; + child2.getNotification().flags |= Notification.FLAG_NO_CLEAR; final NotificationRecord newGroup = generateNotificationRecord( mTestNotificationChannel, 4, "group2", false); mService.addNotification(parent); mService.addNotification(child); mService.addNotification(child2); mService.addNotification(newGroup); - String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), - child2.getSbn().getKey(), newGroup.getSbn().getKey()}; - mService.getBinderService().cancelNotificationsFromListener(null, keys); + mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), + parent.getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); - assertEquals(0, notifs.length); + assertEquals(1, notifs.length); } @Test - public void testUserInitiatedCancelAllWithGroup_OnGoingFlag() throws Exception { + public void testUserInitiatedCancelAllWithGroup_ForegroundServiceFlag() throws Exception { final NotificationRecord parent = generateNotificationRecord( mTestNotificationChannel, 1, "group", true); final NotificationRecord child = generateNotificationRecord( mTestNotificationChannel, 2, "group", false); final NotificationRecord child2 = generateNotificationRecord( mTestNotificationChannel, 3, "group", false); - child2.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; + child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE; final NotificationRecord newGroup = generateNotificationRecord( mTestNotificationChannel, 4, "group2", false); mService.addNotification(parent); @@ -2121,7 +2595,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); - assertEquals(1, notifs.length); + assertEquals(0, notifs.length); } @Test @@ -3396,7 +3870,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mTestNotificationChannel.getId()) .setContentTitle("foo") .setColorized(true).setColor(Color.WHITE) - .setFlag(Notification.FLAG_CAN_COLORIZE, true) + .setFlag(FLAG_CAN_COLORIZE, true) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "testNoFakeColorizedPermission", mUid, 0, @@ -7469,17 +7943,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnBootPhase() { - mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); - - verify(mHistoryManager, never()).onBootPhaseAppsCanStart(); - - mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); - - verify(mHistoryManager, times(1)).onBootPhaseAppsCanStart(); - } - - @Test public void testHandleOnPackageChanged() { String[] pkgs = new String[] {PKG, PKG_N_MR1}; int[] uids = new int[] {mUid, UserHandle.PER_USER_RANGE + 1}; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 07d467bc07d5..ea5bf52af905 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -291,7 +291,8 @@ public class InsetsPolicyTest extends WindowTestsBase { assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState() .getSource(ITYPE_NAVIGATION_BAR).isVisible()); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -319,7 +320,8 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -351,7 +353,8 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); waitUntilWindowAnimatorIdle(); InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); @@ -402,7 +405,8 @@ public class InsetsPolicyTest extends WindowTestsBase { final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); - policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}); + policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}, + true /* isGestureOnSystemBar */); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(app); policy.updateBarControlTarget(app2); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index f8c84df53749..94bc7f2dc0d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -332,7 +332,8 @@ public class InsetsStateControllerTest extends WindowTestsBase { assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible()); provider.getSource().setVisible(false); - mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR }); + mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR }, + true /* isGestureOnSystemBar */); assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR)); assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible()); diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java index 0c65cc40bd82..286cff90daab 100644 --- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java +++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java @@ -688,6 +688,8 @@ class UsbUserPermissionManager { String packageName, PendingIntent pi, int uid) { + boolean throwException = false; + // compare uid with packageName to foil apps pretending to be someone else try { ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0); @@ -695,11 +697,13 @@ class UsbUserPermissionManager { Slog.w(TAG, "package " + packageName + " does not match caller's uid " + uid); EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, ""); - throw new IllegalArgumentException("package " + packageName - + " not found"); + throwException = true; } } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("package " + packageName + " not found"); + throwException = true; + } finally { + if (throwException) + throw new IllegalArgumentException("package " + packageName + " not found"); } requestPermissionDialog(device, accessory, canBeDefault, packageName, uid, mContext, pi); diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index 7861b11158cd..37b4e657973b 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -632,8 +632,9 @@ public abstract class CallScreeningService extends Service { * post-dial digits are passed. * <p> * Calls with a {@link Call.Details#getHandlePresentation()} of - * {@link TelecomManager#PRESENTATION_RESTRICTED}, {@link TelecomManager#PRESENTATION_UNKNOWN} - * or {@link TelecomManager#PRESENTATION_PAYPHONE} presentation are not provided to the + * {@link TelecomManager#PRESENTATION_RESTRICTED}, {@link TelecomManager#PRESENTATION_UNKNOWN}, + * {@link TelecomManager#PRESENTATION_UNAVAILABLE} or + * {@link TelecomManager#PRESENTATION_PAYPHONE} presentation are not provided to the * {@link CallScreeningService}. * * @param callDetails Information about a new call, see {@link Call.Details}. diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 0dc899e392a7..6279bf88ab1c 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -990,6 +990,11 @@ public class TelecomManager { */ public static final int PRESENTATION_PAYPHONE = 4; + /** + * Indicates that the address or number of a call is unavailable. + */ + public static final int PRESENTATION_UNAVAILABLE = 5; + /* * Values for the adb property "persist.radio.videocall.audio.output" @@ -1006,7 +1011,7 @@ public class TelecomManager { @IntDef( prefix = { "PRESENTATION_" }, value = {PRESENTATION_ALLOWED, PRESENTATION_RESTRICTED, PRESENTATION_UNKNOWN, - PRESENTATION_PAYPHONE}) + PRESENTATION_PAYPHONE, PRESENTATION_UNAVAILABLE}) public @interface Presentation {} diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java index fa70c33965ed..d77bf672347a 100644 --- a/telephony/java/android/telephony/CallQuality.java +++ b/telephony/java/android/telephony/CallQuality.java @@ -81,6 +81,13 @@ public final class CallQuality implements Parcelable { private boolean mRtpInactivityDetected; private boolean mRxSilenceDetected; private boolean mTxSilenceDetected; + private int mNumVoiceFrames; + private int mNumNoDataFrames; + private int mNumDroppedRtpPackets; + private long mMinPlayoutDelayMillis; + private long mMaxPlayoutDelayMillis; + private int mNumRtpSidPacketsRx; + private int mNumRtpDuplicatePackets; /** @hide **/ public CallQuality(Parcel in) { @@ -98,6 +105,13 @@ public final class CallQuality implements Parcelable { mRtpInactivityDetected = in.readBoolean(); mRxSilenceDetected = in.readBoolean(); mTxSilenceDetected = in.readBoolean(); + mNumVoiceFrames = in.readInt(); + mNumNoDataFrames = in.readInt(); + mNumDroppedRtpPackets = in.readInt(); + mMinPlayoutDelayMillis = in.readLong(); + mMaxPlayoutDelayMillis = in.readLong(); + mNumRtpSidPacketsRx = in.readInt(); + mNumRtpDuplicatePackets = in.readInt(); } /** @hide **/ @@ -298,6 +312,59 @@ public final class CallQuality implements Parcelable { } /** + * Returns the number of Voice frames sent by jitter buffer to audio + */ + public int getNumVoiceFrames() { + return mNumVoiceFrames; + } + + /** + * Returns the number of no-data frames sent by jitter buffer to audio + */ + public int getNumNoDataFrames() { + return mNumNoDataFrames; + } + + /** + * Returns the number of RTP voice packets dropped by jitter buffer + */ + public int getNumDroppedRtpPackets() { + return mNumDroppedRtpPackets; + } + + /** + * Returns the minimum playout delay in the reporting interval + * in milliseconds. + */ + public long getMinPlayoutDelayMillis() { + return mMinPlayoutDelayMillis; + } + + /** + * Returns the maximum playout delay in the reporting interval + * in milliseconds. + */ + public long getMaxPlayoutDelayMillis() { + return mMaxPlayoutDelayMillis; + } + + /** + * Returns the total number of RTP SID (Silence Insertion Descriptor) packets + * received by this device for an ongoing call + */ + public int getNumRtpSidPacketsRx() { + return mNumRtpSidPacketsRx; + } + + /** + * Returns the total number of RTP duplicate packets received by this device + * for an ongoing call + */ + public int getNumRtpDuplicatePackets() { + return mNumRtpDuplicatePackets; + } + + /** * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in * {@link ImsStreamMediaProfile}. * @@ -345,6 +412,13 @@ public final class CallQuality implements Parcelable { + " rtpInactivityDetected=" + mRtpInactivityDetected + " txSilenceDetected=" + mTxSilenceDetected + " rxSilenceDetected=" + mRxSilenceDetected + + " numVoiceFrames=" + mNumVoiceFrames + + " numNoDataFrames=" + mNumNoDataFrames + + " numDroppedRtpPackets=" + mNumDroppedRtpPackets + + " minPlayoutDelayMillis=" + mMinPlayoutDelayMillis + + " maxPlayoutDelayMillis=" + mMaxPlayoutDelayMillis + + " numRtpSidPacketsRx=" + mNumRtpSidPacketsRx + + " numRtpDuplicatePackets=" + mNumRtpDuplicatePackets + "}"; } @@ -364,7 +438,14 @@ public final class CallQuality implements Parcelable { mCodecType, mRtpInactivityDetected, mRxSilenceDetected, - mTxSilenceDetected); + mTxSilenceDetected, + mNumVoiceFrames, + mNumNoDataFrames, + mNumDroppedRtpPackets, + mMinPlayoutDelayMillis, + mMaxPlayoutDelayMillis, + mNumRtpSidPacketsRx, + mNumRtpDuplicatePackets); } @Override @@ -392,7 +473,14 @@ public final class CallQuality implements Parcelable { && mCodecType == s.mCodecType && mRtpInactivityDetected == s.mRtpInactivityDetected && mRxSilenceDetected == s.mRxSilenceDetected - && mTxSilenceDetected == s.mTxSilenceDetected); + && mTxSilenceDetected == s.mTxSilenceDetected + && mNumVoiceFrames == s.mNumVoiceFrames + && mNumNoDataFrames == s.mNumNoDataFrames + && mNumDroppedRtpPackets == s.mNumDroppedRtpPackets + && mMinPlayoutDelayMillis == s.mMinPlayoutDelayMillis + && mMaxPlayoutDelayMillis == s.mMaxPlayoutDelayMillis + && mNumRtpSidPacketsRx == s.mNumRtpSidPacketsRx + && mNumRtpDuplicatePackets == s.mNumRtpDuplicatePackets); } /** @@ -420,6 +508,13 @@ public final class CallQuality implements Parcelable { dest.writeBoolean(mRtpInactivityDetected); dest.writeBoolean(mRxSilenceDetected); dest.writeBoolean(mTxSilenceDetected); + dest.writeInt(mNumVoiceFrames); + dest.writeInt(mNumNoDataFrames); + dest.writeInt(mNumDroppedRtpPackets); + dest.writeLong(mMinPlayoutDelayMillis); + dest.writeLong(mMaxPlayoutDelayMillis); + dest.writeInt(mNumRtpSidPacketsRx); + dest.writeInt(mNumRtpDuplicatePackets); } public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() { @@ -431,4 +526,322 @@ public final class CallQuality implements Parcelable { return new CallQuality[size]; } }; + + /** + * Provides a convenient way to set the fields of a {@link CallQuality} when creating a new + * instance. + * + * <p>The example below shows how you might create a new {@code CallQuality}: + * + * <pre><code> + * + * CallQuality callQuality = new CallQuality.Builder() + * .setNumRtpPacketsTransmitted(150) + * .setNumRtpPacketsReceived(200) + * .build(); + * </code></pre> + */ + public static final class Builder { + + private int mDownlinkCallQualityLevel; + private int mUplinkCallQualityLevel; + private int mCallDuration; + private int mNumRtpPacketsTransmitted; + private int mNumRtpPacketsReceived; + private int mNumRtpPacketsTransmittedLost; + private int mNumRtpPacketsNotReceived; + private int mAverageRelativeJitter; + private int mMaxRelativeJitter; + private int mAverageRoundTripTime; + private int mCodecType; + private boolean mRtpInactivityDetected; + private boolean mRxSilenceDetected; + private boolean mTxSilenceDetected; + private int mNumVoiceFrames; + private int mNumNoDataFrames; + private int mNumDroppedRtpPackets; + private long mMinPlayoutDelayMillis; + private long mMaxPlayoutDelayMillis; + private int mNumRtpSidPacketsRx; + private int mNumRtpDuplicatePackets; + + /** + * Set the downlink call quality level for ongoing call. + * + * @param downlinkCallQualityLevel the Downlink call quality level + * @return The same instance of the builder. + */ + public @NonNull Builder setDownlinkCallQualityLevel( + @CallQualityLevel int downlinkCallQualityLevel) { + mDownlinkCallQualityLevel = downlinkCallQualityLevel; + return this; + } + + /** + * Set the uplink call quality level for ongoing call. + * + * @param uplinkCallQualityLevel the Uplink call quality level + * @return The same instance of the builder. + */ + public @NonNull Builder setUplinkCallQualityLevel( + @CallQualityLevel int uplinkCallQualityLevel) { + mUplinkCallQualityLevel = uplinkCallQualityLevel; + return this; + } + + /** + * Set the call duration in milliseconds. + * + * @param callDuration the call duration in milliseconds + * @return The same instance of the builder. + */ + public @NonNull Builder setCallDuration(int callDuration) { + mCallDuration = callDuration; + return this; + } + + /** + * Set the number of RTP packets sent for ongoing call. + * + * @param numRtpPacketsTransmitted RTP packets sent to network + * @return The same instance of the builder. + */ + public @NonNull Builder setNumRtpPacketsTransmitted(int numRtpPacketsTransmitted) { + mNumRtpPacketsTransmitted = numRtpPacketsTransmitted; + return this; + } + + /** + * Set the number of RTP packets received for ongoing call. + * + * @param numRtpPacketsReceived RTP packets received from network + * @return The same instance of the builder. + */ + public @NonNull Builder setNumRtpPacketsReceived(int numRtpPacketsReceived) { + mNumRtpPacketsReceived = numRtpPacketsReceived; + return this; + } + + /** + * Set the number of RTP packets which were lost in network and never + * transmitted. + * + * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never + * transmitted + * @return The same instance of the builder. + */ + public @NonNull Builder setNumRtpPacketsTransmittedLost(int numRtpPacketsTransmittedLost) { + mNumRtpPacketsTransmittedLost = numRtpPacketsTransmittedLost; + return this; + } + + /** + * Set the number of RTP packets which were lost in network and never received. + * + * @param numRtpPacketsNotReceived RTP packets which were lost in network and + * never received + * @return The same instance of the builder. + */ + public @NonNull Builder setNumRtpPacketsNotReceived(int numRtpPacketsNotReceived) { + mNumRtpPacketsNotReceived = numRtpPacketsNotReceived; + return this; + } + + /** + * Set the average relative jitter in milliseconds. + * + * @param averageRelativeJitter average relative jitter in milliseconds + * @return The same instance of the builder. + */ + public @NonNull Builder setAverageRelativeJitter(int averageRelativeJitter) { + mAverageRelativeJitter = averageRelativeJitter; + return this; + } + + /** + * Set the maximum relative jitter in milliseconds. + * + * @param maxRelativeJitter maximum relative jitter in milliseconds + * @return The same instance of the builder. + */ + public @NonNull Builder setMaxRelativeJitter(int maxRelativeJitter) { + mMaxRelativeJitter = maxRelativeJitter; + return this; + } + + /** + * Set the average round trip delay in milliseconds. + * + * @param averageRoundTripTime average round trip delay in milliseconds + * @return The same instance of the builder. + */ + public @NonNull Builder setAverageRoundTripTime(int averageRoundTripTime) { + mAverageRoundTripTime = averageRoundTripTime; + return this; + } + + /** + * Set the codec type used in the ongoing call. + * + * @param codecType the codec type. + * @return The same instance of the builder. + */ + public @NonNull Builder setCodecType(int codecType) { + mCodecType = codecType; + return this; + } + + /** + * Set to be True if no incoming RTP is received for a continuous + * duration of 4 seconds. + * + * @param rtpInactivityDetected True if no incoming RTP is received for + * a continuous duration of 4 seconds + * @return The same instance of the builder. + */ + public @NonNull Builder setRtpInactivityDetected(boolean rtpInactivityDetected) { + mRtpInactivityDetected = rtpInactivityDetected; + return this; + } + + /** + * Set to be True if only silence RTP packets are received for 20 seconds + * immediately after call is connected. + * + * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds + * immediately after call is connected + * @return The same instance of the builder. + */ + public @NonNull Builder setIncomingSilenceDetectedAtCallSetup(boolean rxSilenceDetected) { + mRxSilenceDetected = rxSilenceDetected; + return this; + } + + /** + * Set to be True if only silence RTP packets are sent for 20 seconds immediately + * after call is connected. + * + * @param txSilenceDetected True if only silence RTP packets are sent for + * 20 seconds immediately after call is connected. + * @return The same instance of the builder. + */ + public @NonNull Builder setOutgoingSilenceDetectedAtCallSetup(boolean txSilenceDetected) { + mTxSilenceDetected = txSilenceDetected; + return this; + } + + /** + * Set the number of voice frames sent by jitter buffer to audio. + * + * @param numVoiceFrames Voice frames sent by jitter buffer to audio. + * @return The same instance of the builder. + */ + public @NonNull Builder setNumVoiceFrames(int numVoiceFrames) { + mNumVoiceFrames = numVoiceFrames; + return this; + } + + /** + * Set the number of no-data frames sent by jitter buffer to audio. + * + * @param numNoDataFrames no-data frames sent by jitter buffer to audio + * @return The same instance of the builder. + */ + public @NonNull Builder setNumNoDataFrames(int numNoDataFrames) { + mNumNoDataFrames = numNoDataFrames; + return this; + } + + /** + * Set the number of RTP Voice packets dropped by jitter buffer. + * + * @param numDroppedRtpPackets number of RTP Voice packets dropped by jitter buffer + * @return The same instance of the builder. + */ + public @NonNull Builder setNumDroppedRtpPackets(int numDroppedRtpPackets) { + mNumDroppedRtpPackets = numDroppedRtpPackets; + return this; + } + + /** + * Set the minimum playout delay in the reporting interval in milliseconds. + * + * @param minPlayoutDelayMillis minimum playout delay in the reporting interval + * @return The same instance of the builder. + */ + public @NonNull Builder setMinPlayoutDelayMillis(long minPlayoutDelayMillis) { + mMinPlayoutDelayMillis = minPlayoutDelayMillis; + return this; + } + + /** + * Set the maximum Playout delay in the reporting interval in milliseconds. + * + * @param maxPlayoutDelayMillis maximum Playout delay in the reporting interval + * @return The same instance of the builder. + */ + public @NonNull Builder setMaxPlayoutDelayMillis(long maxPlayoutDelayMillis) { + mMaxPlayoutDelayMillis = maxPlayoutDelayMillis; + return this; + } + + /** + * Set the total number of RTP SID (Silence Insertion Descriptor) + * packets received by this device for an ongoing call. + * + * @param numRtpSidPacketsRx the total number of RTP SID packets received + * by this device for an ongoing call. + * @return The same instance of the builder. + */ + public @NonNull Builder setNumRtpSidPacketsRx(int numRtpSidPacketsRx) { + mNumRtpSidPacketsRx = numRtpSidPacketsRx; + return this; + } + + /** + * Set the total number of RTP duplicate packets received by this device + * for an ongoing call. + * + * @param numRtpDuplicatePackets the total number of RTP duplicate packets + * received by this device for an ongoing call + * @return The same instance of the builder. + */ + public @NonNull Builder setNumRtpDuplicatePackets(int numRtpDuplicatePackets) { + mNumRtpDuplicatePackets = numRtpDuplicatePackets; + return this; + } + + /** + * Build the CallQuality. + * + * @return the CallQuality object. + */ + public @NonNull CallQuality build() { + + CallQuality callQuality = new CallQuality(); + callQuality.mDownlinkCallQualityLevel = mDownlinkCallQualityLevel; + callQuality.mUplinkCallQualityLevel = mUplinkCallQualityLevel; + callQuality.mCallDuration = mCallDuration; + callQuality.mNumRtpPacketsTransmitted = mNumRtpPacketsTransmitted; + callQuality.mNumRtpPacketsReceived = mNumRtpPacketsReceived; + callQuality.mNumRtpPacketsTransmittedLost = mNumRtpPacketsTransmittedLost; + callQuality.mNumRtpPacketsNotReceived = mNumRtpPacketsNotReceived; + callQuality.mAverageRelativeJitter = mAverageRelativeJitter; + callQuality.mMaxRelativeJitter = mMaxRelativeJitter; + callQuality.mAverageRoundTripTime = mAverageRoundTripTime; + callQuality.mCodecType = mCodecType; + callQuality.mRtpInactivityDetected = mRtpInactivityDetected; + callQuality.mTxSilenceDetected = mTxSilenceDetected; + callQuality.mRxSilenceDetected = mRxSilenceDetected; + callQuality.mNumVoiceFrames = mNumVoiceFrames; + callQuality.mNumNoDataFrames = mNumNoDataFrames; + callQuality.mNumDroppedRtpPackets = mNumDroppedRtpPackets; + callQuality.mMinPlayoutDelayMillis = mMinPlayoutDelayMillis; + callQuality.mMaxPlayoutDelayMillis = mMaxPlayoutDelayMillis; + callQuality.mNumRtpSidPacketsRx = mNumRtpSidPacketsRx; + callQuality.mNumRtpDuplicatePackets = mNumRtpDuplicatePackets; + + return callQuality; + } + } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index cd399c02a72e..d604dc1d2c92 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2273,6 +2273,7 @@ public class CarrierConfigManager { * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN + * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE * * <p> * 1. For Single SIM(SS) device, it can be customized in both carrier_config_mccmnc.xml @@ -5035,6 +5036,10 @@ public class CarrierConfigManager { /** Specifies the PCO id for IPv4 Epdg server address */ public static final String KEY_EPDG_PCO_ID_IPV4_INT = KEY_PREFIX + "epdg_pco_id_ipv4_int"; + /** Controls if the IKE tunnel setup supports EAP-AKA fast reauth */ + public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = + KEY_PREFIX + "enable_support_for_eap_aka_fast_reauth_bool"; + /** @hide */ @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT}) public @interface AuthenticationMethodType {} @@ -5178,6 +5183,7 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL, false); defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0); defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0); + defaults.putBoolean(KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL, false); return defaults; } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 2295ed7f3876..574a356c43f2 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -194,7 +194,7 @@ public class SubscriptionManager { } @Override - protected T recompute(Void aVoid) { + public T recompute(Void aVoid) { T result = mDefaultValue; try { @@ -228,7 +228,7 @@ public class SubscriptionManager { } @Override - protected T recompute(Integer query) { + public T recompute(Integer query) { T result = mDefaultValue; try { diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 93e10583fbc0..7e2d80eb871c 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -317,6 +317,10 @@ public final class ImsCallProfile implements Parcelable { * Payphone presentation for Originating Identity. */ public static final int OIR_PRESENTATION_PAYPHONE = 4; + /** + * Unavailable presentation for Originating Identity. + */ + public static final int OIR_PRESENTATION_UNAVAILABLE = 5; //Values for EXTRA_DIALSTRING /** @@ -989,6 +993,8 @@ public final class ImsCallProfile implements Parcelable { return ImsCallProfile.OIR_PRESENTATION_PAYPHONE; case PhoneConstants.PRESENTATION_UNKNOWN: return ImsCallProfile.OIR_PRESENTATION_UNKNOWN; + case PhoneConstants.PRESENTATION_UNAVAILABLE: + return ImsCallProfile.OIR_PRESENTATION_UNAVAILABLE; default: return ImsCallProfile.OIR_DEFAULT; } @@ -1017,6 +1023,8 @@ public final class ImsCallProfile implements Parcelable { return PhoneConstants.PRESENTATION_ALLOWED; case ImsCallProfile.OIR_PRESENTATION_PAYPHONE: return PhoneConstants.PRESENTATION_PAYPHONE; + case ImsCallProfile.OIR_PRESENTATION_UNAVAILABLE: + return PhoneConstants.PRESENTATION_UNAVAILABLE; case ImsCallProfile.OIR_PRESENTATION_UNKNOWN: return PhoneConstants.PRESENTATION_UNKNOWN; default: diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index f6502466e25e..813e80e6f355 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -95,6 +95,7 @@ public class PhoneConstants { public static final int PRESENTATION_UNKNOWN = 3; // no specified or unknown by network @UnsupportedAppUsage public static final int PRESENTATION_PAYPHONE = 4; // show pay phone info + public static final int PRESENTATION_UNAVAILABLE = 5; // show unavailable public static final String PHONE_NAME_KEY = "phoneName"; public static final String DATA_NETWORK_TYPE_KEY = "networkType"; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index eef7b57ff4b2..e07a8f94d651 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -99,11 +99,7 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** {@inheritDoc} */ @Presubmit @Test - override fun appLayerReplacesLauncher() { - // This test doesn't work in shell transitions because of b/206094140 - assumeFalse(isShellTransitionsEnabled) - super.appLayerReplacesLauncher() - } + override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() /** {@inheritDoc} */ @Presubmit diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index dfa8f8ea1ac9..cd209b2cfb5b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -96,20 +96,14 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** {@inheritDoc} */ @Presubmit @Test - override fun appWindowReplacesLauncherAsTopWindow() { - // This test doesn't work in shell transitions because of b/206094140 - assumeFalse(isShellTransitionsEnabled) - super.appWindowReplacesLauncherAsTopWindow() - } + override fun appWindowReplacesLauncherAsTopWindow() = + super.appWindowReplacesLauncherAsTopWindow() /** {@inheritDoc} */ @Presubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - // This test doesn't work in shell transitions because of b/206094140 - assumeFalse(isShellTransitionsEnabled) - super.visibleLayersShownMoreThanOneConsecutiveEntry() - } + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() /** {@inheritDoc} */ @FlakyTest @@ -119,11 +113,7 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** {@inheritDoc} */ @Presubmit @Test - override fun appLayerReplacesLauncher() { - // This test doesn't work in shell transitions because of b/206094140 - assumeFalse(isShellTransitionsEnabled) - super.appLayerReplacesLauncher() - } + override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() /** {@inheritDoc} */ @Presubmit diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index cb37fc7b47e9..0a88f6bb5908 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -23,6 +23,7 @@ android:supportsRtl="true"> <activity android:name=".SimpleActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" + android:theme="@style/CutoutShortEdges" android:label="SimpleApp" android:exported="true"> <intent-filter> @@ -32,6 +33,7 @@ </activity> <activity android:name=".ImeActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" + android:theme="@style/CutoutShortEdges" android:label="ImeApp" android:exported="true"> <intent-filter> @@ -40,6 +42,7 @@ </intent-filter> </activity> <activity android:name=".ImeActivityAutoFocus" + android:theme="@style/CutoutShortEdges" android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" android:windowSoftInputMode="stateVisible" android:label="ImeAppAutoFocus" @@ -51,6 +54,7 @@ </activity> <activity android:name=".SeamlessRotationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" + android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="SeamlessApp" android:exported="true"> @@ -60,6 +64,7 @@ </intent-filter> </activity> <activity android:name=".NonResizeableActivity" + android:theme="@style/CutoutShortEdges" android:resizeableActivity="false" android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" android:label="NonResizeableApp" @@ -72,6 +77,7 @@ </activity> <activity android:name=".ButtonActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity" + android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="ButtonActivity" android:exported="true"> @@ -82,6 +88,7 @@ </activity> <activity android:name=".LaunchNewTaskActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity" + android:theme="@style/CutoutShortEdges" android:configChanges="orientation|screenSize" android:label="LaunchNewTaskActivity" android:exported="true"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml new file mode 100644 index 000000000000..87a61a88c094 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <style name="CutoutDefault"> + <item name="android:windowLayoutInDisplayCutoutMode">default</item> + </style> + + <style name="CutoutShortEdges"> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> + + <style name="CutoutNever"> + <item name="android:windowLayoutInDisplayCutoutMode">never</item> + </style> +</resources>
\ No newline at end of file diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java index 476be44ee759..d03aee282ee1 100644 --- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java @@ -15,8 +15,8 @@ */ package android.net.vcn; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -27,13 +27,13 @@ import org.junit.Test; import java.util.HashSet; import java.util.Set; -public class VcnCellUnderlyingNetworkPriorityTest { +public class VcnCellUnderlyingNetworkTemplateTest { private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>(); private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>(); // Package private for use in VcnGatewayConnectionConfigTest - static VcnCellUnderlyingNetworkPriority getTestNetworkPriority() { - return new VcnCellUnderlyingNetworkPriority.Builder() + static VcnCellUnderlyingNetworkTemplate getTestNetworkPriority() { + return new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS) @@ -45,7 +45,7 @@ public class VcnCellUnderlyingNetworkPriorityTest { @Test public void testBuilderAndGetters() { - final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority(); + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); assertTrue(networkPriority.allowMetered()); assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds()); @@ -56,8 +56,8 @@ public class VcnCellUnderlyingNetworkPriorityTest { @Test public void testBuilderAndGettersForDefaultValues() { - final VcnCellUnderlyingNetworkPriority networkPriority = - new VcnCellUnderlyingNetworkPriority.Builder().build(); + final VcnCellUnderlyingNetworkTemplate networkPriority = + new VcnCellUnderlyingNetworkTemplate.Builder().build(); assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); assertFalse(networkPriority.allowMetered()); assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds()); @@ -68,10 +68,10 @@ public class VcnCellUnderlyingNetworkPriorityTest { @Test public void testPersistableBundle() { - final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority(); + final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); assertEquals( networkPriority, - VcnUnderlyingNetworkPriority.fromPersistableBundle( + VcnUnderlyingNetworkTemplate.fromPersistableBundle( networkPriority.toPersistableBundle())); } } diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 377f526a9825..1f2905da08f4 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -54,7 +54,7 @@ public class VcnGatewayConnectionConfigTest { }; public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; - private static final LinkedHashSet<VcnUnderlyingNetworkPriority> UNDERLYING_NETWORK_PRIORITIES = + private static final LinkedHashSet<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet(); static { @@ -62,9 +62,9 @@ public class VcnGatewayConnectionConfigTest { Arrays.sort(UNDERLYING_CAPS); UNDERLYING_NETWORK_PRIORITIES.add( - VcnCellUnderlyingNetworkPriorityTest.getTestNetworkPriority()); + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority()); UNDERLYING_NETWORK_PRIORITIES.add( - VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority()); + VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); } public static final long[] RETRY_INTERVALS_MS = @@ -286,7 +286,7 @@ public class VcnGatewayConnectionConfigTest { } private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities( - LinkedHashSet<VcnUnderlyingNetworkPriority> networkPriorities) { + LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPriorities) { return buildTestConfigWithExposedCaps( new VcnGatewayConnectionConfig.Builder( "buildTestConfigWithVcnUnderlyingNetworkPriorities", @@ -300,17 +300,17 @@ public class VcnGatewayConnectionConfigTest { final VcnGatewayConnectionConfig config = buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES); - final LinkedHashSet<VcnUnderlyingNetworkPriority> networkPrioritiesEqual = + final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesEqual = new LinkedHashSet(); - networkPrioritiesEqual.add(VcnCellUnderlyingNetworkPriorityTest.getTestNetworkPriority()); - networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority()); + networkPrioritiesEqual.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkPriority()); + networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); final VcnGatewayConnectionConfig configEqual = buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual); - final LinkedHashSet<VcnUnderlyingNetworkPriority> networkPrioritiesNotEqual = + final LinkedHashSet<VcnUnderlyingNetworkTemplate> networkPrioritiesNotEqual = new LinkedHashSet(); networkPrioritiesNotEqual.add( - VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority()); + VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkPriority()); final VcnGatewayConnectionConfig configNotEqual = buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual); diff --git a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java index dd272cb38596..652057fd48c7 100644 --- a/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkPriorityTest.java +++ b/tests/vcn/java/android/net/vcn/VcnWifiUnderlyingNetworkTemplateTest.java @@ -15,8 +15,8 @@ */ package android.net.vcn; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_ANY; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -26,13 +26,13 @@ import static org.junit.Assert.fail; import org.junit.Test; -public class VcnWifiUnderlyingNetworkPriorityTest { +public class VcnWifiUnderlyingNetworkTemplateTest { private static final String SSID = "TestWifi"; private static final int INVALID_NETWORK_QUALITY = -1; // Package private for use in VcnGatewayConnectionConfigTest - static VcnWifiUnderlyingNetworkPriority getTestNetworkPriority() { - return new VcnWifiUnderlyingNetworkPriority.Builder() + static VcnWifiUnderlyingNetworkTemplate getTestNetworkPriority() { + return new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .setSsid(SSID) @@ -41,7 +41,7 @@ public class VcnWifiUnderlyingNetworkPriorityTest { @Test public void testBuilderAndGetters() { - final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority(); + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality()); assertTrue(networkPriority.allowMetered()); assertEquals(SSID, networkPriority.getSsid()); @@ -49,8 +49,8 @@ public class VcnWifiUnderlyingNetworkPriorityTest { @Test public void testBuilderAndGettersForDefaultValues() { - final VcnWifiUnderlyingNetworkPriority networkPriority = - new VcnWifiUnderlyingNetworkPriority.Builder().build(); + final VcnWifiUnderlyingNetworkTemplate networkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder().build(); assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality()); assertFalse(networkPriority.allowMetered()); assertNull(SSID, networkPriority.getSsid()); @@ -59,7 +59,7 @@ public class VcnWifiUnderlyingNetworkPriorityTest { @Test public void testBuildWithInvalidNetworkQuality() { try { - new VcnWifiUnderlyingNetworkPriority.Builder() + new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(INVALID_NETWORK_QUALITY); fail("Expected to fail due to the invalid network quality"); } catch (Exception expected) { @@ -68,10 +68,10 @@ public class VcnWifiUnderlyingNetworkPriorityTest { @Test public void testPersistableBundle() { - final VcnWifiUnderlyingNetworkPriority networkPriority = getTestNetworkPriority(); + final VcnWifiUnderlyingNetworkTemplate networkPriority = getTestNetworkPriority(); assertEquals( networkPriority, - VcnUnderlyingNetworkPriority.fromPersistableBundle( + VcnUnderlyingNetworkTemplate.fromPersistableBundle( networkPriority.toPersistableBundle())); } } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 7c7dc4d79e9a..bb98bc0bab53 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -23,7 +23,6 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; @@ -264,6 +263,7 @@ public class VcnManagementServiceTest { @Test public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); + mTestLooper.dispatchAll(); verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); verify(mSubscriptionTracker).register(); @@ -475,10 +475,8 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); - - // Verify teardown after delay - mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); verify(mMockPolicyListener).onPolicyChanged(); } @@ -504,92 +502,6 @@ public class VcnManagementServiceTest { assertEquals(0, mVcnMgmtSvc.getAllVcns().size()); } - /** - * Tests an intermediate state where carrier privileges are marked as lost before active data - * subId changes during a SIM ejection. - * - * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to - * immediately. - */ - @Test - public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges() - throws Exception { - setupActiveSubscription(TEST_UUID_2); - - final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); - final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); - - // Simulate privileges lost - triggerSubscriptionTrackerCbAndGetSnapshot( - TEST_SUBSCRIPTION_ID, - TEST_UUID_2, - Collections.emptySet(), - Collections.emptyMap(), - false /* hasCarrierPrivileges */); - - // Verify teardown after delay - mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); - mTestLooper.dispatchAll(); - verify(vcn).teardownAsynchronously(); - } - - @Test - public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances() - throws Exception { - setupActiveSubscription(TEST_UUID_2); - - final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); - final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); - - // Simulate SIM unloaded - triggerSubscriptionTrackerCbAndGetSnapshot( - INVALID_SUBSCRIPTION_ID, - null /* activeDataSubscriptionGroup */, - Collections.emptySet(), - Collections.emptyMap(), - false /* hasCarrierPrivileges */); - - // Simulate new SIM loaded right during teardown delay. - mTestLooper.moveTimeForward( - VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); - mTestLooper.dispatchAll(); - triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); - - // Verify that even after the full timeout duration, the VCN instance is not torn down - mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); - mTestLooper.dispatchAll(); - verify(vcn, never()).teardownAsynchronously(); - } - - @Test - public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception { - setupActiveSubscription(TEST_UUID_2); - - final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); - final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); - - // Simulate SIM unloaded - triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); - - // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new - // vcnInstance. - mTestLooper.moveTimeForward( - VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); - mTestLooper.dispatchAll(); - mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); - triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); - final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); - - // Verify that new instance was different, and the old one was torn down - assertTrue(oldInstance != newInstance); - verify(oldInstance).teardownAsynchronously(); - - // Verify that even after the full timeout duration, the new VCN instance is not torn down - mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); - mTestLooper.dispatchAll(); - verify(newInstance, never()).teardownAsynchronously(); - } - @Test public void testPackageChangeListenerRegistered() throws Exception { verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> { @@ -925,6 +837,8 @@ public class VcnManagementServiceTest { private void setupSubscriptionAndStartVcn( int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) { mVcnMgmtSvc.systemReady(); + mTestLooper.dispatchAll(); + triggerSubscriptionTrackerCbAndGetSnapshot( subGrp, Collections.singleton(subGrp), @@ -1020,6 +934,7 @@ public class VcnManagementServiceTest { private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) { mVcnMgmtSvc.systemReady(); + mTestLooper.dispatchAll(); final ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class); @@ -1264,15 +1179,14 @@ public class VcnManagementServiceTest { true /* isActive */, true /* hasCarrierPrivileges */); - // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown - // timeout so the VCN goes inactive. + // VCN is currently active. Lose carrier privileges for TEST_PACKAGE so the VCN goes + // inactive. final TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_1, Collections.singleton(TEST_UUID_1), Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1), false /* hasCarrierPrivileges */); - mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 1f0df62fe72c..978bf3ed2e92 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -22,6 +22,7 @@ import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; +import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -34,8 +35,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -57,6 +60,8 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.telephony.TelephonyManager.CarrierPrivilegesListener; +import android.util.ArrayMap; import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -83,7 +88,7 @@ public class TelephonySubscriptionTrackerTest { private static final String PACKAGE_NAME = TelephonySubscriptionTrackerTest.class.getPackage().getName(); private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); - private static final int TEST_SIM_SLOT_INDEX = 1; + private static final int TEST_SIM_SLOT_INDEX = 0; private static final int TEST_SUBSCRIPTION_ID_1 = 2; private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class); private static final int TEST_SUBSCRIPTION_ID_2 = 3; @@ -151,6 +156,8 @@ public class TelephonySubscriptionTrackerTest { @Before public void setUp() throws Exception { + doReturn(2).when(mTelephonyManager).getActiveModemCount(); + mCallback = mock(TelephonySubscriptionTrackerCallback.class); mTelephonySubscriptionTracker = new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps); @@ -180,6 +187,15 @@ public class TelephonySubscriptionTrackerTest { return captor.getValue(); } + private List<CarrierPrivilegesListener> getCarrierPrivilegesListeners() { + final ArgumentCaptor<CarrierPrivilegesListener> captor = + ArgumentCaptor.forClass(CarrierPrivilegesListener.class); + verify(mTelephonyManager, atLeastOnce()) + .addCarrierPrivilegesListener(anyInt(), any(), captor.capture()); + + return captor.getAllValues(); + } + private ActiveDataSubscriptionIdListener getActiveDataSubscriptionIdListener() { final ArgumentCaptor<TelephonyCallback> captor = ArgumentCaptor.forClass(TelephonyCallback.class); @@ -188,6 +204,11 @@ public class TelephonySubscriptionTrackerTest { return (ActiveDataSubscriptionIdListener) captor.getValue(); } + private Intent buildTestMultiSimConfigBroadcastIntent() { + Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED); + return intent; + } + private Intent buildTestBroadcastIntent(boolean hasValidSubscription) { Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED); intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX); @@ -239,12 +260,21 @@ public class TelephonySubscriptionTrackerTest { any(), eq(mHandler)); final IntentFilter filter = getIntentFilter(); - assertEquals(1, filter.countActions()); + assertEquals(2, filter.countActions()); assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED)); + assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED)); verify(mSubscriptionManager) .addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any()); assertNotNull(getOnSubscriptionsChangedListener()); + + verify(mTelephonyManager, times(2)) + .addCarrierPrivilegesListener(anyInt(), any(HandlerExecutor.class), any()); + verify(mTelephonyManager) + .addCarrierPrivilegesListener(eq(0), any(HandlerExecutor.class), any()); + verify(mTelephonyManager) + .addCarrierPrivilegesListener(eq(1), any(HandlerExecutor.class), any()); + assertEquals(2, getCarrierPrivilegesListeners().size()); } @Test @@ -255,6 +285,49 @@ public class TelephonySubscriptionTrackerTest { final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener(); verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(eq(listener)); + + for (CarrierPrivilegesListener carrierPrivilegesListener : + getCarrierPrivilegesListeners()) { + verify(mTelephonyManager) + .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener)); + } + } + + @Test + public void testMultiSimConfigChanged() throws Exception { + final ArrayMap<Integer, Integer> readySubIdsBySlotId = new ArrayMap<>(); + readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1); + readySubIdsBySlotId.put(TEST_SIM_SLOT_INDEX + 1, TEST_SUBSCRIPTION_ID_1); + + mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId); + doReturn(1).when(mTelephonyManager).getActiveModemCount(); + + List<CarrierPrivilegesListener> carrierPrivilegesListeners = + getCarrierPrivilegesListeners(); + + mTelephonySubscriptionTracker.onReceive(mContext, buildTestMultiSimConfigBroadcastIntent()); + mTestLooper.dispatchAll(); + + for (CarrierPrivilegesListener carrierPrivilegesListener : carrierPrivilegesListeners) { + verify(mTelephonyManager) + .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener)); + } + + // Expect cache cleared for inactive slots. + assertNull( + mTelephonySubscriptionTracker + .getReadySubIdsBySlotId() + .get(TEST_SIM_SLOT_INDEX + 1)); + + // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other + // (2 previously registered during startup, for slots 0 & 1) + verify(mTelephonyManager, times(3)) + .addCarrierPrivilegesListener(anyInt(), any(HandlerExecutor.class), any()); + verify(mTelephonyManager, times(2)) + .addCarrierPrivilegesListener(eq(0), any(HandlerExecutor.class), any()); + + // Verify that this triggers a re-evaluation + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); } @Test @@ -314,6 +387,17 @@ public class TelephonySubscriptionTrackerTest { } @Test + public void testOnCarrierPrivilegesChanged() throws Exception { + setupReadySubIds(); + + final CarrierPrivilegesListener listener = getCarrierPrivilegesListeners().get(0); + listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {}); + mTestLooper.dispatchAll(); + + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); + } + + @Test public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception { mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index 46a614f60ae7..f23d5bf67ebf 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -16,7 +16,7 @@ package com.android.server.vcn.routeselection; -import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK; +import static android.net.vcn.VcnUnderlyingNetworkTemplate.NETWORK_QUALITY_OK; import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY; @@ -39,10 +39,10 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; -import android.net.vcn.VcnCellUnderlyingNetworkPriority; +import android.net.vcn.VcnCellUnderlyingNetworkTemplate; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager; -import android.net.vcn.VcnWifiUnderlyingNetworkPriority; +import android.net.vcn.VcnWifiUnderlyingNetworkTemplate; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.test.TestLooper; @@ -142,8 +142,8 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchWithoutNotMeteredBit() { - final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority = - new VcnWifiUnderlyingNetworkPriority.Builder() + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(false /* allowMetered */) .build(); @@ -161,8 +161,8 @@ public class NetworkPriorityClassifierTest { private void verifyMatchWifi( boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) { - final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority = - new VcnWifiUnderlyingNetworkPriority.Builder() + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .build(); @@ -211,8 +211,8 @@ public class NetworkPriorityClassifierTest { private void verifyMatchWifiWithSsid(boolean useMatchedSsid, boolean expectMatch) { final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER; - final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority = - new VcnWifiUnderlyingNetworkPriority.Builder() + final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority = + new VcnWifiUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .setSsid(nwPrioritySsid) @@ -237,8 +237,8 @@ public class NetworkPriorityClassifierTest { verifyMatchWifiWithSsid(false /* useMatchedSsid */, false /* expectMatch */); } - private static VcnCellUnderlyingNetworkPriority.Builder getCellNetworkPriorityBuilder() { - return new VcnCellUnderlyingNetworkPriority.Builder() + private static VcnCellUnderlyingNetworkTemplate.Builder getCellNetworkPriorityBuilder() { + return new VcnCellUnderlyingNetworkTemplate.Builder() .setNetworkQuality(NETWORK_QUALITY_OK) .setAllowMetered(true /* allowMetered */) .setAllowRoaming(true /* allowRoaming */); @@ -257,7 +257,7 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchOpportunisticCell() { - final VcnCellUnderlyingNetworkPriority opportunisticCellNetworkPriority = + final VcnCellUnderlyingNetworkTemplate opportunisticCellNetworkPriority = getCellNetworkPriorityBuilder() .setRequireOpportunistic(true /* requireOpportunistic */) .build(); @@ -277,7 +277,7 @@ public class NetworkPriorityClassifierTest { private void verifyMatchMacroCellWithAllowedPlmnIds( boolean useMatchedPlmnId, boolean expectMatch) { final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER; - final VcnCellUnderlyingNetworkPriority networkPriority = + final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder() .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId)) .build(); @@ -306,7 +306,7 @@ public class NetworkPriorityClassifierTest { private void verifyMatchMacroCellWithAllowedSpecificCarrierIds( boolean useMatchedCarrierId, boolean expectMatch) { final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER; - final VcnCellUnderlyingNetworkPriority networkPriority = + final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder() .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId)) .build(); @@ -335,7 +335,7 @@ public class NetworkPriorityClassifierTest { @Test public void testMatchWifiFailWithoutNotRoamingBit() { - final VcnCellUnderlyingNetworkPriority networkPriority = + final VcnCellUnderlyingNetworkTemplate networkPriority = getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build(); assertFalse( |