summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/time/TimeState.java12
-rw-r--r--core/java/android/app/time/TimeZoneState.java14
-rw-r--r--core/java/android/app/time/UnixEpochTime.java45
-rw-r--r--core/java/android/credentials/ui/UserSelectionResult.java97
-rw-r--r--core/java/android/os/BatteryStats.java138
-rw-r--r--core/java/android/service/resumeonreboot/OWNERS3
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java9
-rw-r--r--core/java/com/android/internal/display/BrightnessSynchronizer.java33
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java115
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistoryIterator.java33
-rw-r--r--core/java/com/android/internal/os/KernelSingleUidTimeReader.java1
-rw-r--r--core/java/com/android/internal/os/ProcessCpuTracker.java28
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java9
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java83
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java373
-rw-r--r--core/tests/coretests/src/android/app/time/TimeStateTest.java17
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneStateTest.java17
-rw-r--r--core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java16
-rw-r--r--core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java41
-rw-r--r--libs/WindowManager/Shell/Android.bp19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java (renamed from services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java)20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java86
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java120
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java46
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java24
-rw-r--r--location/java/android/location/GnssMeasurementRequest.java11
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt39
-rw-r--r--packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml93
-rw-r--r--packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml29
-rw-r--r--packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml70
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt210
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java18
-rw-r--r--packages/SystemUI/tests/Android.bp50
-rw-r--r--packages/SystemUI/tests/Android.mk93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt360
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt69
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java34
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java17
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java12
-rw-r--r--services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java71
-rw-r--r--services/core/java/com/android/server/DropBoxManagerInternal.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java11
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java174
-rw-r--r--services/core/java/com/android/server/am/BroadcastDispatcher.java44
-rw-r--r--services/core/java/com/android/server/am/BroadcastFilter.java7
-rw-r--r--services/core/java/com/android/server/am/BroadcastHistory.java32
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java225
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java55
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java35
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java366
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java98
-rw-r--r--services/core/java/com/android/server/am/BroadcastStats.java4
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java5
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java4
-rw-r--r--services/core/java/com/android/server/am/TEST_MAPPING1
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java6
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java8
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java111
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java35
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java14
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java69
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java266
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java (renamed from services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java)125
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java117
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java41
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java1
-rw-r--r--tools/preload/loadclass/LoadClass.java8
147 files changed, 5443 insertions, 1290 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9f333315451c..bcd099e485ed 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2483,6 +2483,11 @@ package android.service.voice {
ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
}
+ public static class VoiceInteractionSession.ActivityId {
+ method @NonNull public android.os.IBinder getAssistToken();
+ method public int getTaskId();
+ }
+
}
package android.service.watchdog {
diff --git a/core/java/android/app/time/TimeState.java b/core/java/android/app/time/TimeState.java
index 15411e547814..01c869d99338 100644
--- a/core/java/android/app/time/TimeState.java
+++ b/core/java/android/app/time/TimeState.java
@@ -63,6 +63,12 @@ public final class TimeState implements Parcelable {
return new TimeState(unixEpochTime, userShouldConfirmId);
}
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mUnixEpochTime, 0);
+ dest.writeBoolean(mUserShouldConfirmTime);
+ }
+
/** @hide */
@Nullable
public static TimeState parseCommandLineArgs(@NonNull ShellCommand cmd) {
@@ -119,12 +125,6 @@ public final class TimeState implements Parcelable {
return 0;
}
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mUnixEpochTime, 0);
- dest.writeBoolean(mUserShouldConfirmTime);
- }
-
@NonNull
public UnixEpochTime getUnixEpochTime() {
return mUnixEpochTime;
diff --git a/core/java/android/app/time/TimeZoneState.java b/core/java/android/app/time/TimeZoneState.java
index efdff81b079a..8e87111986ce 100644
--- a/core/java/android/app/time/TimeZoneState.java
+++ b/core/java/android/app/time/TimeZoneState.java
@@ -58,11 +58,17 @@ public final class TimeZoneState implements Parcelable {
}
private static TimeZoneState createFromParcel(Parcel in) {
- String zoneId = in.readString();
+ String zoneId = in.readString8();
boolean userShouldConfirmId = in.readBoolean();
return new TimeZoneState(zoneId, userShouldConfirmId);
}
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mId);
+ dest.writeBoolean(mUserShouldConfirmId);
+ }
+
/** @hide */
@Nullable
public static TimeZoneState parseCommandLineArgs(@NonNull ShellCommand cmd) {
@@ -107,12 +113,6 @@ public final class TimeZoneState implements Parcelable {
return 0;
}
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mId);
- dest.writeBoolean(mUserShouldConfirmId);
- }
-
@NonNull
public String getId() {
return mId;
diff --git a/core/java/android/app/time/UnixEpochTime.java b/core/java/android/app/time/UnixEpochTime.java
index f1a176eacc9b..576bf6453eca 100644
--- a/core/java/android/app/time/UnixEpochTime.java
+++ b/core/java/android/app/time/UnixEpochTime.java
@@ -94,7 +94,6 @@ public final class UnixEpochTime implements Parcelable {
}
/** Returns the unix epoch time value. See {@link UnixEpochTime} for more information. */
- @Nullable
public long getUnixEpochTimeMillis() {
return mUnixEpochTimeMillis;
}
@@ -109,7 +108,7 @@ public final class UnixEpochTime implements Parcelable {
}
UnixEpochTime that = (UnixEpochTime) o;
return mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
- && Objects.equals(mUnixEpochTimeMillis, that.mUnixEpochTimeMillis);
+ && mUnixEpochTimeMillis == that.mUnixEpochTimeMillis;
}
@Override
@@ -125,32 +124,19 @@ public final class UnixEpochTime implements Parcelable {
+ '}';
}
- public static final @NonNull Creator<UnixEpochTime> CREATOR =
- new ClassLoaderCreator<UnixEpochTime>() {
-
- @Override
- public UnixEpochTime createFromParcel(@NonNull Parcel source) {
- return createFromParcel(source, null);
- }
-
- @Override
- public UnixEpochTime createFromParcel(
- @NonNull Parcel source, @Nullable ClassLoader classLoader) {
- long elapsedRealtimeMillis = source.readLong();
- long unixEpochTimeMillis = source.readLong();
- return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
- }
-
- @Override
- public UnixEpochTime[] newArray(int size) {
- return new UnixEpochTime[size];
- }
- };
+ public static final @NonNull Creator<UnixEpochTime> CREATOR = new Creator<>() {
+ @Override
+ public UnixEpochTime createFromParcel(@NonNull Parcel source) {
+ long elapsedRealtimeMillis = source.readLong();
+ long unixEpochTimeMillis = source.readLong();
+ return new UnixEpochTime(elapsedRealtimeMillis, unixEpochTimeMillis);
+ }
- @Override
- public int describeContents() {
- return 0;
- }
+ @Override
+ public UnixEpochTime[] newArray(int size) {
+ return new UnixEpochTime[size];
+ }
+ };
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -158,6 +144,11 @@ public final class UnixEpochTime implements Parcelable {
dest.writeLong(mUnixEpochTimeMillis);
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
/**
* Creates a new Unix epoch time value at {@code elapsedRealtimeTimeMillis} by adjusting this
* Unix epoch time by the difference between the elapsed realtime value supplied and the one
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java
new file mode 100644
index 000000000000..0927fb830a0b
--- /dev/null
+++ b/core/java/android/credentials/ui/UserSelectionResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * User selection result information of a UX flow.
+ *
+ * Returned as part of the activity result intent data when the user dialog completes
+ * successfully.
+ *
+ * @hide
+ */
+public class UserSelectionResult implements Parcelable {
+
+ /**
+ * The intent extra key for the {@code UserSelectionResult} object when the credential selector
+ * activity finishes.
+ */
+ public static final String EXTRA_USER_SELECTION_RESULT =
+ "android.credentials.ui.extra.USER_SELECTION_RESULT";
+
+ @NonNull
+ private final IBinder mRequestToken;
+
+ // TODO: consider switching to string or other types, depending on the service implementation.
+ private final int mEntryId;
+
+ public UserSelectionResult(@NonNull IBinder requestToken, int entryId) {
+ mRequestToken = requestToken;
+ mEntryId = entryId;
+ }
+
+ /** Returns token of the app request that initiated this user dialog. */
+ @NonNull
+ public IBinder getRequestToken() {
+ return mRequestToken;
+ }
+
+ /** Returns the id of the visual entry that the user selected. */
+ public int geEntryId() {
+ return mEntryId;
+ }
+
+ protected UserSelectionResult(@NonNull Parcel in) {
+ IBinder requestToken = in.readStrongBinder();
+ int entryId = in.readInt();
+
+ mRequestToken = requestToken;
+ AnnotationValidations.validate(NonNull.class, null, mRequestToken);
+ mEntryId = entryId;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mRequestToken);
+ dest.writeInt(mEntryId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<UserSelectionResult> CREATOR =
+ new Creator<UserSelectionResult>() {
+ @Override
+ public UserSelectionResult createFromParcel(@NonNull Parcel in) {
+ return new UserSelectionResult(in);
+ }
+
+ @Override
+ public UserSelectionResult[] newArray(int size) {
+ return new UserSelectionResult[size];
+ }
+ };
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d71b0235d47d..adeb722833fa 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -296,8 +296,10 @@ public abstract class BatteryStats {
* New in version 35:
* - Fixed bug that was not reporting high cellular tx power correctly
* - Added out of service and emergency service modes to data connection types
+ * New in version 36:
+ * - Added PowerStats and CPU time-in-state data
*/
- static final int CHECKIN_VERSION = 35;
+ static final int CHECKIN_VERSION = 36;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -1810,6 +1812,36 @@ public abstract class BatteryStats {
}
/**
+ * CPU usage for a given UID.
+ */
+ public static final class CpuUsageDetails {
+ /**
+ * Descriptions of CPU power brackets, see PowerProfile.getCpuPowerBracketDescription
+ */
+ public String[] cpuBracketDescriptions;
+ public int uid;
+ /**
+ * The delta, in milliseconds, per CPU power bracket, from the previous record for the
+ * same UID.
+ */
+ public long[] cpuUsageMs;
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ UserHandle.formatUid(sb, uid);
+ sb.append(": ");
+ for (int bracket = 0; bracket < cpuUsageMs.length; bracket++) {
+ if (bracket != 0) {
+ sb.append(", ");
+ }
+ sb.append(cpuUsageMs[bracket]);
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
* Battery history record.
*/
public static final class HistoryItem {
@@ -1952,6 +1984,9 @@ public abstract class BatteryStats {
// Non-null when there is measured energy information
public MeasuredEnergyDetails measuredEnergyDetails;
+ // Non-null when there is CPU usage information
+ public CpuUsageDetails cpuUsageDetails;
+
public static final int EVENT_FLAG_START = 0x8000;
public static final int EVENT_FLAG_FINISH = 0x4000;
@@ -2161,6 +2196,7 @@ public abstract class BatteryStats {
eventTag = null;
tagsFirstOccurrence = false;
measuredEnergyDetails = null;
+ cpuUsageDetails = null;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -2211,6 +2247,7 @@ public abstract class BatteryStats {
tagsFirstOccurrence = o.tagsFirstOccurrence;
currentTime = o.currentTime;
measuredEnergyDetails = o.measuredEnergyDetails;
+ cpuUsageDetails = o.cpuUsageDetails;
}
public boolean sameNonEvent(HistoryItem o) {
@@ -6808,6 +6845,25 @@ public abstract class BatteryStats {
private String printNextItem(HistoryItem rec, long baseTime, boolean checkin,
boolean verbose) {
StringBuilder item = new StringBuilder();
+
+ if (rec.cpuUsageDetails != null
+ && rec.cpuUsageDetails.cpuBracketDescriptions != null
+ && checkin) {
+ String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+ for (int bracket = 0; bracket < descriptions.length; bracket++) {
+ item.append(BATTERY_STATS_CHECKIN_VERSION);
+ item.append(',');
+ item.append(HISTORY_DATA);
+ item.append(",0,XB,");
+ item.append(descriptions.length);
+ item.append(',');
+ item.append(bracket);
+ item.append(',');
+ item.append(descriptions[bracket]);
+ item.append("\n");
+ }
+ }
+
if (!checkin) {
item.append(" ");
TimeUtils.formatDuration(
@@ -7000,14 +7056,6 @@ public abstract class BatteryStats {
item.append("\"");
}
}
- if ((rec.states2 & HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
- if (!checkin) {
- item.append(" ext=");
- if (rec.measuredEnergyDetails != null) {
- item.append("E");
- }
- }
- }
if (rec.eventCode != HistoryItem.EVENT_NONE) {
item.append(checkin ? "," : " ");
if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) {
@@ -7036,6 +7084,58 @@ public abstract class BatteryStats {
item.append("\"");
}
}
+ boolean firstExtension = true;
+ if (rec.measuredEnergyDetails != null) {
+ firstExtension = false;
+ if (!checkin) {
+ item.append(" ext=energy:");
+ item.append(rec.measuredEnergyDetails);
+ } else {
+ item.append(",XE");
+ for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
+ if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
+ item.append(',');
+ item.append(rec.measuredEnergyDetails.consumers[i].name);
+ item.append('=');
+ item.append(rec.measuredEnergyDetails.chargeUC[i]);
+ }
+ }
+ }
+ }
+ if (rec.cpuUsageDetails != null) {
+ if (!checkin) {
+ if (!firstExtension) {
+ item.append("\n ");
+ }
+ String[] descriptions = rec.cpuUsageDetails.cpuBracketDescriptions;
+ if (descriptions != null) {
+ for (int bracket = 0; bracket < descriptions.length; bracket++) {
+ item.append(" ext=cpu-bracket:");
+ item.append(bracket);
+ item.append(":");
+ item.append(descriptions[bracket]);
+ item.append("\n ");
+ }
+ }
+ item.append(" ext=cpu:");
+ item.append(rec.cpuUsageDetails);
+ } else {
+ if (!firstExtension) {
+ item.append('\n');
+ item.append(BATTERY_STATS_CHECKIN_VERSION);
+ item.append(',');
+ item.append(HISTORY_DATA);
+ item.append(",0");
+ }
+ item.append(",XC,");
+ item.append(rec.cpuUsageDetails.uid);
+ for (int i = 0; i < rec.cpuUsageDetails.cpuUsageMs.length; i++) {
+ item.append(',');
+ item.append(rec.cpuUsageDetails.cpuUsageMs[i]);
+ }
+ }
+ firstExtension = false;
+ }
item.append("\n");
if (rec.stepDetails != null) {
if (!checkin) {
@@ -7132,25 +7232,6 @@ public abstract class BatteryStats {
item.append("\n");
}
}
- if (rec.measuredEnergyDetails != null) {
- if (!checkin) {
- item.append(" Energy: ");
- item.append(rec.measuredEnergyDetails);
- item.append("\n");
- } else {
- item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(',');
- item.append(HISTORY_DATA); item.append(",0,XE");
- for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
- if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
- item.append(',');
- item.append(rec.measuredEnergyDetails.consumers[i].name);
- item.append('=');
- item.append(rec.measuredEnergyDetails.chargeUC[i]);
- }
- }
- item.append("\n");
- }
- }
oldState = rec.states;
oldState2 = rec.states2;
// Clear High Tx Power Flag for volta positioning
@@ -7158,7 +7239,6 @@ public abstract class BatteryStats {
rec.states2 &= ~HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
}
}
-
return item.toString();
}
diff --git a/core/java/android/service/resumeonreboot/OWNERS b/core/java/android/service/resumeonreboot/OWNERS
index 721fbaf2d4ed..e09805310bbb 100644
--- a/core/java/android/service/resumeonreboot/OWNERS
+++ b/core/java/android/service/resumeonreboot/OWNERS
@@ -1 +1,2 @@
-ejyzhang@google.com \ No newline at end of file
+aveena@google.com
+ejyzhang@google.com
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index fc47778ea508..2c702299cf92 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -24,6 +24,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.Dialog;
import android.app.DirectAction;
@@ -2311,11 +2312,15 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
mAssistToken = assistToken;
}
- int getTaskId() {
+ /** @hide */
+ @TestApi
+ public int getTaskId() {
return mTaskId;
}
- IBinder getAssistToken() {
+ /** @hide */
+ @TestApi
+ @NonNull public IBinder getAssistToken() {
return mAssistToken;
}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 627631a376f7..d503904c2e3c 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -115,7 +115,7 @@ public class BrightnessSynchronizer {
Slog.i(TAG, "Setting initial brightness to default value of: " + defaultBrightness);
}
- mBrightnessSyncObserver.startObserving();
+ mBrightnessSyncObserver.startObserving(mHandler);
mHandler.sendEmptyMessageAtTime(MSG_RUN_UPDATE, mClock.uptimeMillis());
}
@@ -482,30 +482,31 @@ public class BrightnessSynchronizer {
}
};
- private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (selfChange) {
- return;
- }
- if (BRIGHTNESS_URI.equals(uri)) {
- handleBrightnessChangeInt(getScreenBrightnessInt());
+ private ContentObserver createBrightnessContentObserver(Handler handler) {
+ return new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) {
+ return;
+ }
+ if (BRIGHTNESS_URI.equals(uri)) {
+ handleBrightnessChangeInt(getScreenBrightnessInt());
+ }
}
- }
- };
+ };
+ }
boolean isObserving() {
return mIsObserving;
}
- void startObserving() {
+ void startObserving(Handler handler) {
final ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(BRIGHTNESS_URI, false, mContentObserver,
- UserHandle.USER_ALL);
- mDisplayManager.registerDisplayListener(mListener, mHandler,
+ cr.registerContentObserver(BRIGHTNESS_URI, false,
+ createBrightnessContentObserver(handler), UserHandle.USER_ALL);
+ mDisplayManager.registerDisplayListener(mListener, handler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
mIsObserving = true;
}
-
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 552334486356..696f0ffba518 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.BatteryStats.BitDescription;
+import android.os.BatteryStats.CpuUsageDetails;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.HistoryStepDetails;
import android.os.BatteryStats.HistoryTag;
@@ -78,7 +79,7 @@ public class BatteryStatsHistory {
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- private static final int VERSION = 208;
+ private static final int VERSION = 209;
private static final String HISTORY_DIR = "battery-history";
private static final String FILE_SUFFIX = ".bin";
@@ -122,6 +123,8 @@ public class BatteryStatsHistory {
static final int EXTENSION_MEASURED_ENERGY_HEADER_FLAG = 0x00000001;
static final int EXTENSION_MEASURED_ENERGY_FLAG = 0x00000002;
+ static final int EXTENSION_CPU_USAGE_HEADER_FLAG = 0x00000004;
+ static final int EXTENSION_CPU_USAGE_FLAG = 0x00000008;
private final Parcel mHistoryBuffer;
private final File mSystemDir;
@@ -194,6 +197,8 @@ public class BatteryStatsHistory {
private long mTrackRunningHistoryUptimeMs = 0;
private long mHistoryBaseTimeMs;
private boolean mMeasuredEnergyHeaderWritten = false;
+ private boolean mCpuUsageHeaderWritten = false;
+ private final VarintParceler mVarintParceler = new VarintParceler();
private byte mLastHistoryStepLevel = 0;
@@ -351,6 +356,7 @@ public class BatteryStatsHistory {
mTrackRunningHistoryElapsedRealtimeMs = 0;
mTrackRunningHistoryUptimeMs = 0;
mMeasuredEnergyHeaderWritten = false;
+ mCpuUsageHeaderWritten = false;
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
@@ -1180,7 +1186,7 @@ public class BatteryStatsHistory {
final int idx = code & HistoryItem.EVENT_TYPE_MASK;
final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" :
- (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
+ (code & HistoryItem.EVENT_FLAG_FINISH) != 0 ? "-" : "";
final String[] names = BatteryStats.HISTORY_EVENT_NAMES;
if (idx < 0 || idx >= names.length) return;
@@ -1191,6 +1197,17 @@ public class BatteryStatsHistory {
}
/**
+ * Records CPU usage by a specific UID. The recorded data is the delta from
+ * the previous record for the same UID.
+ */
+ public void recordCpuUsage(long elapsedRealtimeMs, long uptimeMs,
+ CpuUsageDetails cpuUsageDetails) {
+ mHistoryCur.cpuUsageDetails = cpuUsageDetails;
+ mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
* Writes changes to a HistoryItem state bitmap to Atrace.
*/
private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) {
@@ -1338,6 +1355,7 @@ public class BatteryStatsHistory {
entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
}
mMeasuredEnergyHeaderWritten = false;
+ mCpuUsageHeaderWritten = false;
// Make a copy of mHistoryCur.
HistoryItem copy = new HistoryItem();
@@ -1377,6 +1395,7 @@ public class BatteryStatsHistory {
cur.eventTag = null;
cur.tagsFirstOccurrence = false;
cur.measuredEnergyDetails = null;
+ cur.cpuUsageDetails = null;
if (DEBUG) {
Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ " now " + mHistoryBuffer.dataPosition()
@@ -1502,12 +1521,18 @@ public class BatteryStatsHistory {
extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
}
}
+ if (cur.cpuUsageDetails != null) {
+ extensionFlags |= EXTENSION_CPU_USAGE_FLAG;
+ if (!mCpuUsageHeaderWritten) {
+ extensionFlags |= BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG;
+ }
+ }
if (extensionFlags != 0) {
cur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
} else {
cur.states2 &= ~HistoryItem.STATE2_EXTENSIONS_FLAG;
}
- final boolean state2IntChanged = cur.states2 != last.states2;
+ final boolean state2IntChanged = cur.states2 != last.states2 || extensionFlags != 0;
if (state2IntChanged) {
firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
}
@@ -1641,9 +1666,19 @@ public class BatteryStatsHistory {
}
mMeasuredEnergyHeaderWritten = true;
}
- for (long chargeUC : cur.measuredEnergyDetails.chargeUC) {
- dest.writeLong(chargeUC);
+ mVarintParceler.writeLongArray(dest, cur.measuredEnergyDetails.chargeUC);
+ }
+
+ if (cur.cpuUsageDetails != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
}
+ if (!mCpuUsageHeaderWritten) {
+ dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
+ mCpuUsageHeaderWritten = true;
+ }
+ dest.writeInt(cur.cpuUsageDetails.uid);
+ mVarintParceler.writeLongArray(dest, cur.cpuUsageDetails.cpuUsageMs);
}
}
}
@@ -1892,4 +1927,74 @@ public class BatteryStatsHistory {
entry.getKey());
}
}
+
+ /**
+ * Writes/reads an array of longs into Parcel using a compact format, where small integers use
+ * fewer bytes. It is a bit more expensive than just writing the long into the parcel,
+ * but at scale saves a lot of storage and allows recording of longer battery history.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static final class VarintParceler {
+ /**
+ * Writes an array of longs into Parcel using the varint format, see
+ * https://developers.google.com/protocol-buffers/docs/encoding#varints
+ */
+ public void writeLongArray(Parcel parcel, long[] values) {
+ int out = 0;
+ int shift = 0;
+ for (long value : values) {
+ boolean done = false;
+ while (!done) {
+ final byte b;
+ if ((value & ~0x7FL) == 0) {
+ b = (byte) value;
+ done = true;
+ } else {
+ b = (byte) (((int) value & 0x7F) | 0x80);
+ value >>>= 7;
+ }
+ if (shift == 32) {
+ parcel.writeInt(out);
+ shift = 0;
+ out = 0;
+ }
+ out |= (b & 0xFF) << shift;
+ shift += 8;
+ }
+ }
+ if (shift != 0) {
+ parcel.writeInt(out);
+ }
+ }
+
+ /**
+ * Reads a long written with {@link #writeLongArray}
+ */
+ public void readLongArray(Parcel parcel, long[] values) {
+ int in = parcel.readInt();
+ int available = 4;
+ for (int i = 0; i < values.length; i++) {
+ long result = 0;
+ int shift;
+ for (shift = 0; shift < 64; shift += 7) {
+ if (available == 0) {
+ in = parcel.readInt();
+ available = 4;
+ }
+ final byte b = (byte) in;
+ in >>= 8;
+ available--;
+
+ result |= (long) (b & 0x7F) << shift;
+ if ((b & 0x80) == 0) {
+ values[i] = result;
+ break;
+ }
+ }
+ if (shift >= 64) {
+ throw new ParcelFormatException("Invalid varint format");
+ }
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index ee3d15b1ad7e..09fe1006ef8f 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -34,6 +34,9 @@ public class BatteryStatsHistoryIterator {
new BatteryStats.HistoryStepDetails();
private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails;
+ private BatteryStats.CpuUsageDetails mCpuUsageDetails;
+ private final BatteryStatsHistory.VarintParceler mVarintParceler =
+ new BatteryStatsHistory.VarintParceler();
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
mBatteryStatsHistory = history;
@@ -60,7 +63,7 @@ public class BatteryStatsHistoryIterator {
return true;
}
- void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
+ private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
int firstToken = src.readInt();
int deltaTimeToken = firstToken & BatteryStatsHistory.DELTA_TIME_MASK;
cur.cmd = BatteryStats.HistoryItem.CMD_UPDATE;
@@ -225,13 +228,35 @@ public class BatteryStatsHistoryIterator {
throw new IllegalStateException("MeasuredEnergyDetails without a header");
}
- for (int i = 0; i < mMeasuredEnergyDetails.chargeUC.length; i++) {
- mMeasuredEnergyDetails.chargeUC[i] = src.readLong();
- }
+ mVarintParceler.readLongArray(src, mMeasuredEnergyDetails.chargeUC);
cur.measuredEnergyDetails = mMeasuredEnergyDetails;
+ } else {
+ cur.measuredEnergyDetails = null;
+ }
+
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
+ mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
+ mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
+ mCpuUsageDetails.cpuUsageMs =
+ new long[mCpuUsageDetails.cpuBracketDescriptions.length];
+ } else if (mCpuUsageDetails != null) {
+ mCpuUsageDetails.cpuBracketDescriptions = null;
+ }
+
+ if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_FLAG) != 0) {
+ if (mCpuUsageDetails == null) {
+ throw new IllegalStateException("CpuUsageDetails without a header");
+ }
+
+ mCpuUsageDetails.uid = src.readInt();
+ mVarintParceler.readLongArray(src, mCpuUsageDetails.cpuUsageMs);
+ cur.cpuUsageDetails = mCpuUsageDetails;
+ } else {
+ cur.cpuUsageDetails = null;
}
} else {
cur.measuredEnergyDetails = null;
+ cur.cpuUsageDetails = null;
}
}
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index 07a8998b6f78..de3edeb22a40 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -33,7 +33,6 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
-@VisibleForTesting(visibility = PACKAGE)
public class KernelSingleUidTimeReader {
private static final String TAG = KernelSingleUidTimeReader.class.getName();
private static final boolean DBG = false;
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 7058341c5181..0df006d3a9bf 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -119,6 +119,14 @@ public class ProcessCpuTracker {
private final String[] mProcessFullStatsStringData = new String[6];
private final long[] mProcessFullStatsData = new long[6];
+ private static final int[] PROCESS_SCHEDSTATS_FORMAT = new int[] {
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ };
+
+ static final int PROCESS_SCHEDSTAT_CPU_TIME = 0;
+ static final int PROCESS_SCHEDSTAT_CPU_DELAY_TIME = 1;
+
private static final int[] SYSTEM_CPU_FORMAT = new int[] {
PROC_SPACE_TERM|PROC_COMBINE,
PROC_SPACE_TERM|PROC_OUT_LONG, // 1: user time
@@ -617,8 +625,8 @@ public class ProcessCpuTracker {
}
/**
- * Returns the total time (in milliseconds) spent executing in
- * both user and system code. Safe to call without lock held.
+ * Returns the total time (in milliseconds) the given PID has spent
+ * executing in both user and system code. Safe to call without lock held.
*/
public long getCpuTimeForPid(int pid) {
synchronized (mSinglePidStatsData) {
@@ -635,6 +643,22 @@ public class ProcessCpuTracker {
}
/**
+ * Returns the total time (in milliseconds) the given PID has spent waiting
+ * in the runqueue. Safe to call without lock held.
+ */
+ public long getCpuDelayTimeForPid(int pid) {
+ synchronized (mSinglePidStatsData) {
+ final String statFile = "/proc/" + pid + "/schedstat";
+ final long[] statsData = mSinglePidStatsData;
+ if (Process.readProcFile(statFile, PROCESS_SCHEDSTATS_FORMAT,
+ null, statsData, null)) {
+ return statsData[PROCESS_SCHEDSTAT_CPU_DELAY_TIME] / 1_000_000;
+ }
+ return 0;
+ }
+ }
+
+ /**
* @return time in milliseconds.
*/
final public int getLastUserTime() {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 55a26fee407f..953b36b0ef8a 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1508,8 +1508,7 @@ public class LockPatternUtils {
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
- SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1562,12 +1561,6 @@ public class LockPatternUtils {
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
- * Some authentication is required because the trustagent either timed out or was disabled
- * manually.
- */
- public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
-
- /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
new file mode 100644
index 000000000000..e2556d67b811
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.server.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.Metadata;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.util.ArrayMap;
+
+final class AidlTestUtils {
+
+ private AidlTestUtils() {
+ throw new UnsupportedOperationException("AidlTestUtils class is noninstantiable");
+ }
+
+ static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+ return new RadioManager.ProgramInfo(selector,
+ selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+ /* infoFlags= */ 0, signalQuality,
+ new RadioMetadata.Builder().build(), new ArrayMap<>());
+ }
+
+ static RadioManager.ProgramInfo makeProgramInfo(int programType,
+ ProgramSelector.Identifier identifier, int signalQuality) {
+ ProgramSelector selector = makeProgramSelector(programType, identifier);
+ return makeProgramInfo(selector, signalQuality);
+ }
+
+ static ProgramSelector makeFMSelector(long freq) {
+ return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ freq));
+ }
+
+ static ProgramSelector makeProgramSelector(int programType,
+ ProgramSelector.Identifier identifier) {
+ return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+ /* vendorIds= */ null);
+ }
+
+ static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
+ // Note that because ConversionUtils does not by design provide functions for all
+ // conversions, this function only copies fields that are set by makeProgramInfo().
+ ProgramInfo hwInfo = new ProgramInfo();
+ hwInfo.selector = ConversionUtils.programSelectorToHalProgramSelector(info.getSelector());
+ hwInfo.logicallyTunedTo =
+ ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo());
+ hwInfo.physicallyTunedTo =
+ ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo());
+ hwInfo.signalQuality = info.getSignalStrength();
+ hwInfo.relatedContent = new ProgramIdentifier[]{};
+ hwInfo.metadata = new Metadata[]{};
+ return hwInfo;
+ }
+
+ static ProgramInfo makeHalProgramSelector(
+ android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
+ ProgramInfo hwInfo = new ProgramInfo();
+ hwInfo.selector = hwSel;
+ hwInfo.logicallyTunedTo = hwSel.primaryId;
+ hwInfo.physicallyTunedTo = hwSel.primaryId;
+ hwInfo.signalQuality = hwSignalQuality;
+ hwInfo.relatedContent = new ProgramIdentifier[]{};
+ hwInfo.metadata = new Metadata[]{};
+ return hwInfo;
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
new file mode 100644
index 000000000000..518c3be098d6
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -0,0 +1,373 @@
+/*
+ * 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.server.broadcastradio.aidl;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+/**
+ * Tests for AIDL HAL RadioModule.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerSessionTest {
+ private static final VerificationWithTimeout CALLBACK_TIMEOUT =
+ timeout(/* millis= */ 200);
+
+ private final int mSignalQuality = 1;
+ private final long mAmfmFrequencySpacing = 500;
+ private final long[] mAmfmFrequencyList = {97500, 98100, 99100};
+
+ // Mocks
+ @Mock private IBroadcastRadio mBroadcastRadioMock;
+ private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks;
+
+ private final Object mLock = new Object();
+ // RadioModule under test
+ private RadioModule mRadioModule;
+
+ // Objects created by mRadioModule
+ private ITunerCallback mHalTunerCallback;
+ private ProgramInfo mHalCurrentInfo;
+ private TunerSession[] mTunerSessions;
+
+ @Before
+ public void setup() throws RemoteException {
+ mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
+ /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
+ /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
+ /* numAudioSources= */ 0, /* isInitializationRequired= */ false,
+ /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false,
+ new int[] {}, new int[] {},
+ /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock);
+
+ doAnswer(invocation -> {
+ mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
+ return null;
+ }).when(mBroadcastRadioMock).setTunerCallback(any());
+ mRadioModule.setInternalHalCallback();
+
+ doAnswer(invocation -> {
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+ (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0],
+ mSignalQuality);
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return null;
+ }).when(mBroadcastRadioMock).tune(any());
+
+ doAnswer(invocation -> {
+ if ((boolean) invocation.getArguments()[0]) {
+ mHalCurrentInfo.selector.primaryId.value += mAmfmFrequencySpacing;
+ } else {
+ mHalCurrentInfo.selector.primaryId.value -= mAmfmFrequencySpacing;
+ }
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return null;
+ }).when(mBroadcastRadioMock).step(anyBoolean());
+
+ doAnswer(invocation -> {
+ mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+ mHalCurrentInfo.selector.primaryId.value,
+ !(boolean) invocation.getArguments()[0]);
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return null;
+ }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+ when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
+ }
+
+ @Test
+ public void openSession_withMultipleSessions() throws RemoteException {
+ int numSessions = 3;
+
+ openAidlClients(numSessions);
+
+ for (int index = 0; index < numSessions; index++) {
+ assertWithMessage("Session of index %s close state", index)
+ .that(mTunerSessions[index].isClosed()).isFalse();
+ }
+ }
+
+ @Test
+ public void close_withOneSession() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].close();
+
+ assertWithMessage("Close state of broadcast radio service session")
+ .that(mTunerSessions[0].isClosed()).isTrue();
+ }
+
+ @Test
+ public void close_withOnlyOneSession_withMultipleSessions() throws RemoteException {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ int closeIdx = 0;
+
+ mTunerSessions[closeIdx].close();
+
+ for (int index = 0; index < numSessions; index++) {
+ if (index == closeIdx) {
+ assertWithMessage(
+ "Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isTrue();
+ } else {
+ assertWithMessage(
+ "Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isFalse();
+ }
+ }
+ }
+
+ @Test
+ public void close_withOneSession_withError() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+ int errorCode = RadioTuner.ERROR_SERVER_DIED;
+
+ mTunerSessions[0].close(errorCode);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode);
+ assertWithMessage("Close state of broadcast radio service session")
+ .that(mTunerSessions[0].isClosed()).isTrue();
+ }
+
+ @Test
+ public void closeSessions_withMultipleSessions_withError() throws RemoteException {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+
+ int errorCode = RadioTuner.ERROR_SERVER_DIED;
+ mRadioModule.closeSessions(errorCode);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode);
+ assertWithMessage("Close state of broadcast radio service session of index %s", index)
+ .that(mTunerSessions[index].isClosed()).isTrue();
+ }
+ }
+
+ @Test
+ public void tune_withOneSession() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+
+ mTunerSessions[0].tune(initialSel);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+ }
+
+ @Test
+ public void tune_withMultipleSessions() throws RemoteException {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ RadioManager.ProgramInfo tuneInfo =
+ AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+
+ mTunerSessions[0].tune(initialSel);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(tuneInfo);
+ }
+ }
+
+ @Test
+ public void step_withDirectionUp() throws RemoteException {
+ long initFreq = mAmfmFrequencyList[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ RadioManager.ProgramInfo stepUpInfo = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeFMSelector(initFreq + mAmfmFrequencySpacing),
+ mSignalQuality);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+ mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(stepUpInfo);
+ }
+
+ @Test
+ public void step_withDirectionDown() throws RemoteException {
+ long initFreq = mAmfmFrequencyList[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ RadioManager.ProgramInfo stepDownInfo = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeFMSelector(initFreq - mAmfmFrequencySpacing),
+ mSignalQuality);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+ mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(stepDownInfo);
+ }
+
+ @Test
+ public void scan_withDirectionUp() throws RemoteException {
+ long initFreq = mAmfmFrequencyList[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+ mSignalQuality);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(scanUpInfo);
+ }
+
+ @Test
+ public void scan_withDirectionDown() throws RemoteException {
+ long initFreq = mAmfmFrequencyList[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+ AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+ mSignalQuality);
+ openAidlClients(/* numClients= */ 1);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+
+ mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
+ .onCurrentProgramInfoChanged(scanUpInfo);
+ }
+
+ @Test
+ public void cancel() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ mTunerSessions[0].tune(initialSel);
+
+ mTunerSessions[0].cancel();
+
+ verify(mBroadcastRadioMock).cancel();
+ }
+
+ @Test
+ public void getImage_withInvalidId_throwsIllegalArgumentException() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+ int imageId = IBroadcastRadio.INVALID_IMAGE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mTunerSessions[0].getImage(imageId);
+ });
+
+ assertWithMessage("Exception for getting image with invalid ID")
+ .that(thrown).hasMessageThat().contains("Image ID is missing");
+ }
+
+ @Test
+ public void getImage_withValidId() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+ int imageId = 1;
+
+ Bitmap imageTest = mTunerSessions[0].getImage(imageId);
+
+ assertWithMessage("Null image").that(imageTest).isEqualTo(null);
+ }
+
+ @Test
+ public void startBackgroundScan() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+
+ mTunerSessions[0].startBackgroundScan();
+
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete();
+ }
+
+ @Test
+ public void stopProgramListUpdates() throws RemoteException {
+ openAidlClients(/* numClients= */ 1);
+ ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
+ mTunerSessions[0].startProgramListUpdates(aidlFilter);
+
+ mTunerSessions[0].stopProgramListUpdates();
+
+ verify(mBroadcastRadioMock).stopProgramListUpdates();
+ }
+
+ private void openAidlClients(int numClients) throws RemoteException {
+ mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
+ mTunerSessions = new TunerSession[numClients];
+ for (int index = 0; index < numClients; index++) {
+ mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
+ mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+ }
+ }
+
+ private long getSeekFrequency(long currentFrequency, boolean seekDown) {
+ long seekFrequency;
+ if (seekDown) {
+ seekFrequency = mAmfmFrequencyList[mAmfmFrequencyList.length - 1];
+ for (int i = mAmfmFrequencyList.length - 1; i >= 0; i--) {
+ if (mAmfmFrequencyList[i] < currentFrequency) {
+ seekFrequency = mAmfmFrequencyList[i];
+ break;
+ }
+ }
+ } else {
+ seekFrequency = mAmfmFrequencyList[0];
+ for (int index = 0; index < mAmfmFrequencyList.length; index++) {
+ if (mAmfmFrequencyList[index] > currentFrequency) {
+ seekFrequency = mAmfmFrequencyList[index];
+ break;
+ }
+ }
+ }
+ return seekFrequency;
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeStateTest.java b/core/tests/coretests/src/android/app/time/TimeStateTest.java
index a03229060dfb..bce09099e4d3 100644
--- a/core/tests/coretests/src/android/app/time/TimeStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeStateTest.java
@@ -16,12 +16,12 @@
package android.app.time;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.os.Parcel;
import android.os.ShellCommand;
import androidx.test.runner.AndroidJUnit4;
@@ -60,19 +60,8 @@ public class TimeStateTest {
@Test
public void testParceling() {
UnixEpochTime time = new UnixEpochTime(1, 2);
- TimeState value = new TimeState(time, true);
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeParcelable(value, 0);
-
- parcel.setDataPosition(0);
-
- TimeState stringValueCopy =
- parcel.readParcelable(null /* classLoader */, TimeState.class);
- assertEquals(value, stringValueCopy);
- } finally {
- parcel.recycle();
- }
+ assertRoundTripParcelable(new TimeState(time, true));
+ assertRoundTripParcelable(new TimeState(time, false));
}
@Test(expected = IllegalArgumentException.class)
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
index 9786bb044cb2..35a9dbc200e4 100644
--- a/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneStateTest.java
@@ -16,12 +16,12 @@
package android.app.time;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.os.Parcel;
import android.os.ShellCommand;
import androidx.test.runner.AndroidJUnit4;
@@ -59,19 +59,8 @@ public class TimeZoneStateTest {
@Test
public void testParceling() {
- TimeZoneState value = new TimeZoneState("Europe/London", true);
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeParcelable(value, 0);
-
- parcel.setDataPosition(0);
-
- TimeZoneState stringValueCopy =
- parcel.readParcelable(null /* classLoader */, TimeZoneState.class);
- assertEquals(value, stringValueCopy);
- } finally {
- parcel.recycle();
- }
+ assertRoundTripParcelable(new TimeZoneState("Europe/London", true));
+ assertRoundTripParcelable(new TimeZoneState("Europe/London", false));
}
@Test(expected = IllegalArgumentException.class)
diff --git a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
index cd753489b50e..3ab01f3d8832 100644
--- a/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
+++ b/core/tests/coretests/src/android/app/time/UnixEpochTimeTest.java
@@ -16,12 +16,12 @@
package android.app.time;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
import static android.app.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.os.Parcel;
import android.os.ShellCommand;
import androidx.test.runner.AndroidJUnit4;
@@ -57,19 +57,7 @@ public class UnixEpochTimeTest {
@Test
public void testParceling() {
- UnixEpochTime value = new UnixEpochTime(1000, 1);
- Parcel parcel = Parcel.obtain();
- try {
- parcel.writeParcelable(value, 0);
-
- parcel.setDataPosition(0);
-
- UnixEpochTime stringValueCopy =
- parcel.readParcelable(null /* classLoader */, UnixEpochTime.class);
- assertEquals(value, stringValueCopy);
- } finally {
- parcel.recycle();
- }
+ assertRoundTripParcelable(new UnixEpochTime(1000, 1));
}
@Test(expected = IllegalArgumentException.class)
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
new file mode 100644
index 000000000000..81cc9d830228
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.internal.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class ProcessCpuTrackerTest {
+ @Test
+ public void testGetCpuTime() throws Exception {
+ final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
+ assertThat(tracker.getCpuTimeForPid(android.os.Process.myPid())).isGreaterThan(0L);
+ }
+
+ @Test
+ public void testGetCpuDelayTime() throws Exception {
+ final ProcessCpuTracker tracker = new ProcessCpuTracker(false);
+ assertThat(tracker.getCpuDelayTimeForPid(android.os.Process.myPid())).isGreaterThan(0L);
+ }
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 7960dec5080b..82573b2b9acc 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,6 +44,7 @@ filegroup {
srcs: [
"src/com/android/wm/shell/util/**/*.java",
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+ "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
],
path: "src",
}
@@ -100,6 +101,21 @@ genrule {
out: ["wm_shell_protolog.json"],
}
+genrule {
+ name: "protolog.json.gz",
+ srcs: [":generate-wm_shell_protolog.json"],
+ out: ["wmshell.protolog.json.gz"],
+ cmd: "$(location minigzip) -c < $(in) > $(out)",
+ tools: ["minigzip"],
+}
+
+prebuilt_etc {
+ name: "wmshell.protolog.json.gz",
+ system_ext_specific: true,
+ src: ":protolog.json.gz",
+ filename_from_src: true,
+}
+
// End ProtoLog
java_library {
@@ -123,9 +139,6 @@ android_library {
resource_dirs: [
"res",
],
- java_resources: [
- ":generate-wm_shell_protolog.json",
- ],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.arch.core_core-runtime",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 86f9d5b534f4..8cbe44b15e42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,13 +29,6 @@ import com.android.wm.shell.common.annotations.ExternalThread;
public interface BackAnimation {
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 6f9c8b18625f..43f39b78ca1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.back;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -53,10 +54,12 @@ import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -102,6 +105,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
private final ContentResolver mContentResolver;
+ private final ShellController mShellController;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
private final Runnable mResetTransitionRunnable = () -> {
@@ -188,11 +192,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
public BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
Transitions transitions) {
- this(shellInit, shellExecutor, backgroundHandler,
+ this(shellInit, shellController, shellExecutor, backgroundHandler,
ActivityTaskManager.getService(), context, context.getContentResolver(),
transitions);
}
@@ -200,11 +205,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
BackAnimationController(
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver,
Transitions transitions) {
+ mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
mContext = context;
@@ -221,6 +228,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mBackTransitionHandler = new BackTransitionHandler(this);
mTransitions.addHandler(mBackTransitionHandler);
}
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
+ this::createExternalInterface, this);
}
private void setupAnimationDeveloperSettingsObserver(
@@ -253,7 +262,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return mBackAnimation;
}
- private final BackAnimation mBackAnimation = new BackAnimationImpl();
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IBackAnimationImpl(this);
+ }
+
+ private final BackAnimationImpl mBackAnimation = new BackAnimationImpl();
@Override
public Context getContext() {
@@ -266,17 +279,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private class BackAnimationImpl implements BackAnimation {
- private IBackAnimationImpl mBackAnimation;
-
- @Override
- public IBackAnimation createExternalInterface() {
- if (mBackAnimation != null) {
- mBackAnimation.invalidate();
- }
- mBackAnimation = new IBackAnimationImpl(BackAnimationController.this);
- return mBackAnimation;
- }
-
@Override
public void onBackMotion(
float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
@@ -295,7 +297,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private static class IBackAnimationImpl extends IBackAnimation.Stub {
+ private static class IBackAnimationImpl extends IBackAnimation.Stub
+ implements ExternalInterfaceBinder {
private BackAnimationController mController;
IBackAnimationImpl(BackAnimationController controller) {
@@ -315,7 +318,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
(controller) -> controller.clearBackToLauncherCallback());
}
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
index 9b370d87e386..aa5b0cb628e1 100644
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ExternalInterfaceBinder.java
@@ -14,15 +14,21 @@
* limitations under the License.
*/
-package com.android.server.accessibility.cursor;
+package com.android.wm.shell.common;
+
+import android.os.IBinder;
/**
- * Allows the Software Cursor feature to interface with its corresponding code in the SystemUI
- * process.
+ * An interface for binders which can be registered to be sent to other processes.
*/
-public final class SoftwareCursorManager {
+public interface ExternalInterfaceBinder {
+ /**
+ * Invalidates this binder (detaches it from the controller it would call).
+ */
+ void invalidate();
- public SoftwareCursorManager() {
- // TODO: Add behavior in a future CL.
- }
+ /**
+ * Returns the IBinder to send.
+ */
+ IBinder asBinder();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 7c3125be7b37..64dbfbbb738d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -261,14 +261,15 @@ public abstract class WMShellBaseModule {
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler,
Transitions transitions
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
- new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
- context, transitions));
+ new BackAnimationController(shellInit, shellController, shellExecutor,
+ backgroundHandler, context, transitions));
}
return Optional.empty();
}
@@ -472,6 +473,7 @@ public abstract class WMShellBaseModule {
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -479,9 +481,9 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, shellInit, shellCommandHandler,
- taskStackListener, activityTaskManager, desktopModeTaskRepository,
- mainExecutor));
+ RecentTasksController.create(context, shellInit, shellController,
+ shellCommandHandler, taskStackListener, activityTaskManager,
+ desktopModeTaskRepository, mainExecutor));
}
//
@@ -498,14 +500,15 @@ public abstract class WMShellBaseModule {
@Provides
static Transitions provideTransitions(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer organizer,
TransactionPool pool,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor);
+ return new Transitions(context, shellInit, shellController, organizer, pool,
+ displayController, mainExecutor, mainHandler, animExecutor);
}
@WMSingleton
@@ -622,13 +625,15 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static StartingWindowController provideStartingWindowController(Context context,
+ static StartingWindowController provideStartingWindowController(
+ Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ return new StartingWindowController(context, shellInit, shellController, shellTaskOrganizer,
splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
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 461d7dcf30ad..1ec98d3e94f3 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
@@ -598,7 +598,9 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
+ static DesktopModeController provideDesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -606,7 +608,7 @@ public abstract class WMShellModule {
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index ff3be38d09e1..44a467ffcf3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -23,9 +23,4 @@ import com.android.wm.shell.common.annotations.ExternalThread;
*/
@ExternalThread
public interface DesktopMode {
-
- /** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
- default IDesktopMode createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 99739c457aa6..b96facf4c46e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -19,9 +19,11 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
@@ -29,23 +31,30 @@ import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
+import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -55,18 +64,22 @@ import java.util.Comparator;
/**
* Handles windowing changes when desktop mode system setting changes
*/
-public class DesktopModeController implements RemoteCallable<DesktopModeController> {
+public class DesktopModeController implements RemoteCallable<DesktopModeController>,
+ Transitions.TransitionHandler {
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final Transitions mTransitions;
private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
- private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
+ private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
private final SettingsObserver mSettingsObserver;
- public DesktopModeController(Context context, ShellInit shellInit,
+ public DesktopModeController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@@ -74,6 +87,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
@ShellMainThread Handler mainHandler,
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTransitions = transitions;
@@ -85,10 +99,13 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
+ this::createExternalInterface, this);
mSettingsObserver.observe();
if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
+ mTransitions.addHandler(this);
}
@Override
@@ -108,6 +125,13 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
return mDesktopModeImpl;
}
+ /**
+ * Creates a new instance of the external interface to pass to another process.
+ */
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IDesktopModeImpl(this);
+ }
+
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -157,7 +181,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
/**
* Show apps on desktop
*/
- public void showDesktopApps() {
+ WindowContainerTransaction showDesktopApps() {
ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
@@ -173,7 +197,12 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
- mShellTaskOrganizer.applyTransaction(wct);
+
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
+
+ return wct;
}
/**
@@ -195,6 +224,35 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
.configuration.windowConfiguration.getWindowingMode();
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // This handler should never be the sole handler, so should not animate anything.
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+
+ // Only do anything if we are in desktop mode and opening a task/app
+ if (!DesktopModeStatus.isActive(mContext) || request.getType() != TRANSIT_OPEN) {
+ return null;
+ }
+
+ WindowContainerTransaction wct = mTransitions.dispatchRequest(transition, request, this);
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ wct.merge(showDesktopApps(), true /* transfer */);
+ wct.reorder(request.getTriggerTask().token, true /* onTop */);
+
+ return wct;
+ }
+
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
@@ -235,24 +293,15 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
*/
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
-
- private IDesktopModeImpl mIDesktopMode;
-
- @Override
- public IDesktopMode createExternalInterface() {
- if (mIDesktopMode != null) {
- mIDesktopMode.invalidate();
- }
- mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
- return mIDesktopMode;
- }
+ // Do nothing
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub {
+ private static class IDesktopModeImpl extends IDesktopMode.Stub
+ implements ExternalInterfaceBinder {
private DesktopModeController mController;
@@ -263,7 +312,8 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 2aa933d641fa..fbf326eadcd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -29,19 +29,37 @@ As mentioned in the [Dagger usage](dagger.md) docs, you need to determine whethe
### SysUI accessible components
In addition to doing the above, you will also need to provide an interface for calling to SysUI
from the Shell and vice versa. The current pattern is to have a parallel `Optional<Component name>`
-interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+interface that the `<Component name>Controller` implements and handles on the main Shell thread
+(see [SysUI/Shell threading](threading.md)).
In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
`SysUIComponent` to take the interface so it can be injected in SysUI code. The binding between
the two is done in `SystemUIFactory#init()` which will need to be updated as well.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the external interface within the controller
+- Have all incoming calls post to the main shell thread (inject @ShellMainThread Executor into the
+ controller if needed)
+- Note that callbacks into SysUI should take an associated executor to call back on
+
### Launcher accessible components
Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
Launcher requires a new AIDL interface to be created and implemented by the controller. The
implementation of the stub interface in the controller otherwise behaves similar to the interface
to SysUI where it posts the work to the main Shell thread.
+Specifically, to support calling into a controller from an external process (like Launcher):
+- Create an implementation of the interface binder's `Stub` class within the controller, have it
+ extend `ExternalInterfaceBinder` and implement `invalidate()` to ensure it doesn't hold long
+ references to the outer controller
+- Make the controller implement `RemoteCallable<T>`, and have all incoming calls use one of
+ the `ExecutorUtils.executeRemoteCallWithTaskPermission()` calls to verify the caller's identity
+ and ensure the call happens on the main shell thread and not the binder thread
+- Inject `ShellController` and add the instance of the implementation as external interface
+- In Launcher, update `TouchInteractionService` to pass the interface to `SystemUIProxy`, and then
+ call the SystemUIProxy method as needed in that code
+
### Component initialization
To initialize the component:
- On the Shell side, you potentially need to do two things to initialize the component:
@@ -64,8 +82,9 @@ adb logcat *:S WindowManagerShell
### General Do's & Dont's
Do:
-- Do add unit tests for all new components
-- Do keep controllers simple and break them down as needed
+- Add unit tests for all new components
+- Keep controllers simple and break them down as needed
+- Any SysUI callbacks should also take an associated executor to run the callback on
Don't:
- **Don't** do initialization in the constructor, only do initialization in the init callbacks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
index 935666026bf4..f86d467360f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
@@ -33,9 +33,4 @@ public interface FloatingTasks {
* - If there is a floating task for this intent, and it's not stashed, this stashes it.
*/
void showOrSetStashed(Intent intent);
-
- /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */
- default IFloatingTasks createExternalInterface() {
- return null;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
index 67552991869b..b3c09d32055b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
@@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
import android.annotation.Nullable;
import android.content.Context;
@@ -40,6 +41,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -136,11 +138,13 @@ public class FloatingTasksController implements RemoteCallable<FloatingTasksCont
if (isFloatingTasksEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
- mShellCommandHandler.addDumpCallback(this::dump, this);
}
protected void onInit() {
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
+ this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/** Only used for testing. */
@@ -168,6 +172,10 @@ public class FloatingTasksController implements RemoteCallable<FloatingTasksCont
return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IFloatingTasksImpl(this);
+ }
+
@Override
public void onThemeChanged() {
if (mIsFloatingLayerAdded) {
@@ -412,28 +420,18 @@ public class FloatingTasksController implements RemoteCallable<FloatingTasksCont
*/
@ExternalThread
private class FloatingTaskImpl implements FloatingTasks {
- private IFloatingTasksImpl mIFloatingTasks;
-
@Override
public void showOrSetStashed(Intent intent) {
mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
}
-
- @Override
- public IFloatingTasks createExternalInterface() {
- if (mIFloatingTasks != null) {
- mIFloatingTasks.invalidate();
- }
- mIFloatingTasks = new IFloatingTasksImpl(FloatingTasksController.this);
- return mIFloatingTasks;
- }
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IFloatingTasksImpl extends IFloatingTasks.Stub {
+ private static class IFloatingTasksImpl extends IFloatingTasks.Stub
+ implements ExternalInterfaceBinder {
private FloatingTasksController mController;
IFloatingTasksImpl(FloatingTasksController controller) {
@@ -443,7 +441,8 @@ public class FloatingTasksController implements RemoteCallable<FloatingTasksCont
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 7129165a78dc..2ee334873780 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -30,13 +30,6 @@ public interface OneHanded {
OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
/**
- * Returns a binder that can be passed to an external process to manipulate OneHanded.
- */
- default IOneHanded createExternalInterface() {
- return null;
- }
-
- /**
* Enters one handed mode.
*/
void startOneHanded();
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 e0c4fe8c4fba..679d4ca2ac48 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
@@ -24,6 +24,7 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import android.annotation.BinderThread;
import android.content.ComponentName;
@@ -49,6 +50,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
@@ -296,12 +298,18 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED,
+ this::createExternalInterface, this);
}
public OneHanded asOneHanded() {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IOneHandedImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -709,17 +717,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
*/
@ExternalThread
private class OneHandedImpl implements OneHanded {
- private IOneHandedImpl mIOneHanded;
-
- @Override
- public IOneHanded createExternalInterface() {
- if (mIOneHanded != null) {
- mIOneHanded.invalidate();
- }
- mIOneHanded = new IOneHandedImpl(OneHandedController.this);
- return mIOneHanded;
- }
-
@Override
public void startOneHanded() {
mMainExecutor.execute(() -> {
@@ -767,7 +764,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IOneHandedImpl extends IOneHanded.Stub {
+ private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder {
private OneHandedController mController;
IOneHandedImpl(OneHandedController controller) {
@@ -777,7 +774,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 72b9dd37ac7d..f34d2a827e69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -27,14 +27,6 @@ import java.util.function.Consumer;
*/
@ExternalThread
public interface Pip {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate PIP.
- */
- default IPip createExternalInterface() {
- return null;
- }
-
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 3345b1b2d0e8..a918559d897e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -33,6 +33,7 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -68,6 +69,7 @@ import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -632,6 +634,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
+ }
+
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
}
@Override
@@ -1040,17 +1048,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* The interface for calls from outside the Shell, within the host process.
*/
private class PipImpl implements Pip {
- private IPipImpl mIPip;
-
- @Override
- public IPip createExternalInterface() {
- if (mIPip != null) {
- mIPip.invalidate();
- }
- mIPip = new IPipImpl(PipController.this);
- return mIPip;
- }
-
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
@@ -1098,7 +1095,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IPipImpl extends IPip.Stub {
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
private PipController mController;
private final SingleInstanceRemoteListener<PipController,
IPipAnimationListener> mListener;
@@ -1129,7 +1126,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
index 552ebde05274..93ffb3dc8115 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java
@@ -17,22 +17,14 @@
package com.android.wm.shell.protolog;
import android.annotation.Nullable;
-import android.content.Context;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.BaseProtoLogImpl;
import com.android.internal.protolog.ProtoLogViewerConfigReader;
import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.wm.shell.R;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
-import org.json.JSONException;
-
/**
* A service for the ProtoLog logging system.
@@ -40,8 +32,9 @@ import org.json.JSONException;
public class ShellProtoLogImpl extends BaseProtoLogImpl {
private static final String TAG = "ProtoLogImpl";
private static final int BUFFER_CAPACITY = 1024 * 1024;
- // TODO: Get the right path for the proto log file when we initialize the shell components
- private static final String LOG_FILENAME = new File("wm_shell_log.pb").getAbsolutePath();
+ // TODO: find a proper location to save the protolog message file
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope";
+ private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz";
private static ShellProtoLogImpl sServiceInstance = null;
@@ -111,18 +104,8 @@ public class ShellProtoLogImpl extends BaseProtoLogImpl {
}
public int startTextLogging(String[] groups, PrintWriter pw) {
- try (InputStream is =
- getClass().getClassLoader().getResourceAsStream("wm_shell_protolog.json")){
- mViewerConfig.loadViewerConfig(is);
- return setLogging(true /* setTextLogging */, true, pw, groups);
- } catch (IOException e) {
- Log.i(TAG, "Unable to load log definitions: IOException while reading "
- + "wm_shell_protolog. " + e);
- } catch (JSONException e) {
- Log.i(TAG, "Unable to load log definitions: JSON parsing exception while reading "
- + "wm_shell_protolog. " + e);
- }
- return -1;
+ mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+ return setLogging(true /* setTextLogging */, true, pw, groups);
}
public int stopTextLogging(String[] groups, PrintWriter pw) {
@@ -130,7 +113,8 @@ public class ShellProtoLogImpl extends BaseProtoLogImpl {
}
private ShellProtoLogImpl() {
- super(new File(LOG_FILENAME), null, BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+ super(new File(LOG_FILENAME), VIEWER_CONFIG_FILENAME, BUFFER_CAPACITY,
+ new ProtoLogViewerConfigReader());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 2a625524b48b..069066e4bd49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -29,13 +29,6 @@ import java.util.function.Consumer;
@ExternalThread
public interface RecentTasks {
/**
- * Returns a binder that can be passed to an external process to fetch recent tasks.
- */
- default IRecentTasks createExternalInterface() {
- return null;
- }
-
- /**
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 02b5a35f653b..08f3db65e62f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -20,6 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.PackageManager.FEATURE_PC;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -37,6 +38,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -48,6 +50,7 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -69,11 +72,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
private static final String TAG = RecentTasksController.class.getSimpleName();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
- private final RecentTasks mImpl = new RecentTasksImpl();
+ private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -97,6 +101,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
public static RecentTasksController create(
Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
@@ -106,18 +111,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
- activityTaskManager, desktopModeTaskRepository, mainExecutor);
+ return new RecentTasksController(context, shellInit, shellController, shellCommandHandler,
+ taskStackListener, activityTaskManager, desktopModeTaskRepository, mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
@@ -131,7 +138,13 @@ public class RecentTasksController implements TaskStackListenerCallback,
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IRecentTasksImpl(this);
+ }
+
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mTaskStackListener.addListener(this);
mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this));
@@ -366,17 +379,6 @@ public class RecentTasksController implements TaskStackListenerCallback,
*/
@ExternalThread
private class RecentTasksImpl implements RecentTasks {
- private IRecentTasksImpl mIRecentTasks;
-
- @Override
- public IRecentTasks createExternalInterface() {
- if (mIRecentTasks != null) {
- mIRecentTasks.invalidate();
- }
- mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
- return mIRecentTasks;
- }
-
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
Consumer<List<GroupedRecentTaskInfo>> callback) {
@@ -393,7 +395,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IRecentTasksImpl extends IRecentTasks.Stub {
+ private static class IRecentTasksImpl extends IRecentTasks.Stub
+ implements ExternalInterfaceBinder {
private RecentTasksController mController;
private final SingleInstanceRemoteListener<RecentTasksController,
IRecentTasksListener> mListener;
@@ -424,7 +427,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e73b799b7a3d..d86aadc996e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -70,13 +70,6 @@ public interface SplitScreen {
/** Unregisters listener that gets split screen callback. */
void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 07a6895e2720..eeb2c0f1be6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -29,6 +29,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
@@ -71,6 +72,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -214,6 +216,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new ISplitScreenImpl(this);
+ }
+
/**
* This will be called after ShellTaskOrganizer has initialized/registered because of the
* dependency order.
@@ -224,6 +230,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler,
this);
mShellController.addKeyguardChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN,
+ this::createExternalInterface, this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = createStageCoordinator();
@@ -658,7 +666,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
*/
@ExternalThread
private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
@Override
@@ -704,15 +711,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
};
@Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
if (mExecutors.containsKey(listener)) return;
@@ -752,7 +750,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private static class ISplitScreenImpl extends ISplitScreen.Stub
+ implements ExternalInterfaceBinder {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
@@ -779,7 +778,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
index 76105a39189b..538bbec2aa2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurface.java
@@ -22,14 +22,6 @@ import android.graphics.Color;
* Interface to engage starting window feature.
*/
public interface StartingSurface {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate starting windows.
- */
- default IStartingWindow createExternalInterface() {
- return null;
- }
-
/**
* Returns the background color for a starting window if existing.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index 379af21ac956..0c23f109feaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,6 +23,7 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -43,10 +44,12 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
/**
@@ -76,6 +79,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellController mShellController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
@@ -86,12 +90,14 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
public StartingWindowController(Context context,
ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellController = shellController;
mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
@@ -107,8 +113,14 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IStartingWindowImpl(this);
+ }
+
private void onInit() {
mShellTaskOrganizer.initStartingWindow(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW,
+ this::createExternalInterface, this);
}
@Override
@@ -222,17 +234,6 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
* The interface for calls from outside the Shell, within the host process.
*/
private class StartingSurfaceImpl implements StartingSurface {
- private IStartingWindowImpl mIStartingWindow;
-
- @Override
- public IStartingWindowImpl createExternalInterface() {
- if (mIStartingWindow != null) {
- mIStartingWindow.invalidate();
- }
- mIStartingWindow = new IStartingWindowImpl(StartingWindowController.this);
- return mIStartingWindow;
- }
-
@Override
public int getBackgroundColor(TaskInfo taskInfo) {
synchronized (mTaskBackgroundColors) {
@@ -256,7 +257,8 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IStartingWindowImpl extends IStartingWindow.Stub {
+ private static class IStartingWindowImpl extends IStartingWindow.Stub
+ implements ExternalInterfaceBinder {
private StartingWindowController mController;
private SingleInstanceRemoteListener<StartingWindowController,
IStartingWindowListener> mListener;
@@ -276,7 +278,8 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mController = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 57993948886b..fdf073f0bf26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -23,23 +23,28 @@ import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
/**
* Handles event callbacks from SysUI that can be used within the Shell.
@@ -59,6 +64,11 @@ public class ShellController {
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
+ new ArrayMap<>();
+ // References to the existing interfaces, to be invalidated when they are recreated
+ private ArrayMap<String, ExternalInterfaceBinder> mExternalInterfaces = new ArrayMap<>();
+
private Configuration mLastConfiguration;
@@ -67,6 +77,11 @@ public class ShellController {
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellCommandHandler.addDumpCallback(this::dump, this);
}
/**
@@ -124,6 +139,47 @@ public class ShellController {
mUserChangeListeners.remove(listener);
}
+ /**
+ * Adds an interface that can be called from a remote process. This method takes a supplier
+ * because each binder reference is valid for a single process, and in multi-user mode, SysUI
+ * will request new binder instances for each instance of Launcher that it provides binders
+ * to.
+ *
+ * @param extra the key for the interface, {@see ShellSharedConstants}
+ * @param binderSupplier the supplier of the binder to pass to the external process
+ * @param callerInstance the instance of the caller, purely for logging
+ */
+ public void addExternalInterface(String extra, Supplier<ExternalInterfaceBinder> binderSupplier,
+ Object callerInstance) {
+ ProtoLog.v(WM_SHELL_INIT, "Adding external interface from %s with key %s",
+ callerInstance.getClass().getSimpleName(), extra);
+ if (mExternalInterfaceSuppliers.containsKey(extra)) {
+ throw new IllegalArgumentException("Supplier with same key already exists: "
+ + extra);
+ }
+ mExternalInterfaceSuppliers.put(extra, binderSupplier);
+ }
+
+ /**
+ * Updates the given bundle with the set of external interfaces, invalidating the old set of
+ * binders.
+ */
+ private void createExternalInterfaces(Bundle output) {
+ // Invalidate the old binders
+ for (int i = 0; i < mExternalInterfaces.size(); i++) {
+ mExternalInterfaces.valueAt(i).invalidate();
+ }
+ mExternalInterfaces.clear();
+
+ // Create new binders for each key
+ for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
+ final String key = mExternalInterfaceSuppliers.keyAt(i);
+ final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
+ mExternalInterfaces.put(key, b);
+ output.putBinder(key, b.asBinder());
+ }
+ }
+
@VisibleForTesting
void onConfigurationChanged(Configuration newConfig) {
// The initial config is send on startup and doesn't trigger listener callbacks
@@ -204,6 +260,14 @@ public class ShellController {
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
pw.println(innerPrefix + "mUserChangeListeners=" + mUserChangeListeners.size());
+
+ if (!mExternalInterfaces.isEmpty()) {
+ pw.println(innerPrefix + "mExternalInterfaces={");
+ for (String key : mExternalInterfaces.keySet()) {
+ pw.println(innerPrefix + "\t" + key + ": " + mExternalInterfaces.get(key));
+ }
+ pw.println(innerPrefix + "}");
+ }
}
/**
@@ -211,7 +275,6 @@ public class ShellController {
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
-
@Override
public void onInit() {
try {
@@ -222,28 +285,6 @@ public class ShellController {
}
@Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = mShellCommandHandler.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
-
- @Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
@@ -274,5 +315,38 @@ public class ShellController {
mMainExecutor.execute(() ->
ShellController.this.onUserProfilesChanged(profiles));
}
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void createExternalInterfaces(Bundle bundle) {
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ ShellController.this.createExternalInterfaces(bundle);
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to get Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index 2108c824ac6f..bc5dd11ef54e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.sysui;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -37,18 +38,6 @@ public interface ShellInterface {
default void onInit() {}
/**
- * Dumps the shell state.
- */
- default void dump(PrintWriter pw) {}
-
- /**
- * Handles a shell command.
- */
- default boolean handleCommand(final String[] args, PrintWriter pw) {
- return false;
- }
-
- /**
* Notifies the Shell that the configuration has changed.
*/
default void onConfigurationChanged(Configuration newConfiguration) {}
@@ -74,4 +63,21 @@ public interface ShellInterface {
* Notifies the Shell when a profile belonging to the user changes.
*/
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
+ * Updates the given {@param bundle} with the set of exposed interfaces.
+ */
+ default void createExternalInterfaces(Bundle bundle) {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
new file mode 100644
index 000000000000..bdda6a8e926b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -0,0 +1,43 @@
+/*
+ * 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.sysui;
+
+/**
+ * General shell-related constants that are shared with users of the library.
+ */
+public class ShellSharedConstants {
+ // See IPip.aidl
+ public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See ISplitScreen.aidl
+ public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
+ // See IOneHanded.aidl
+ public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
+ // See IShellTransitions.aidl
+ public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
+ "extra_shell_shell_transitions";
+ // See IStartingWindow.aidl
+ public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
+ "extra_shell_starting_window";
+ // See IRecentTasks.aidl
+ public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
+ // See IBackAnimation.aidl
+ public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
+ // See IFloatingTasks.aidl
+ public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
+ // See IDesktopMode.aidl
+ public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
index b34049d4ec42..da39017a0313 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ShellTransitions.java
@@ -27,14 +27,6 @@ import com.android.wm.shell.common.annotations.ExternalThread;
*/
@ExternalThread
public interface ShellTransitions {
-
- /**
- * Returns a binder that can be passed to an external process to manipulate remote transitions.
- */
- default IShellTransitions createExternalInterface() {
- return null;
- }
-
/**
* Registers a remote transition.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 63e01a760cfb..aaaccd837298 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -30,6 +30,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -62,11 +63,13 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -116,6 +119,7 @@ public class Transitions implements RemoteCallable<Transitions> {
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
+ private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -143,6 +147,7 @@ public class Transitions implements RemoteCallable<Transitions> {
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
+ @NonNull ShellController shellController,
@NonNull WindowOrganizer organizer,
@NonNull TransactionPool pool,
@NonNull DisplayController displayController,
@@ -157,10 +162,14 @@ public class Transitions implements RemoteCallable<Transitions> {
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ mShellController = shellController;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
+
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
@@ -194,6 +203,10 @@ public class Transitions implements RemoteCallable<Transitions> {
return mImpl;
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IShellTransitionsImpl(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -546,6 +559,22 @@ public class Transitions implements RemoteCallable<Transitions> {
"This shouldn't happen, maybe the default handler is broken.");
}
+ /**
+ * Gives every handler (in order) a chance to handle request until one consumes the transition.
+ * @return the WindowContainerTransaction given by the handler which consumed the transition.
+ */
+ public WindowContainerTransaction dispatchRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @Nullable TransitionHandler skip) {
+ for (int i = mHandlers.size() - 1; i >= 0; --i) {
+ if (mHandlers.get(i) == skip) continue;
+ WindowContainerTransaction wct = mHandlers.get(i).handleRequest(transition, request);
+ if (wct != null) {
+ return wct;
+ }
+ }
+ return null;
+ }
+
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
@@ -901,17 +930,6 @@ public class Transitions implements RemoteCallable<Transitions> {
*/
@ExternalThread
private class ShellTransitionImpl implements ShellTransitions {
- private IShellTransitionsImpl mIShellTransitions;
-
- @Override
- public IShellTransitions createExternalInterface() {
- if (mIShellTransitions != null) {
- mIShellTransitions.invalidate();
- }
- mIShellTransitions = new IShellTransitionsImpl(Transitions.this);
- return mIShellTransitions;
- }
-
@Override
public void registerRemote(@NonNull TransitionFilter filter,
@NonNull RemoteTransition remoteTransition) {
@@ -932,7 +950,8 @@ public class Transitions implements RemoteCallable<Transitions> {
* The interface for calls from outside the host process.
*/
@BinderThread
- private static class IShellTransitionsImpl extends IShellTransitions.Stub {
+ private static class IShellTransitionsImpl extends IShellTransitions.Stub
+ implements ExternalInterfaceBinder {
private Transitions mTransitions;
IShellTransitionsImpl(Transitions transitions) {
@@ -942,7 +961,8 @@ public class Transitions implements RemoteCallable<Transitions> {
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
- void invalidate() {
+ @Override
+ public void invalidate() {
mTransitions = null;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index cb49e18d672c..a2eefece4995 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -106,7 +106,7 @@ class DismissSplitScreenByGoHome(
fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest(bugId = 251268711)
@Test
override fun entireScreenCovered() =
super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 504238f15b8c..201594bca8e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,7 +51,6 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -117,7 +117,7 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest(bugId = 251269324)
@Test
override fun entireScreenCovered() =
super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 2ecf81931e4a..553840cf0e47 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.WindowManagerPolicyConstants
@@ -104,7 +105,7 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest
@Test
override fun entireScreenCovered() =
super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 384489d99de3..e2f7f7e583be 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.WindowManagerPolicyConstants
@@ -103,7 +104,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest
@Test
override fun entireScreenCovered() =
super.entireScreenCovered()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 04ebbf527b3d..d7b3ec2256c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.WindowManagerPolicyConstants
@@ -150,7 +151,7 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index ac5236f95040..6484b0759bd7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -21,6 +21,7 @@ import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -60,7 +61,9 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -102,6 +105,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Mock
private Transitions mTransitions;
+ @Mock
+ private ShellController mShellController;
+
private BackAnimationController mController;
private int mEventTime = 0;
@@ -118,7 +124,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
ANIMATION_ENABLED);
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
- mController = new BackAnimationController(mShellInit,
+ mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
mContentResolver, mTransitions);
@@ -167,6 +173,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ }
+
+ @Test
public void verifyAnimationFinishes() {
RemoteAnimationTarget animationTarget = createAnimationTarget();
boolean[] backNavigationDone = new boolean[]{false};
@@ -210,7 +222,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Toggle the setting off
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
- mController = new BackAnimationController(shellInit,
+ mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
mContentResolver, mTransitions);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dd23d97d9199..c850a3b3b780 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -52,6 +52,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -68,6 +69,8 @@ import org.mockito.Mockito;
public class DesktopModeControllerTest extends ShellTestCase {
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellTaskOrganizer mShellTaskOrganizer;
@Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@@ -94,8 +97,8 @@ public class DesktopModeControllerTest extends ShellTestCase {
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
- mRootTaskDisplayAreaOrganizer, mMockTransitions,
+ mController = new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
mDesktopModeTaskRepository, mMockHandler, mExecutor);
when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
index a88c83779f25..d378a177650a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
@@ -52,6 +52,7 @@ import com.android.wm.shell.floating.views.FloatingTaskLayer;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.After;
import org.junit.Before;
@@ -168,6 +169,18 @@ public class FloatingTasksControllerTest extends ShellTestCase {
}
}
+ @Test
+ public void onInit_addExternalInterface() {
+ if (FLOATING_TASKS_ACTUALLY_ENABLED) {
+ createController();
+ setUpTabletConfig();
+ mController.onInit();
+
+ verify(mShellController, times(1)).addExternalInterface(
+ ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
+ }
+ }
+
//
// Tests for floating layer, which is only available for tablets.
//
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 cf8297eec061..8ad3d2a72617 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
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -176,6 +177,12 @@ public class OneHandedControllerTest extends OneHandedTestCase {
}
@Test
+ public void testControllerRegisteresExternalInterface() {
+ verify(mMockShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 1e08f1e55797..d06fb55a5769 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -61,6 +62,7 @@ import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -152,6 +154,12 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
+ public void instantiatePipController_registerExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+ }
+
+ @Test
public void instantiatePipController_registerUserChangeListener() {
verify(mShellController, times(1)).addUserChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b8aaaa76e3c7..f6ac3ee0a8e4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -57,7 +58,9 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -84,6 +87,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
+ private ShellController mShellController;
+ @Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@@ -101,7 +106,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -121,6 +126,12 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ }
+
+ @Test
public void testAddRemoveSplitNotifyChange() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 5a68361c595c..55883ab2ef70 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -58,6 +58,7 @@ import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -133,6 +134,15 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ public void instantiateController_addExternalInterface() {
+ doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onInit();
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any());
+ }
+
+ @Test
public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 35515e3bb6e8..90165d1cd1b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -36,7 +37,9 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -56,25 +59,34 @@ public class StartingWindowControllerTests extends ShellTestCase {
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
- private @Mock ShellInit mShellInit;
+ private @Mock ShellController mShellController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
private @Mock IconProvider mIconProvider;
private @Mock TransactionPool mTransactionPool;
private StartingWindowController mController;
+ private ShellInit mShellInit;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
- mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
- mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit = spy(new ShellInit(mMainExecutor));
+ mController = new StartingWindowController(mContext, mShellInit, mShellController,
+ mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ mShellInit.init();
}
@Test
- public void instantiate_addInitCallback() {
+ public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
+
+ @Test
+ public void instantiateController_addExternalInterface() {
+ verify(mShellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index d6ddba9e927d..fbc50c68eff9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.sysui;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -30,6 +34,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
@@ -49,6 +54,7 @@ import java.util.Locale;
public class ShellControllerTest extends ShellTestCase {
private static final int TEST_USER_ID = 100;
+ private static final String EXTRA_TEST_BINDER = "test_binder";
@Mock
private ShellInit mShellInit;
@@ -81,6 +87,47 @@ public class ShellControllerTest extends ShellTestCase {
}
@Test
+ public void testAddExternalInterface_ensureCallback() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+
+ Bundle b = new Bundle();
+ mController.asShell().createExternalInterfaces(b);
+ assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
+ }
+
+ @Test
+ public void testAddExternalInterface_disallowDuplicateKeys() {
+ Binder callback = new Binder();
+ ExternalInterfaceBinder wrapper = new ExternalInterfaceBinder() {
+ @Override
+ public void invalidate() {
+ // Do nothing
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return callback;
+ }
+ };
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ assertThrows(IllegalArgumentException.class, () -> {
+ mController.addExternalInterface(EXTRA_TEST_BINDER, () -> wrapper, this);
+ });
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index db9136d8eacb..c764741d4cd6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -87,7 +87,9 @@ import com.android.wm.shell.common.DisplayController;
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.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -124,12 +126,25 @@ public class ShellTransitionTests extends ShellTestCase {
@Test
public void instantiate_addInitCallback() {
ShellInit shellInit = mock(ShellInit.class);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
verify(shellInit, times(1)).addInitCallback(any(), eq(t));
}
@Test
+ public void instantiateController_addExternalInterface() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ ShellController shellController = mock(ShellController.class);
+ final Transitions t = new Transitions(mContext, shellInit, shellController,
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ shellInit.init();
+ verify(shellController, times(1)).addExternalInterface(
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1063,8 +1078,9 @@ public class ShellTransitionTests extends ShellTestCase {
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
- final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
- createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+ mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
shellInit.init();
return t;
}
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index a924b977f288..5cf10678622e 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -36,6 +36,9 @@ public final class GnssMeasurementRequest implements Parcelable {
* measurements or power usage itself, but may receive GNSS measurements generated in response
* to other requests.
*
+ * <p class="note">Note that on Android T, such a request will trigger one GNSS measurement.
+ * Another GNSS measurement will be triggered after {@link #PASSIVE_INTERVAL} and so on.
+ *
* @see GnssMeasurementRequest#getIntervalMillis()
*/
public static final int PASSIVE_INTERVAL = Integer.MAX_VALUE;
@@ -94,6 +97,10 @@ public final class GnssMeasurementRequest implements Parcelable {
* GNSS chipset can report.
*
* <p>The GNSS chipset may report measurements with a rate faster than requested.
+ *
+ * <p class="note">Note that on Android T, a request interval of {@link #PASSIVE_INTERVAL}
+ * will first trigger one GNSS measurement. Another GNSS measurement will be triggered after
+ * {@link #PASSIVE_INTERVAL} milliseconds ans so on.
*/
public @IntRange(from = 0) int getIntervalMillis() {
return mIntervalMillis;
@@ -232,6 +239,10 @@ public final class GnssMeasurementRequest implements Parcelable {
* <p>An interval of 0 milliseconds means the fastest rate the chipset can report.
*
* <p>The GNSS chipset may report measurements with a rate faster than requested.
+ *
+ * <p class="note">Note that on Android T, a request interval of {@link #PASSIVE_INTERVAL}
+ * will first trigger one GNSS measurement. Another GNSS measurement will be triggered after
+ * {@link #PASSIVE_INTERVAL} milliseconds and so on.
*/
@NonNull public Builder setIntervalMillis(@IntRange(from = 0) int value) {
mIntervalMillis = Preconditions.checkArgumentInRange(value, 0, Integer.MAX_VALUE,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f3614d327561..5e0d93548dc3 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -300,5 +300,6 @@ android_app {
dxflags: ["--multi-dex"],
required: [
"privapp_whitelist_com.android.systemui",
+ "wmshell.protolog.json.gz",
],
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
index 3175dcfa092b..4d94bab6c26c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
@@ -17,8 +17,6 @@
package com.android.systemui.user.ui.compose
-import android.graphics.Bitmap
-import android.graphics.Canvas
import android.graphics.drawable.Drawable
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.Image
@@ -50,10 +48,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -62,6 +58,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
import com.android.systemui.common.ui.compose.load
import com.android.systemui.compose.SysUiOutlinedButton
import com.android.systemui.compose.SysUiTextButton
@@ -356,10 +353,11 @@ private fun MenuItem(
remember(viewModel.iconResourceId) {
val drawable =
checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
+ val size = with(density) { 20.dp.toPx() }.toInt()
drawable
.toBitmap(
- size = with(density) { 20.dp.toPx() }.toInt(),
- tintColor = Color.White,
+ width = size,
+ height = size,
)
.asImageBitmap()
}
@@ -392,32 +390,3 @@ private fun MenuItem(
),
)
}
-
-/**
- * Converts the [Drawable] to a [Bitmap].
- *
- * Note that this is a relatively memory-heavy operation as it allocates a whole bitmap and draws
- * the `Drawable` onto it. Use sparingly and with care.
- */
-private fun Drawable.toBitmap(
- size: Int? = null,
- tintColor: Color? = null,
-): Bitmap {
- val bitmap =
- if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
- Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- } else {
- Bitmap.createBitmap(
- size ?: intrinsicWidth,
- size ?: intrinsicHeight,
- Bitmap.Config.ARGB_8888
- )
- }
- val canvas = Canvas(bitmap)
- setBounds(0, 0, canvas.width, canvas.height)
- if (tintColor != null) {
- setTint(tintColor.toArgb())
- }
- draw(canvas)
- return bitmap
-}
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
new file mode 100644
index 000000000000..29832a081612
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto" >
+
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/mobile_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <FrameLayout
+ android:id="@+id/inout_container"
+ android:layout_height="17dp"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical">
+ <ImageView
+ android:id="@+id/mobile_in"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_down"
+ android:visibility="gone"
+ android:paddingEnd="2dp"
+ />
+ <ImageView
+ android:id="@+id/mobile_out"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:src="@drawable/ic_activity_up"
+ android:paddingEnd="2dp"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_type"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="2.5dp"
+ android:paddingEnd="1dp"
+ android:visibility="gone" />
+ <Space
+ android:id="@+id/mobile_roaming_space"
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/roaming_icon_start_padding"
+ android:visibility="gone"
+ />
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+ <com.android.systemui.statusbar.AnimatedImageView
+ android:id="@+id/mobile_signal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ systemui:hasOverlappingRendering="false"
+ />
+ <ImageView
+ android:id="@+id/mobile_roaming"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </FrameLayout>
+ <ImageView
+ android:id="@+id/mobile_roaming_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/stat_sys_roaming_large"
+ android:contentDescription="@string/data_connection_roaming"
+ android:visibility="gone" />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
new file mode 100644
index 000000000000..1b38fd2c4283
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_new.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/mobile_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical" >
+
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
+
+</com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView>
+
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 10d49b38ae75..d6c63eb4feac 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -18,80 +18,12 @@
-->
<com.android.systemui.statusbar.StatusBarMobileView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/mobile_combo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical" >
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/mobile_group"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:orientation="horizontal" >
+ <include layout="@layout/status_bar_mobile_signal_group_inner" />
- <FrameLayout
- android:id="@+id/inout_container"
- android:layout_height="17dp"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical">
- <ImageView
- android:id="@+id/mobile_in"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_down"
- android:visibility="gone"
- android:paddingEnd="2dp"
- />
- <ImageView
- android:id="@+id/mobile_out"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_activity_up"
- android:paddingEnd="2dp"
- android:visibility="gone"
- />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_type"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingStart="2.5dp"
- android:paddingEnd="1dp"
- android:visibility="gone" />
- <Space
- android:id="@+id/mobile_roaming_space"
- android:layout_height="match_parent"
- android:layout_width="@dimen/roaming_icon_start_padding"
- android:visibility="gone"
- />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical">
- <com.android.systemui.statusbar.AnimatedImageView
- android:id="@+id/mobile_signal"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- systemui:hasOverlappingRendering="false"
- />
- <ImageView
- android:id="@+id/mobile_roaming"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </FrameLayout>
- <ImageView
- android:id="@+id/mobile_roaming_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/stat_sys_roaming_large"
- android:contentDescription="@string/data_connection_roaming"
- android:visibility="gone" />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
</com.android.systemui.statusbar.StatusBarMobileView>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 85278dd4b883..f2742b7889b1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -43,27 +43,8 @@ public class QuickStepContract {
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
- // See IPip.aidl
- public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
- // See ISplitScreen.aidl
- public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
- // See IFloatingTasks.aidl
- public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
- // See IOneHanded.aidl
- public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed";
- // See IShellTransitions.aidl
- public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS =
- "extra_shell_shell_transitions";
- // See IStartingWindow.aidl
- public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
- "extra_shell_starting_window";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
- // See IRecentTasks.aidl
- public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
- public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
- // See IDesktopMode.aidl
- public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 1e5c53de4446..2cc5ccdc3fa1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,7 +24,6 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -107,8 +106,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 5b223242670c..987164557a7a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,9 +330,6 @@ public class KeyguardPatternViewController
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
- break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150e6c39..c46e33d9fd53 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,7 +22,6 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
-import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -124,8 +123,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
- case PROMPT_REASON_TRUSTAGENT_EXPIRED:
- return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 9d0a8acf02b4..ac00e9453c97 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,12 +61,6 @@ public interface KeyguardSecurityView {
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
- * Some auth is required because the trustagent expired either from timeout or manually by
- * the user
- */
- int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
-
- /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 80b9c4e13c5c..50012a589b5a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.keyguard.logging
import com.android.systemui.log.LogBuffer
@@ -14,6 +30,11 @@ import javax.inject.Inject
private const val TAG = "KeyguardLog"
+/**
+ * Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding
+ * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
+ * an overkill.
+ */
class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index edd6188f0814..5816f6b0062d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BA
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -804,9 +803,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
- } else if (trustAgentsEnabled
- && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
- return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 4c4b588888d1..45b668e609ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -157,7 +157,11 @@ constructor(
}
}
dozeHost.addCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial isDozing: false")
+ trySendWithFailureLogging(
+ statusBarStateController.isDozing,
+ TAG,
+ "initial isDozing",
+ )
awaitClose { dozeHost.removeCallback(callback) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 899e57d7d0ae..66be00d8de66 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,15 +25,6 @@ import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -110,16 +101,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.wm.shell.back.BackAnimation;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.transition.ShellTransitions;
+import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -151,10 +133,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final Optional<Pip> mPipOptional;
+ private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- private final Optional<SplitScreen> mSplitScreenOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -164,14 +144,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
- private final Optional<OneHanded> mOneHandedOptional;
private final CommandQueue mCommandQueue;
- private final ShellTransitions mShellTransitions;
- private final Optional<StartingSurface> mStartingSurface;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
- private final Optional<RecentTasks> mRecentTasks;
- private final Optional<BackAnimation> mBackAnimation;
- private final Optional<DesktopMode> mDesktopModeOptional;
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -456,36 +430,10 @@ public class OverviewProxyService extends CurrentUserTracker implements
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
-
- mPipOptional.ifPresent((pip) -> params.putBinder(
- KEY_EXTRA_SHELL_PIP,
- pip.createExternalInterface().asBinder()));
- mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(
- KEY_EXTRA_SHELL_SPLIT_SCREEN,
- splitscreen.createExternalInterface().asBinder()));
- mFloatingTasksOptional.ifPresent(floatingTasks -> params.putBinder(
- KEY_EXTRA_SHELL_FLOATING_TASKS,
- floatingTasks.createExternalInterface().asBinder()));
- mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(
- KEY_EXTRA_SHELL_ONE_HANDED,
- onehanded.createExternalInterface().asBinder()));
- params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- mShellTransitions.createExternalInterface().asBinder());
- mStartingSurface.ifPresent((startingwindow) -> params.putBinder(
- KEY_EXTRA_SHELL_STARTING_WINDOW,
- startingwindow.createExternalInterface().asBinder()));
- params.putBinder(
- KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+ params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
- mRecentTasks.ifPresent(recentTasks -> params.putBinder(
- KEY_EXTRA_RECENT_TASKS,
- recentTasks.createExternalInterface().asBinder()));
- mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
- KEY_EXTRA_SHELL_BACK_ANIMATION,
- backAnimation.createExternalInterface().asBinder()));
- mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
- KEY_EXTRA_SHELL_DESKTOP_MODE,
- desktopMode.createExternalInterface().asBinder())));
+ // Add all the interfaces exposed by the shell
+ mShellInterface.createExternalInterfaces(params);
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -559,21 +507,14 @@ public class OverviewProxyService extends CurrentUserTracker implements
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyService(Context context, CommandQueue commandQueue,
+ public OverviewProxyService(Context context,
+ CommandQueue commandQueue,
+ ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- Optional<Pip> pipOptional,
- Optional<SplitScreen> splitScreenOptional,
- Optional<FloatingTasks> floatingTasksOptional,
- Optional<OneHanded> oneHandedOptional,
- Optional<RecentTasks> recentTasks,
- Optional<BackAnimation> backAnimation,
- Optional<StartingSurface> startingSurface,
- Optional<DesktopMode> desktopModeOptional,
BroadcastDispatcher broadcastDispatcher,
- ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
@@ -587,7 +528,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
mContext = context;
- mPipOptional = pipOptional;
+ mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
@@ -602,11 +543,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
.supportsRoundedCornersOnWindows(mContext.getResources());
mSysUiState = sysUiState;
mSysUiState.addCallback(this::notifySystemUiStateFlags);
- mOneHandedOptional = oneHandedOptional;
- mShellTransitions = shellTransitions;
- mRecentTasks = recentTasks;
- mBackAnimation = backAnimation;
- mDesktopModeOptional = desktopModeOptional;
mUiEventLogger = uiEventLogger;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -636,9 +572,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
});
mCommandQueue = commandQueue;
- mSplitScreenOptional = splitScreenOptional;
- mFloatingTasksOptional = floatingTasksOptional;
-
// Listen for user setup
startTracking();
@@ -647,7 +580,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
- mStartingSurface = startingSurface;
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
// Listen for assistant changes
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index 4d53064d047d..ce730baeed0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -20,14 +20,16 @@ import android.util.AttributeSet
import android.widget.FrameLayout
/**
- * A temporary base class that's shared between our old status bar wifi view implementation
- * ([StatusBarWifiView]) and our new status bar wifi view implementation
- * ([ModernStatusBarWifiView]).
+ * A temporary base class that's shared between our old status bar connectivity view implementations
+ * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
+ * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
*
* Once our refactor is over, we should be able to delete this go-between class and the old view
* class.
*/
-abstract class BaseStatusBarWifiView @JvmOverloads constructor(
+abstract class BaseStatusBarFrameLayout
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 48c6e273bbb4..fdad101ae0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -29,7 +29,6 @@ import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -43,7 +42,10 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
import java.util.ArrayList;
-public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
+/**
+ * View group for the mobile icon in the status bar
+ */
+public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
StatusIconDisplayable {
private static final String TAG = "StatusBarMobileView";
@@ -101,11 +103,6 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
super(context, attrs, defStyleAttr);
}
- public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
@Override
public void getDrawingRect(Rect outRect) {
super.getDrawingRect(outRect);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index f3e74d92fc8a..decc70d175b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -40,7 +40,7 @@ import java.util.ArrayList;
/**
* Start small: StatusBarWifiView will be able to layout from a WifiIconState
*/
-public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkReceiver {
+public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
private static final String TAG = "StatusBarWifiView";
/// Used to show etc dots
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d6d021ff2819..ece7ee0ec98a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
import android.annotation.Nullable;
@@ -38,7 +39,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.BaseStatusBarWifiView;
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,6 +49,10 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorI
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.util.Assert;
@@ -84,6 +89,12 @@ public interface StatusBarIconController {
void setMobileIcons(String slot, List<MobileIconState> states);
/**
+ * This method completely replaces {@link #setMobileIcons} with the information from the new
+ * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
+ * to worry about funneling MobileIconState objects through anymore.
+ */
+ void setNewMobileIconSubIds(List<Integer> subIds);
+ /**
* Display the no calling & SMS icons.
*/
void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
@@ -141,12 +152,14 @@ public interface StatusBarIconController {
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -207,6 +220,7 @@ public interface StatusBarIconController {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
@@ -214,10 +228,12 @@ public interface StatusBarIconController {
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
+ MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
+ mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
}
@@ -227,6 +243,7 @@ public interface StatusBarIconController {
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -244,11 +261,14 @@ public interface StatusBarIconController {
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
mobileContextProvider);
}
@@ -284,14 +304,18 @@ public interface StatusBarIconController {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
mWifiViewModel = wifiViewModel;
+ mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -301,6 +325,7 @@ public interface StatusBarIconController {
location,
mStatusBarPipelineFlags,
mWifiViewModel,
+ mMobileUiAdapter,
mMobileContextProvider);
}
}
@@ -315,6 +340,8 @@ public interface StatusBarIconController {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final MobileIconsViewModel mMobileIconsViewModel;
+
protected final Context mContext;
protected final int mIconSize;
// Whether or not these icons show up in dumpsys
@@ -333,7 +360,9 @@ public interface StatusBarIconController {
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
- MobileContextProvider mobileContextProvider) {
+ MobileUiAdapter mobileUiAdapter,
+ MobileContextProvider mobileContextProvider
+ ) {
mGroup = group;
mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
@@ -342,6 +371,14 @@ public interface StatusBarIconController {
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+
+ if (statusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ // This starts the flow for the new pipeline, and will notify us of changes
+ mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+ MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+ } else {
+ mMobileIconsViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -394,6 +431,9 @@ public interface StatusBarIconController {
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
+
+ case TYPE_MOBILE_NEW:
+ return addNewMobileIcon(index, slot, holder.getTag());
}
return null;
@@ -410,7 +450,7 @@ public interface StatusBarIconController {
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarWifiView view;
+ final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
view = onCreateModernStatusBarWifiView(slot);
// When [ModernStatusBarWifiView] is created, it will automatically apply the
@@ -429,17 +469,47 @@ public interface StatusBarIconController {
}
@VisibleForTesting
- protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
+ protected StatusIconDisplayable addMobileIcon(
+ int index,
+ String slot,
+ MobileIconState state
+ ) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "pipeline is enabled is not supported");
+ }
+
// Use the `subId` field as a key to query for the correct context
- StatusBarMobileView view = onCreateStatusBarMobileView(state.subId, slot);
- view.applyMobileState(state);
- mGroup.addView(view, index, onCreateLayoutParams());
+ StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
+ mobileView.applyMobileState(state);
+ mGroup.addView(mobileView, index, onCreateLayoutParams());
if (mIsInDemoMode) {
Context mobileContext = mMobileContextProvider
.getMobileContextForSub(state.subId, mContext);
mDemoStatusIcons.addMobileView(state, mobileContext);
}
+ return mobileView;
+ }
+
+ protected StatusIconDisplayable addNewMobileIcon(
+ int index,
+ String slot,
+ int subId
+ ) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ throw new IllegalStateException("Attempting to add a mobile icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
+ mGroup.addView(view, index, onCreateLayoutParams());
+
+ if (mIsInDemoMode) {
+ // TODO (b/249790009): demo mode should be handled at the data layer in the
+ // new pipeline
+ }
+
return view;
}
@@ -464,6 +534,15 @@ public interface StatusBarIconController {
return view;
}
+ private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
+ String slot, int subId) {
+ return ModernStatusBarMobileView
+ .constructAndBind(
+ mContext,
+ slot,
+ mMobileIconsViewModel.viewModelForSub(subId));
+ }
+
protected LinearLayout.LayoutParams onCreateLayoutParams() {
return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
}
@@ -519,6 +598,10 @@ public interface StatusBarIconController {
return;
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
+ return;
+ case TYPE_MOBILE_NEW:
+ // Nothing, the icon updates itself now
+ return;
default:
break;
}
@@ -542,9 +625,13 @@ public interface StatusBarIconController {
}
public void onSetMobileIcon(int viewIndex, MobileIconState state) {
- StatusBarMobileView view = (StatusBarMobileView) mGroup.getChildAt(viewIndex);
- if (view != null) {
- view.applyMobileState(state);
+ View view = mGroup.getChildAt(viewIndex);
+ if (view instanceof StatusBarMobileView) {
+ ((StatusBarMobileView) view).applyMobileState(state);
+ } else {
+ // ModernStatusBarMobileView automatically updates via the ViewModel
+ throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
+ + "the new pipeline");
}
if (mIsInDemoMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 7c31366ba4f0..e106b9e327ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
@@ -66,8 +67,8 @@ public class StatusBarIconControllerImpl implements Tunable,
private final StatusBarIconList mStatusBarIconList;
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconHideList = new ArraySet<>();
-
- private Context mContext;
+ private final StatusBarPipelineFlags mStatusBarPipelineFlags;
+ private final Context mContext;
/** */
@Inject
@@ -78,9 +79,12 @@ public class StatusBarIconControllerImpl implements Tunable,
ConfigurationController configurationController,
TunerService tunerService,
DumpManager dumpManager,
- StatusBarIconList statusBarIconList) {
+ StatusBarIconList statusBarIconList,
+ StatusBarPipelineFlags statusBarPipelineFlags
+ ) {
mStatusBarIconList = statusBarIconList;
mContext = context;
+ mStatusBarPipelineFlags = statusBarPipelineFlags;
configurationController.addCallback(this);
commandQueue.addCallback(this);
@@ -220,6 +224,11 @@ public class StatusBarIconControllerImpl implements Tunable,
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
+ if (mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ + "pipeline frontend is enabled");
+ return;
+ }
Slot mobileSlot = mStatusBarIconList.getSlot(slot);
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
@@ -227,7 +236,6 @@ public class StatusBarIconControllerImpl implements Tunable,
Collections.reverse(iconStates);
for (MobileIconState state : iconStates) {
-
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
@@ -239,6 +247,28 @@ public class StatusBarIconControllerImpl implements Tunable,
}
}
+ @Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ if (!mStatusBarPipelineFlags.isNewPipelineFrontendEnabled()) {
+ Log.d(TAG, "ignoring new pipeline callback, "
+ + "since the frontend is disabled");
+ return;
+ }
+ Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+
+ Collections.reverse(subIds);
+
+ for (Integer subId : subIds) {
+ StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
+ if (holder == null) {
+ holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
+ setIcon("mobile", holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+ }
+
/**
* Accept a list of CallIndicatorIconStates, and show the call strength icons.
* @param slot statusbar slot for the call strength icons
@@ -384,8 +414,6 @@ public class StatusBarIconControllerImpl implements Tunable,
}
}
-
-
private void handleSet(String slotName, StatusBarIconHolder holder) {
int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd31a76..68a203e30f98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -25,6 +26,10 @@ import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
@@ -33,15 +38,35 @@ public class StatusBarIconHolder {
public static final int TYPE_ICON = 0;
public static final int TYPE_WIFI = 1;
public static final int TYPE_MOBILE = 2;
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place
+ * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
+ * to inform the old view system about changes to the data set (the list of mobile icons). The
+ * design of the new pipeline should allow for removal of this icon holder type, and obsolete
+ * the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_MOBILE_NEW = 3;
+
+ @IntDef({
+ TYPE_ICON,
+ TYPE_WIFI,
+ TYPE_MOBILE,
+ TYPE_MOBILE_NEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface IconType {}
private StatusBarIcon mIcon;
private WifiIconState mWifiState;
private MobileIconState mMobileState;
- private int mType = TYPE_ICON;
+ private @IconType int mType = TYPE_ICON;
private int mTag = 0;
private StatusBarIconHolder() {
-
}
public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
@@ -80,6 +105,18 @@ public class StatusBarIconHolder {
}
/**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_MOBILE_NEW;
+ holder.mTag = subId;
+
+ return holder;
+ }
+
+ /**
* Creates a new StatusBarIconHolder from a CallIndicatorIconState.
*/
public static StatusBarIconHolder fromCallIndicatorState(
@@ -95,7 +132,7 @@ public class StatusBarIconHolder {
return holder;
}
- public int getType() {
+ public @IconType int getType() {
return mType;
}
@@ -134,8 +171,12 @@ public class StatusBarIconHolder {
return mWifiState.visible;
case TYPE_MOBILE:
return mMobileState.visible;
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ return true;
- default: return true;
+ default:
+ return true;
}
}
@@ -156,6 +197,10 @@ public class StatusBarIconHolder {
case TYPE_MOBILE:
mMobileState.visible = visible;
break;
+
+ case TYPE_MOBILE_NEW:
+ //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 9a7c3fae780c..06d554232565 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,10 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -30,4 +34,12 @@ abstract class StatusBarPipelineModule {
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+
+ @Binds
+ abstract fun mobileSubscriptionRepository(
+ impl: MobileSubscriptionRepositoryImpl
+ ): MobileSubscriptionRepository
+
+ @Binds
+ abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
new file mode 100644
index 000000000000..46ccf32cc7f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.pipeline.mobile.data.model
+
+import android.annotation.IntRange
+import android.telephony.Annotation.DataActivityType
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+
+/**
+ * Data class containing all of the relevant information for a particular line of service, known as
+ * a Subscription in the telephony world. These models are the result of a single telephony listener
+ * which has many callbacks which each modify some particular field on this object.
+ *
+ * The design goal here is to de-normalize fields from the system into our model fields below. So
+ * any new field that needs to be tracked should be copied into this data class rather than
+ * threading complex system objects through the pipeline.
+ */
+data class MobileSubscriptionModel(
+ /** From [ServiceStateListener.onServiceStateChanged] */
+ val isEmergencyOnly: Boolean = false,
+
+ /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+ val isGsm: Boolean = false,
+ @IntRange(from = 0, to = 4)
+ val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+ @IntRange(from = 0, to = 4)
+ val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+
+ /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
+ val dataConnectionState: Int? = null,
+
+ /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
+ @DataActivityType val dataActivityDirection: Int? = null,
+
+ /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+ val carrierNetworkChangeActive: Boolean? = null,
+
+ /** From [DisplayInfoListener.onDisplayInfoChanged] */
+ val displayInfo: TelephonyDisplayInfo? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
new file mode 100644
index 000000000000..36de2a254160
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+interface MobileSubscriptionRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Get or create an observable for the given subscription ID */
+ fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileSubscriptionRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+) : MobileSubscriptionRepository {
+ private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = mutableMapOf()
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+ * info object, but for now we keep track of the infos themselves.
+ */
+ override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ trySend(Unit)
+ }
+ }
+
+ subscriptionManager.addOnSubscriptionsChangedListener(
+ bgDispatcher.asExecutor(),
+ callback,
+ )
+
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .mapLatest { fetchSubscriptionsList() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+ /** StateFlow that keeps track of the current active mobile data subscription */
+ override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ trySend(subId)
+ }
+ }
+
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ /**
+ * Each mobile subscription needs its own flow, which comes from registering listeners on the
+ * system. Use this method to create those flows and cache them for reuse
+ */
+ override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
+ return subIdFlowCache[subId]
+ ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+ }
+
+ @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
+
+ private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+ var state = MobileSubscriptionModel()
+ conflatedCallbackFlow {
+ val phony = telephonyManager.createForSubscriptionId(subId)
+ // TODO (b/240569788): log all of these into the connectivity logger
+ val callback =
+ object :
+ TelephonyCallback(),
+ ServiceStateListener,
+ SignalStrengthsListener,
+ DataConnectionStateListener,
+ DataActivityListener,
+ CarrierNetworkListener,
+ DisplayInfoListener {
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+ trySend(state)
+ }
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ val cdmaLevel =
+ signalStrength
+ .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+ .let { strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ state =
+ state.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ trySend(state)
+ }
+ override fun onDataConnectionStateChanged(
+ dataState: Int,
+ networkType: Int
+ ) {
+ state = state.copy(dataConnectionState = dataState)
+ trySend(state)
+ }
+ override fun onDataActivity(direction: Int) {
+ state = state.copy(dataActivityDirection = direction)
+ trySend(state)
+ }
+ override fun onCarrierNetworkChange(active: Boolean) {
+ state = state.copy(carrierNetworkChangeActive = active)
+ trySend(state)
+ }
+ override fun onDisplayInfoChanged(
+ telephonyDisplayInfo: TelephonyDisplayInfo
+ ) {
+ state = state.copy(displayInfo = telephonyDisplayInfo)
+ trySend(state)
+ }
+ }
+ phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose {
+ phony.unregisterTelephonyCallback(callback)
+ // Release the cached flow
+ subIdFlowCache.remove(subId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
new file mode 100644
index 000000000000..77de849691db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.pipeline.mobile.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository to observe the state of [DeviceProvisionedController.isUserSetup]. This information
+ * can change some policy related to display
+ */
+interface UserSetupRepository {
+ /** Observable tracking [DeviceProvisionedController.isUserSetup] */
+ val isUserSetupFlow: Flow<Boolean>
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class UserSetupRepositoryImpl
+@Inject
+constructor(
+ private val deviceProvisionedController: DeviceProvisionedController,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application scope: CoroutineScope,
+) : UserSetupRepository {
+ /** State flow that tracks [DeviceProvisionedController.isUserSetup] */
+ override val isUserSetupFlow: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onUserSetupChanged() {
+ trySend(Unit)
+ }
+ }
+
+ deviceProvisionedController.addCallback(callback)
+
+ awaitClose { deviceProvisionedController.removeCallback(callback) }
+ }
+ .onStart { emit(Unit) }
+ .mapLatest { fetchUserSetupState() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private suspend fun fetchUserSetupState(): Boolean =
+ withContext(bgDispatcher) { deviceProvisionedController.isCurrentUserSetup }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
new file mode 100644
index 000000000000..40fe0f3e8fe0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.CarrierConfigTracker
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+interface MobileIconInteractor {
+ /** Identifier for RAT type indicator */
+ val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** True if this line of service is emergency-only */
+ val isEmergencyOnly: Flow<Boolean>
+ /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
+ val level: Flow<Int>
+ /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
+ val numberOfLevels: Flow<Int>
+ /** True when we want to draw an icon that makes room for the exclamation mark */
+ val cutOut: Flow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+class MobileIconInteractorImpl(
+ mobileStatusInfo: Flow<MobileSubscriptionModel>,
+) : MobileIconInteractor {
+ override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
+
+ override val level: Flow<Int> =
+ mobileStatusInfo.map { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
+ }
+ }
+
+ /**
+ * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+ * once it's wired up inside of [CarrierConfigTracker]
+ */
+ override val numberOfLevels: Flow<Int> = flowOf(4)
+
+ /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
+ // TODO: find a better name for this?
+ override val cutOut: Flow<Boolean> = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
new file mode 100644
index 000000000000..8e67e19f3e35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.pipeline.mobile.domain.interactor
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Business layer logic for mobile subscription icons
+ *
+ * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
+ * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ */
+@SysUISingleton
+class MobileIconsInteractor
+@Inject
+constructor(
+ private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val carrierConfigTracker: CarrierConfigTracker,
+ userSetupRepo: UserSetupRepository,
+) {
+ private val activeMobileDataSubscriptionId =
+ mobileSubscriptionRepo.activeMobileDataSubscriptionId
+
+ private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ mobileSubscriptionRepo.subscriptionsFlow
+
+ /**
+ * Generally, SystemUI wants to show iconography for each subscription that is listed by
+ * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+ * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+ *
+ * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+ * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+ *
+ * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+ * subscriptions based on
+ * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+ * and by checking which subscription is opportunistic, or which one is active.
+ */
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
+ ->
+ // Based on the old logic,
+ if (unfilteredSubs.size != 2) {
+ return@combine unfilteredSubs
+ }
+
+ val info1 = unfilteredSubs[0]
+ val info2 = unfilteredSubs[1]
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return@combine unfilteredSubs
+ }
+
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+ } else {
+ return@combine if (info1.subscriptionId == activeId) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
+ }
+ }
+
+ val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+ /** Vends out new [MobileIconInteractor] for a particular subId */
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
+
+ /**
+ * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+ */
+ private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
+ mobileSubscriptionRepo.getFlowForSubId(subId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
new file mode 100644
index 000000000000..380017cd3418
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -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.systemui.statusbar.pipeline.mobile.ui
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This class is intended to provide a context to collect on the
+ * [MobileIconsInteractor.filteredSubscriptions] data source and supply a state flow that can
+ * control [StatusBarIconController] to keep the old UI in sync with the new data source.
+ *
+ * It also provides a mechanism to create a top-level view model for each IconManager to know about
+ * the list of available mobile lines of service for which we want to show icons.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileUiAdapter
+@Inject
+constructor(
+ interactor: MobileIconsInteractor,
+ private val iconController: StatusBarIconController,
+ private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ @Application scope: CoroutineScope,
+) {
+ private val mobileSubIds: Flow<List<Int>> =
+ interactor.filteredSubscriptions.mapLatest { infos ->
+ infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+ }
+
+ /**
+ * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
+ * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
+ * house the mobile infos.
+ *
+ * NOTE: this should go away as the view presenter learns more about this data pipeline
+ */
+ private val mobileSubIdsState: StateFlow<List<Int>> =
+ mobileSubIds
+ .onEach {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ /**
+ * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
+ * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
+ * the old view system.
+ */
+ fun createMobileIconsViewModel(): MobileIconsViewModel =
+ iconsViewModelFactory.create(mobileSubIdsState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
new file mode 100644
index 000000000000..1405b050234b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.pipeline.mobile.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.R
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+object MobileIconBinder {
+ /** Binds the view to the view-model, continuing to update the former based on the latter */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: MobileIconViewModel,
+ ) {
+ val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
+ val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+
+ view.isVisible = true
+ iconView.isVisible = true
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Set the icon for the triangle
+ launch {
+ viewModel.iconId.distinctUntilChanged().collect { iconId ->
+ mobileDrawable.level = iconId
+ }
+ }
+
+ // Set the tint
+ launch {
+ viewModel.tint.collect { tint ->
+ iconView.imageTintList = ColorStateList.valueOf(tint)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
new file mode 100644
index 000000000000..e7d5ee264efe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconsBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.pipeline.mobile.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+object MobileIconsBinder {
+ /**
+ * Start this ViewModel collecting on the list of mobile subscriptions in the scope of [view]
+ * which is passed in and managed by [IconManager]. Once the subscription list flow starts
+ * collecting, [MobileUiAdapter] will send updates to the icon manager.
+ */
+ @JvmStatic
+ fun bind(view: View, viewModel: MobileIconsViewModel) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.subscriptionIdsFlow.collect {
+ // TODO(b/249790733): This is an empty collect, because [MobileUiAdapter]
+ // sets up a side-effect in this flow to trigger the methods on
+ // [StatusBarIconController] which allows for this pipeline to be a data
+ // source for the mobile icons.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
new file mode 100644
index 000000000000..ec4fa9ca8128
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.pipeline.mobile.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import java.util.ArrayList
+
+class ModernStatusBarMobileView(
+ context: Context,
+ attrs: AttributeSet?,
+) : BaseStatusBarFrameLayout(context, attrs) {
+
+ private lateinit var slot: String
+ override fun getSlot() = slot
+
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ // TODO
+ }
+
+ override fun setStaticDrawableColor(color: Int) {
+ // TODO
+ }
+
+ override fun setDecorColor(color: Int) {
+ // TODO
+ }
+
+ override fun setVisibleState(state: Int, animate: Boolean) {
+ // TODO
+ }
+
+ override fun getVisibleState(): Int {
+ return STATE_ICON
+ }
+
+ override fun isIconVisible(): Boolean {
+ return true
+ }
+
+ companion object {
+
+ /**
+ * Inflates a new instance of [ModernStatusBarMobileView], binds it to [viewModel], and
+ * returns it.
+ */
+ @JvmStatic
+ fun constructAndBind(
+ context: Context,
+ slot: String,
+ viewModel: MobileIconViewModel,
+ ): ModernStatusBarMobileView {
+ return (LayoutInflater.from(context)
+ .inflate(R.layout.status_bar_mobile_signal_group_new, null)
+ as ModernStatusBarMobileView)
+ .also {
+ it.slot = slot
+ MobileIconBinder.bind(it, viewModel)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
new file mode 100644
index 000000000000..cfabeba8432c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
+ * a single line of service via [MobileIconInteractor] and update the UI based on that
+ * subscription's information.
+ *
+ * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
+ * [MobileIconsInteractor.filteredSubscriptions]
+ *
+ * TODO: figure out where carrier merged and VCN models go (probably here?)
+ */
+class MobileIconViewModel
+constructor(
+ val subscriptionId: Int,
+ iconInteractor: MobileIconInteractor,
+ logger: ConnectivityPipelineLogger,
+) {
+ /** An int consumable by [SignalDrawable] for display */
+ var iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ level,
+ numberOfLevels,
+ cutOut ->
+ SignalDrawable.getState(level, numberOfLevels, cutOut)
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "iconId($subscriptionId)")
+
+ var tint: Flow<Int> = flowOf(Color.CYAN)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
new file mode 100644
index 000000000000..24c1db995d50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(InternalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import javax.inject.Inject
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * View model for describing the system's current mobile cellular connections. The result is a list
+ * of [MobileIconViewModel]s which describe the individual icons and can be bound to
+ * [ModernStatusBarMobileView]
+ */
+class MobileIconsViewModel
+@Inject
+constructor(
+ val subscriptionIdsFlow: Flow<List<Int>>,
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+) {
+ /** TODO: do we need to cache these? */
+ fun viewModelForSub(subId: Int): MobileIconViewModel =
+ MobileIconViewModel(
+ subId,
+ interactor.createMobileConnectionInteractorForSubId(subId),
+ logger
+ )
+
+ class Factory
+ @Inject
+ constructor(
+ private val interactor: MobileIconsInteractor,
+ private val logger: ConnectivityPipelineLogger,
+ ) {
+ fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+ return MobileIconsViewModel(
+ subscriptionIdsFlow,
+ interactor,
+ logger,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 6c616ac7c3b8..0cd9bd7d97b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,7 +22,7 @@ import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarWifiView
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -37,7 +37,7 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
class ModernStatusBarWifiView(
context: Context,
attrs: AttributeSet?
-) : BaseStatusBarWifiView(context, attrs) {
+) : BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
private lateinit var binding: WifiViewBinder.Binding
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 5f7d74542fff..a925e384d3be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -67,6 +67,8 @@ public class CarrierConfigTracker
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
private boolean mDefaultShowOperatorNameConfigLoaded;
private boolean mDefaultShowOperatorNameConfig;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded;
+ private boolean mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
@Inject
public CarrierConfigTracker(
@@ -207,6 +209,22 @@ public class CarrierConfigTracker
}
/**
+ * Returns KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN value for
+ * the default carrier config.
+ */
+ public boolean getAlwaysShowPrimarySignalBarInOpportunisticNetworkDefault() {
+ if (!mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded) {
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig = CarrierConfigManager
+ .getDefaultConfig().getBoolean(CarrierConfigManager
+ .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN
+ );
+ mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfigLoaded = true;
+ }
+
+ return mDefaultAlwaysShowPrimarySignalBarInOpportunisticNetworkConfig;
+ }
+
+ /**
* Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
* default value if no override exists
*
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
new file mode 100644
index 000000000000..3c418ed49adc
--- /dev/null
+++ b/packages/SystemUI/tests/Android.bp
@@ -0,0 +1,50 @@
+//
+// 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 {
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_test {
+ name: "SystemUITests",
+
+ dxflags: ["--multi-dex"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ static_libs: ["SystemUI-tests"],
+ compile_multilib: "both",
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
+ libs: [
+ "android.test.runner",
+ "telephony-common",
+ "android.test.base",
+ ],
+ aaptflags: [
+ "--extra-packages com.android.systemui",
+ ],
+
+ // sign this with platform cert, so this test is allowed to inject key events into
+ // UI it doesn't own. This is necessary to allow screenshots to be taken
+ certificate: "platform",
+
+ additional_manifests: ["AndroidManifest.xml"],
+ manifest: "AndroidManifest-base.xml",
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
deleted file mode 100644
index ff5165d4e7cf..000000000000
--- a/packages/SystemUI/tests/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2011 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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := SystemUITests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- SystemUI-tests
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
- libdexmakerjvmtiagent \
- libmultiplejvmtiagentsinterferenceagent \
- libstaticjvmtiagent
-
-LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
- telephony-common \
- android.test.base \
-
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
-
-# sign this with platform cert, so this test is allowed to inject key events into
-# UI it doesn't own. This is necessary to allow screenshots to be taken
-LOCAL_CERTIFICATE := platform
-
-LOCAL_FULL_LIBS_MANIFEST_FILES := $(LOCAL_PATH)/AndroidManifest.xml
-LOCAL_MANIFEST_FILE := AndroidManifest-base.xml
-
-# Provide jack a list of classes to exclude from code coverage.
-# This is needed because the SystemUITests compile SystemUI source directly, rather than using
-# LOCAL_INSTRUMENTATION_FOR := SystemUI.
-#
-# We want to exclude the test classes from code coverage measurements, but they share the same
-# package as the rest of SystemUI so they can't be easily filtered by package name.
-#
-# Generate a comma separated list of patterns based on the test source files under src/
-# SystemUI classes are in ../src/ so they won't be excluded.
-# Example:
-# Input files: src/com/android/systemui/Test.java src/com/android/systemui/AnotherTest.java
-# Generated exclude list: com.android.systemui.Test*,com.android.systemui.AnotherTest*
-
-# Filter all src files under src/ to just java files
-local_java_files := $(filter %.java,$(call all-java-files-under, src))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-local_comma := ,
-local_empty :=
-local_space := $(local_empty) $(local_empty)
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
- include $(BUILD_PACKAGE)
-endif
-
-# Reset variables
-local_java_files :=
-local_classes :=
-local_comma :=
-local_space :=
-jacoco_exclude :=
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
index d418836b5753..7f55d388c34f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java
@@ -49,6 +49,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@@ -148,8 +149,12 @@ public class GlobalActionsImeTest extends SysuiTestCase {
return false;
}
- private static void executeShellCommand(String cmd) {
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd);
+ private void executeShellCommand(String cmd) {
+ try {
+ runShellCommand(cmd);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index eea2e952c81f..7a1568098e4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.doze.DozeHost
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -33,7 +34,6 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -153,6 +153,21 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
+ var latest: Boolean? = null
+
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ job.cancel()
+
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ job.cancel()
+ }
+
+ @Test
fun dozeAmount() = runBlockingTest {
val values = mutableListOf<Float>()
val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 34399b80c9f7..9c56c2670c63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -80,6 +81,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -123,12 +125,14 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
WifiViewModel wifiViewModel,
+ MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
wifiViewModel,
+ mobileUiAdapter,
contextProvider,
darkIconDispatcher);
}
@@ -169,6 +173,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
mock(WifiViewModel.class),
+ mock(MobileUiAdapter.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
new file mode 100644
index 000000000000..0d1526883023
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+ private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+
+ private val _activeMobileDataSubscriptionId =
+ MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+ private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
+ override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
+ return subIdFlows[subId]
+ ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ }
+
+ fun setSubscriptions(subs: List<SubscriptionInfo>) {
+ _subscriptionsFlow.value = subs
+ }
+
+ fun setActiveMobileDataSubscriptionId(subId: Int) {
+ _activeMobileDataSubscriptionId.value = subId
+ }
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
+ val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
+ subscription.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
new file mode 100644
index 000000000000..6c495c5c705a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.pipeline.mobile.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Defaults to `true` */
+class FakeUserSetupRepository : UserSetupRepository {
+ private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+
+ fun setUserSetup(setup: Boolean) {
+ _isUserSetup.value = setup
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
new file mode 100644
index 000000000000..316b795ac949
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
@@ -0,0 +1,360 @@
+/*
+ * 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.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.CarrierNetworkListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DataConnectionStateListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyCallback.SignalStrengthsListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileSubscriptionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileSubscriptionRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ MobileSubscriptionRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_default() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(MobileSubscriptionModel())
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_emergencyOnly_toggles() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ val serviceState = ServiceState()
+ serviceState.isEmergencyOnly = true
+ callback.onServiceStateChanged(serviceState)
+ serviceState.isEmergencyOnly = false
+ callback.onServiceStateChanged(serviceState)
+
+ assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
+ val strength = signalStrength(1, 2, true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest?.isGsm).isEqualTo(true)
+ assertThat(latest?.primaryLevel).isEqualTo(1)
+ assertThat(latest?.cdmaLevel).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(100, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(100)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataActivity() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(3)
+
+ assertThat(latest?.dataActivityDirection).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_carrierNetworkChange() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_displayInfo() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<DisplayInfoListener>()
+ val ti = mock<TelephonyDisplayInfo>()
+ callback.onDisplayInfoChanged(ti)
+
+ assertThat(latest?.displayInfo).isEqualTo(ti)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ val state1 = underTest.getFlowForSubId(SUB_1_ID)
+ val state2 = underTest.getFlowForSubId(SUB_1_ID)
+
+ assertThat(state1).isEqualTo(state2)
+ }
+
+ @Test
+ fun testFlowForSubId_isRemovedAfterFinish() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+
+ var latest: MobileSubscriptionModel? = null
+
+ // Start collecting on some flow
+ val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+
+ // There should be once cached flow now
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+
+ // When the job is canceled, the cache should be cleared
+ job.cancel()
+
+ assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
+ getTelephonyCallbackForType()
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ /** Convenience constructor for SignalStrength */
+ private fun signalStrength(gsmLevel: Int, cdmaLevel: Int, isGsm: Boolean): SignalStrength {
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.isGsm).thenReturn(isGsm)
+ whenever(signalStrength.level).thenReturn(gsmLevel)
+ val cdmaStrength =
+ mock<CellSignalStrengthCdma>().also { whenever(it.level).thenReturn(cdmaLevel) }
+ whenever(signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java))
+ .thenReturn(listOf(cdmaStrength))
+
+ return signalStrength
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
new file mode 100644
index 000000000000..91c233a4177d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepositoryTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.pipeline.mobile.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class UserSetupRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: UserSetupRepository
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ UserSetupRepositoryImpl(
+ deviceProvisionedController,
+ IMMEDIATE,
+ scope,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testUserSetup_defaultFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testUserSetup_updatesOnChange() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+
+ val job = underTest.isUserSetupFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ val callback = getDeviceProvisionedListener()
+ callback.onUserSetupChanged()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ private fun getDeviceProvisionedListener(): DeviceProvisionedListener {
+ val captor = argumentCaptor<DeviceProvisionedListener>()
+ verify(deviceProvisionedController).addCallback(captor.capture())
+ return captor.value!!
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
new file mode 100644
index 000000000000..8ec68f36a837
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconInteractor : MobileIconInteractor {
+ private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+ override val iconGroup = _iconGroup
+
+ private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+ override val isEmergencyOnly = _isEmergencyOnly
+
+ private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val level = _level
+
+ private val _numberOfLevels = MutableStateFlow<Int>(4)
+ override val numberOfLevels = _numberOfLevels
+
+ private val _cutOut = MutableStateFlow<Boolean>(false)
+ override val cutOut = _cutOut
+
+ fun setIconGroup(group: SignalIcon.MobileIconGroup) {
+ _iconGroup.value = group
+ }
+
+ fun setIsEmergencyOnly(emergency: Boolean) {
+ _isEmergencyOnly.value = emergency
+ }
+
+ fun setLevel(level: Int) {
+ _level.value = level
+ }
+
+ fun setNumberOfLevels(num: Int) {
+ _numberOfLevels.value = num
+ }
+
+ fun setCutOut(cutOut: Boolean) {
+ _cutOut.value = cutOut
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
new file mode 100644
index 000000000000..2f07d9cb3831
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.pipeline.mobile.domain.interactor
+
+import android.telephony.CellSignalStrength
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class MobileIconInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconInteractor
+ private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
+ private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+
+ @Before
+ fun setUp() {
+ underTest = MobileIconInteractorImpl(sub1Flow)
+ }
+
+ @Test
+ fun gsm_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = true),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ job.cancel()
+ }
+
+ @Test
+ fun gsm_usesGsmLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = true,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_level_default_unknown() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(isGsm = false),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ job.cancel()
+ }
+
+ @Test
+ fun cdma_usesCdmaLevel() =
+ runBlocking(IMMEDIATE) {
+ mobileSubscriptionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ isGsm = false,
+ primaryLevel = GSM_LEVEL,
+ cdmaLevel = CDMA_LEVEL
+ ),
+ SUB_1_ID
+ )
+
+ var latest: Int? = null
+ val job = underTest.level.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val GSM_LEVEL = 1
+ private const val CDMA_LEVEL = 2
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
new file mode 100644
index 000000000000..89ad9cb9e51e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.pipeline.mobile.domain.interactor
+
+import android.telephony.SubscriptionInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconsInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconsInteractor
+ private val userSetupRepository = FakeUserSetupRepository()
+ private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+
+ @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ MobileIconsInteractor(
+ subscriptionsRepository,
+ carrierConfigTracker,
+ userSetupRepository,
+ )
+ }
+
+ @After fun tearDown() {}
+
+ @Test
+ fun filteredSubscriptions_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+ runBlocking(IMMEDIATE) {
+ subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ var latest: List<SubscriptionInfo>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(SUB_1))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val SUB_3_ID = 3
+ private val SUB_3_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_3_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+
+ private const val SUB_4_ID = 4
+ private val SUB_4_OPP =
+ mock<SubscriptionInfo>().also {
+ whenever(it.subscriptionId).thenReturn(SUB_4_ID)
+ whenever(it.isOpportunistic).thenReturn(true)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
new file mode 100644
index 000000000000..b374abbd5082
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileIconViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: MobileIconViewModel
+ private val interactor = FakeMobileIconInteractor()
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ interactor.apply {
+ setLevel(1)
+ setCutOut(false)
+ setIconGroup(TelephonyIcons.THREE_G)
+ setIsEmergencyOnly(false)
+ setNumberOfLevels(4)
+ }
+ underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+ }
+
+ @Test
+ fun iconId_correctLevel_notCutout() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val SUB_1_ID = 1
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 2be67edfc946..23c7a6139de8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -70,6 +70,10 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
}
@Override
+ public void setNewMobileIconSubIds(List<Integer> subIds) {
+ }
+
+ @Override
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 487703bb73b2..75724bffabf8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -38,7 +38,6 @@ import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
import com.android.server.LocalServices;
-import com.android.server.accessibility.cursor.SoftwareCursorGestureHandler;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
@@ -142,13 +141,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
static final int FLAG_SEND_MOTION_EVENTS = 0x00000400;
- /**
- * Flag for enabling the Software Cursor accessibility feature.
- *
- * @see setUserAndEnabledFeatures(int, int)
- */
- static final int FLAG_FEATURE_SOFTWARE_CURSOR = 0x00000800;
-
static final int FEATURES_AFFECTING_MOTION_EVENTS =
FLAG_FEATURE_INJECT_MOTION_EVENTS
| FLAG_FEATURE_AUTOCLICK
@@ -157,8 +149,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
| FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
| FLAG_SERVICE_HANDLES_DOUBLE_TAP
| FLAG_REQUEST_MULTI_FINGER_GESTURES
- | FLAG_REQUEST_2_FINGER_PASSTHROUGH
- | FLAG_FEATURE_SOFTWARE_CURSOR;
+ | FLAG_REQUEST_2_FINGER_PASSTHROUGH;
private final Context mContext;
@@ -179,9 +170,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private KeyboardInterceptor mKeyboardInterceptor;
- private SparseArray<SoftwareCursorGestureHandler> mSoftwareCursorGestureHandler =
- new SparseArray<>(0);
-
private boolean mInstalled;
private int mUserId;
@@ -507,16 +495,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
}
- if ((mEnabledFeatures & FLAG_FEATURE_SOFTWARE_CURSOR) != 0) {
- // TODO: Add full support for multiple displays.
- final SoftwareCursorGestureHandler softwareCursorGestureHandler =
- new SoftwareCursorGestureHandler(displayContext,
- mAms.getSoftwareCursorManager(),
- mAms.getTraceManager());
- addFirstEventHandler(displayId, softwareCursorGestureHandler);
- mSoftwareCursorGestureHandler.put(displayId, softwareCursorGestureHandler);
- }
-
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
MotionEventInjector injector = new MotionEventInjector(
mContext.getMainLooper(), mAms.getTraceManager());
@@ -587,20 +565,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mTouchExplorer.remove(displayId);
}
- final MagnificationGestureHandler handler =
- mMagnificationGestureHandler.get(displayId);
+ final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
if (handler != null) {
handler.onDestroy();
mMagnificationGestureHandler.remove(displayId);
}
- final SoftwareCursorGestureHandler softwareCursorHandler =
- mSoftwareCursorGestureHandler.get(displayId);
- if (softwareCursorHandler != null) {
- softwareCursorHandler.onDestroy();
- mSoftwareCursorGestureHandler.remove(displayId);
- }
-
final EventStreamTransformation eventStreamTransformation = mEventHandler.get(displayId);
if (eventStreamTransformation != null) {
mEventHandler.remove(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 482947098cdb..1efbb0a3c171 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -140,7 +140,6 @@ import com.android.internal.util.IntPair;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.accessibility.cursor.SoftwareCursorManager;
import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationProcessor;
import com.android.server.accessibility.magnification.MagnificationScaleProvider;
@@ -245,8 +244,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final MagnificationController mMagnificationController;
private final MagnificationProcessor mMagnificationProcessor;
- private final SoftwareCursorManager mSoftwareCursorManager;
-
private final MainHandler mMainHandler;
// Lazily initialized - access through getSystemActionPerformer()
@@ -415,7 +412,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mMagnificationController = magnificationController;
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
- mSoftwareCursorManager = new SoftwareCursorManager();
if (inputFilter != null) {
mInputFilter = inputFilter;
mHasInputFilter = true;
@@ -449,7 +445,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
new MagnificationScaleProvider(mContext));
mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
- mSoftwareCursorManager = new SoftwareCursorManager();
init();
}
@@ -2287,9 +2282,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (userState.isPerformGesturesEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
}
- if (userState.isSoftwareCursorEnabledLocked()) {
- flags |= AccessibilityInputFilter.FLAG_FEATURE_SOFTWARE_CURSOR;
- }
if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
@@ -3491,15 +3483,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mMagnificationController;
}
- /**
- * Getter of {@link SoftwareCursorManager}.
- *
- * @return SoftwareCursorManager
- */
- SoftwareCursorManager getSoftwareCursorManager() {
- return mSoftwareCursorManager;
- }
-
@Override
public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 5366a45b3ad0..55dc196fc18d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -109,7 +109,6 @@ class AccessibilityUserState {
private boolean mIsAudioDescriptionByDefaultRequested;
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
- private boolean mIsSoftwareCursorEnabled;
private boolean mIsFilterKeyEventsEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mAccessibilityFocusOnlyInActiveWindow;
@@ -209,7 +208,6 @@ class AccessibilityUserState {
mRequestTwoFingerPassthrough = false;
mSendMotionEventsEnabled = false;
mIsDisplayMagnificationEnabled = false;
- mIsSoftwareCursorEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -511,8 +509,6 @@ class AccessibilityUserState {
pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled));
pw.append(", displayMagnificationEnabled=").append(String.valueOf(
mIsDisplayMagnificationEnabled));
- pw.append(", softwareCursorEnabled=").append(String.valueOf(
- mIsSoftwareCursorEnabled));
pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -623,14 +619,6 @@ class AccessibilityUserState {
mIsDisplayMagnificationEnabled = enabled;
}
- public boolean isSoftwareCursorEnabledLocked() {
- return mIsSoftwareCursorEnabled;
- }
-
- public void setSoftwareCursorEnabledLocked(boolean enabled) {
- mIsSoftwareCursorEnabled = enabled;
- }
-
public boolean isFilterKeyEventsEnabledLocked() {
return mIsFilterKeyEventsEnabled;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java
deleted file mode 100644
index 6ffa8f723257..000000000000
--- a/services/accessibility/java/com/android/server/accessibility/cursor/SoftwareCursorGestureHandler.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.server.accessibility.cursor;
-
-import android.content.Context;
-import android.util.Log;
-import android.util.Slog;
-import android.view.MotionEvent;
-
-import com.android.server.accessibility.AccessibilityTraceManager;
-import com.android.server.accessibility.BaseEventStreamTransformation;
-
-/**
- * Handles touch input for the Software Cursor accessibility feature.
- *
- * The behavior is as follows:
- *
- * <ol>
- * <li> 1. Enable Software Cursor by swiping from the trigger zone on the edge of the screen.
- * <li> 2. Move the cursor by swiping anywhere on the touch screen. Select by tapping anywhere on
- * the touch screen.
- * <li> 3. Put the cursor away by swiping it past either edge of the screen.
- * </ol>
- *
- * TODO(b/243552818): Determine how to handle multi-display.
- */
-public final class SoftwareCursorGestureHandler extends BaseEventStreamTransformation {
-
-
- private static final String LOG_TAG = "SWCursorGestureHandler";
- private static final boolean DEBUG_ALL = Log.isLoggable("SWCursorGestureHandler",
- Log.DEBUG);
-
- Context mContext;
- SoftwareCursorManager mSoftwareCursorManager;
- AccessibilityTraceManager mTraceManager;
-
- public SoftwareCursorGestureHandler(Context context,
- SoftwareCursorManager softwareCursorManager,
- AccessibilityTraceManager traceManager) {
- mContext = context;
- mSoftwareCursorManager = softwareCursorManager;
- mTraceManager = traceManager;
- }
-
- @Override
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (DEBUG_ALL) {
- Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
- }
- // TODO: Add logic.
- // TODO: Prevent users from enabling this filter in conjuntion with TouchExplorer.
- super.onMotionEvent(event, rawEvent, policyFlags);
- }
-
-
-}
diff --git a/services/core/java/com/android/server/DropBoxManagerInternal.java b/services/core/java/com/android/server/DropBoxManagerInternal.java
index 3785a9c011ce..6ede8e9d7c58 100644
--- a/services/core/java/com/android/server/DropBoxManagerInternal.java
+++ b/services/core/java/com/android/server/DropBoxManagerInternal.java
@@ -34,7 +34,15 @@ public abstract class DropBoxManagerInternal {
* to dynamically generate the entry contents.
*/
public interface EntrySource extends Closeable {
- public @BytesLong long length();
public void writeTo(@NonNull FileDescriptor fd) throws IOException;
+
+ public default @BytesLong long length() {
+ // By default, length is unknown
+ return 0;
+ }
+
+ public default void close() throws IOException {
+ // By default, no resources to close
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5c1031a659cf..b34fe69f5a3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2441,8 +2441,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mEnableOffloadQueue = SystemProperties.getBoolean(
"persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
- mEnableModernQueue = SystemProperties.getBoolean(
- "persist.device_config.activity_manager_native_boot.modern_queue_enabled", false);
+ mEnableModernQueue = foreConstants.MODERN_QUEUE_ENABLED;
if (mEnableModernQueue) {
mBroadcastQueues = new BroadcastQueue[1];
@@ -10697,8 +10696,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ @NeverCompile
void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
+ boolean dumpConstants = true;
+ boolean dumpHistory = true;
boolean needSep = false;
boolean onlyHistory = false;
boolean printedAnything = false;
@@ -10783,7 +10785,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!onlyReceivers) {
for (BroadcastQueue q : mBroadcastQueues) {
- needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep);
+ needSep = q.dumpLocked(fd, pw, args, opti,
+ dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
printedAnything |= needSep;
}
}
@@ -10841,6 +10844,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ @NeverCompile
void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
if (mCurBroadcastStats == null) {
@@ -10874,6 +10878,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ @NeverCompile
void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean fullCheckin, String dumpPackage) {
if (mCurBroadcastStats == null) {
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index ceff67e168ab..b7de57f8fc71 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1892,6 +1892,12 @@ public class AppProfiler {
}
}
+ long getCpuDelayTimeForPid(int pid) {
+ synchronized (mProcessCpuTracker) {
+ return mProcessCpuTracker.getCpuDelayTimeForPid(pid);
+ }
+ }
+
List<ProcessCpuTracker.Stats> getCpuStats(Predicate<ProcessCpuTracker.Stats> predicate) {
synchronized (mProcessCpuTracker) {
return mProcessCpuTracker.getStats(st -> predicate.test(st));
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index f9b0dd0d6f28..a4a1c2f0d87c 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -16,8 +16,11 @@
package com.android.server.am;
+import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
@@ -26,13 +29,16 @@ import android.database.ContentObserver;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
import android.util.Slog;
import android.util.TimeUtils;
-import java.io.PrintWriter;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -122,10 +128,19 @@ public class BroadcastConstants {
public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT;
/**
+ * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
+ * of the default {@link BroadcastQueueImpl}.
+ */
+ public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
+ private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
+ private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = false;
+
+ /**
* For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
* dispatch broadcasts to simultaneously.
*/
public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES;
+ private static final String KEY_MAX_RUNNING_PROCESS_QUEUES = "bcast_max_running_process_queues";
private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4;
/**
@@ -134,6 +149,7 @@ public class BroadcastConstants {
* being "runnable" to give other processes a chance to run.
*/
public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
+ private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts";
private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
/**
@@ -142,22 +158,43 @@ public class BroadcastConstants {
* might have applied to that process.
*/
public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
+ private static final String KEY_MAX_PENDING_BROADCASTS = "bcast_max_pending_broadcasts";
private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
/**
- * For {@link BroadcastQueueModernImpl}: Default delay to apply to normal
+ * For {@link BroadcastQueueModernImpl}: Delay to apply to normal
* broadcasts, giving a chance for debouncing of rapidly changing events.
*/
public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
+ private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
private static final long DEFAULT_DELAY_NORMAL_MILLIS = 10_000 * Build.HW_TIMEOUT_MULTIPLIER;
/**
- * For {@link BroadcastQueueModernImpl}: Default delay to apply to
- * broadcasts targeting cached applications.
+ * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
+ * targeting cached applications.
*/
public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
+ private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
private static final long DEFAULT_DELAY_CACHED_MILLIS = 30_000 * Build.HW_TIMEOUT_MULTIPLIER;
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of complete
+ * historical broadcasts to retain for debugging purposes.
+ */
+ public int MAX_HISTORY_COMPLETE_SIZE = DEFAULT_MAX_HISTORY_COMPLETE_SIZE;
+ private static final String KEY_MAX_HISTORY_COMPLETE_SIZE = "bcast_max_history_complete_size";
+ private static final int DEFAULT_MAX_HISTORY_COMPLETE_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of summarized
+ * historical broadcasts to retain for debugging purposes.
+ */
+ public int MAX_HISTORY_SUMMARY_SIZE = DEFAULT_MAX_HISTORY_SUMMARY_SIZE;
+ private static final String KEY_MAX_HISTORY_SUMMARY_SIZE = "bcast_max_history_summary_size";
+ private static final int DEFAULT_MAX_HISTORY_SUMMARY_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+
// Settings override tracking for this instance
private String mSettingsKey;
private SettingsObserver mSettingsObserver;
@@ -179,6 +216,9 @@ public class BroadcastConstants {
// that instance's values are drawn.
public BroadcastConstants(String settingsKey) {
mSettingsKey = settingsKey;
+
+ // Load initial values at least once before we start observing below
+ updateDeviceConfigConstants();
}
/**
@@ -193,14 +233,13 @@ public class BroadcastConstants {
false, mSettingsObserver);
updateSettingsConstants();
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
new HandlerExecutor(handler), this::updateDeviceConfigConstants);
- updateDeviceConfigConstants(
- DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER));
+ updateDeviceConfigConstants();
}
private void updateSettingsConstants() {
- synchronized (mParser) {
+ synchronized (this) {
try {
mParser.setString(Settings.Global.getString(mResolver, mSettingsKey));
} catch (IllegalArgumentException e) {
@@ -220,51 +259,104 @@ public class BroadcastConstants {
}
}
+ /**
+ * Return the {@link SystemProperty} name for the given key in our
+ * {@link DeviceConfig} namespace.
+ */
+ private @NonNull String propertyFor(@NonNull String key) {
+ return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
+ }
+
+ /**
+ * Return the {@link SystemProperty} name for the given key in our
+ * {@link DeviceConfig} namespace, but with a different prefix that can be
+ * used to locally override the {@link DeviceConfig} value.
+ */
+ private @NonNull String propertyOverrideFor(@NonNull String key) {
+ return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
+ }
+
+ private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
+ return SystemProperties.getBoolean(propertyOverrideFor(key),
+ SystemProperties.getBoolean(propertyFor(key), def));
+ }
+
+ private int getDeviceConfigInt(@NonNull String key, int def) {
+ return SystemProperties.getInt(propertyOverrideFor(key),
+ SystemProperties.getInt(propertyFor(key), def));
+ }
+
+ private long getDeviceConfigLong(@NonNull String key, long def) {
+ return SystemProperties.getLong(propertyOverrideFor(key),
+ SystemProperties.getLong(propertyFor(key), def));
+ }
+
private void updateDeviceConfigConstants(@NonNull DeviceConfig.Properties properties) {
- MAX_RUNNING_PROCESS_QUEUES = properties.getInt("bcast_max_running_process_queues",
- DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
- MAX_RUNNING_ACTIVE_BROADCASTS = properties.getInt("bcast_max_running_active_broadcasts",
- DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
- MAX_PENDING_BROADCASTS = properties.getInt("bcast_max_pending_broadcasts",
- DEFAULT_MAX_PENDING_BROADCASTS);
- DELAY_NORMAL_MILLIS = properties.getLong("bcast_delay_normal_millis",
- DEFAULT_DELAY_NORMAL_MILLIS);
- DELAY_CACHED_MILLIS = properties.getLong("bcast_delay_cached_millis",
- DEFAULT_DELAY_CACHED_MILLIS);
+ updateDeviceConfigConstants();
+ }
+
+ /**
+ * Since our values are stored in a "native boot" namespace, we load them
+ * directly from the system properties.
+ */
+ private void updateDeviceConfigConstants() {
+ synchronized (this) {
+ MODERN_QUEUE_ENABLED = getDeviceConfigBoolean(KEY_MODERN_QUEUE_ENABLED,
+ DEFAULT_MODERN_QUEUE_ENABLED);
+ MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES,
+ DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
+ MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
+ DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
+ MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
+ DEFAULT_MAX_PENDING_BROADCASTS);
+ DELAY_NORMAL_MILLIS = getDeviceConfigLong(KEY_DELAY_NORMAL_MILLIS,
+ DEFAULT_DELAY_NORMAL_MILLIS);
+ DELAY_CACHED_MILLIS = getDeviceConfigLong(KEY_DELAY_CACHED_MILLIS,
+ DEFAULT_DELAY_CACHED_MILLIS);
+ MAX_HISTORY_COMPLETE_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_COMPLETE_SIZE,
+ DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
+ MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
+ DEFAULT_MAX_HISTORY_SUMMARY_SIZE);
+ }
}
/**
* Standard dumpsys support; invoked from BroadcastQueue dump
*/
- public void dump(PrintWriter pw) {
- synchronized (mParser) {
- pw.println();
- pw.print(" Broadcast parameters (key=");
+ @NeverCompile
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ synchronized (this) {
+ pw.print("Broadcast parameters (key=");
pw.print(mSettingsKey);
pw.print(", observing=");
pw.print(mSettingsObserver != null);
pw.println("):");
-
- pw.print(" "); pw.print(KEY_TIMEOUT); pw.print(" = ");
- TimeUtils.formatDuration(TIMEOUT, pw);
- pw.println();
-
- pw.print(" "); pw.print(KEY_SLOW_TIME); pw.print(" = ");
- TimeUtils.formatDuration(SLOW_TIME, pw);
+ pw.increaseIndent();
+ pw.print(KEY_TIMEOUT, TimeUtils.formatDuration(TIMEOUT)).println();
+ pw.print(KEY_SLOW_TIME, TimeUtils.formatDuration(SLOW_TIME)).println();
+ pw.print(KEY_DEFERRAL, TimeUtils.formatDuration(DEFERRAL)).println();
+ pw.print(KEY_DEFERRAL_DECAY_FACTOR, DEFERRAL_DECAY_FACTOR).println();
+ pw.print(KEY_DEFERRAL_FLOOR, DEFERRAL_FLOOR).println();
+ pw.print(KEY_ALLOW_BG_ACTIVITY_START_TIMEOUT,
+ TimeUtils.formatDuration(ALLOW_BG_ACTIVITY_START_TIMEOUT)).println();
+ pw.decreaseIndent();
pw.println();
- pw.print(" "); pw.print(KEY_DEFERRAL); pw.print(" = ");
- TimeUtils.formatDuration(DEFERRAL, pw);
- pw.println();
-
- pw.print(" "); pw.print(KEY_DEFERRAL_DECAY_FACTOR); pw.print(" = ");
- pw.println(DEFERRAL_DECAY_FACTOR);
-
- pw.print(" "); pw.print(KEY_DEFERRAL_FLOOR); pw.print(" = ");
- TimeUtils.formatDuration(DEFERRAL_FLOOR, pw);
-
- pw.print(" "); pw.print(KEY_ALLOW_BG_ACTIVITY_START_TIMEOUT); pw.print(" = ");
- TimeUtils.formatDuration(ALLOW_BG_ACTIVITY_START_TIMEOUT, pw);
+ pw.print("Broadcast parameters (namespace=");
+ pw.print(NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT);
+ pw.println("):");
+ pw.increaseIndent();
+ pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println();
+ pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println();
+ pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println();
+ pw.print(KEY_MAX_PENDING_BROADCASTS, MAX_PENDING_BROADCASTS).println();
+ pw.print(KEY_DELAY_NORMAL_MILLIS,
+ TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println();
+ pw.print(KEY_DELAY_CACHED_MILLIS,
+ TimeUtils.formatDuration(DELAY_CACHED_MILLIS)).println();
+ pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
+ pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+ pw.decreaseIndent();
pw.println();
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
index 4c443433d396..2adcf2f48343 100644
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java
@@ -20,7 +20,9 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.Handler;
@@ -36,6 +38,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -80,12 +84,14 @@ public class BroadcastDispatcher {
return broadcasts.isEmpty();
}
+ @NeverCompile
void dumpDebug(ProtoOutputStream proto, long fieldId) {
for (BroadcastRecord br : broadcasts) {
br.dumpDebug(proto, fieldId);
}
}
+ @NeverCompile
void dumpLocked(Dumper d) {
for (BroadcastRecord br : broadcasts) {
d.dump(br);
@@ -143,6 +149,7 @@ public class BroadcastDispatcher {
return mPrinted;
}
+ @NeverCompile
void dump(BroadcastRecord br) {
if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
if (!mPrinted) {
@@ -422,6 +429,7 @@ public class BroadcastDispatcher {
return size;
}
+ @NeverCompile
public void dump(Dumper dumper, String action) {
SparseArray<BroadcastRecord> brs = getDeferredList(action);
if (brs == null) {
@@ -432,6 +440,7 @@ public class BroadcastDispatcher {
}
}
+ @NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
@@ -441,6 +450,7 @@ public class BroadcastDispatcher {
}
}
+ @NeverCompile
private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
for (int i = 0, size = brs.size(); i < size; i++) {
final Object receiver = brs.valueAt(i).receivers.get(0);
@@ -540,6 +550,38 @@ public class BroadcastDispatcher {
}
}
+ private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
+ @UptimeMillisLong long barrierTime) {
+ for (int i = 0; i < list.size(); i++) {
+ if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
+ @UptimeMillisLong long barrierTime) {
+ for (int i = 0; i < list.size(); i++) {
+ if (list.get(i).enqueueTime <= barrierTime) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
+ synchronized (mLock) {
+ if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
+ return false;
+ }
+ return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
+ && isBeyondBarrier(mAlarmQueue, barrierTime)
+ && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
+ && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
+ }
+ }
+
private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
int pending = 0;
final int numEntries = list.size();
@@ -806,6 +848,7 @@ public class BroadcastDispatcher {
/**
* Standard proto dump entry point
*/
+ @NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
if (mCurrentBroadcast != null) {
mCurrentBroadcast.dumpDebug(proto, fieldId);
@@ -1133,6 +1176,7 @@ public class BroadcastDispatcher {
// ----------------------------------
+ @NeverCompile
boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
SimpleDateFormat sdf) {
final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index 8e38f0a97640..a92723ee7036 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -21,6 +21,8 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
final class BroadcastFilter extends IntentFilter {
@@ -53,6 +55,7 @@ final class BroadcastFilter extends IntentFilter {
exported = _exported;
}
+ @NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
super.dumpDebug(proto, BroadcastFilterProto.INTENT_FILTER);
@@ -64,20 +67,24 @@ final class BroadcastFilter extends IntentFilter {
proto.end(token);
}
+ @NeverCompile
public void dump(PrintWriter pw, String prefix) {
dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix);
receiverList.dumpLocal(pw, prefix);
}
+ @NeverCompile
public void dumpBrief(PrintWriter pw, String prefix) {
dumpBroadcastFilterState(pw, prefix);
}
+ @NeverCompile
public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) {
super.dump(pr, prefix);
dumpBroadcastFilterState(pw, prefix);
}
+ @NeverCompile
void dumpBroadcastFilterState(PrintWriter pw, String prefix) {
if (requiredPermission != null) {
pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
diff --git a/services/core/java/com/android/server/am/BroadcastHistory.java b/services/core/java/com/android/server/am/BroadcastHistory.java
index d820d6c802b3..6ac0e8bee58b 100644
--- a/services/core/java/com/android/server/am/BroadcastHistory.java
+++ b/services/core/java/com/android/server/am/BroadcastHistory.java
@@ -16,12 +16,14 @@
package com.android.server.am;
-import android.app.ActivityManager;
+import android.annotation.NonNull;
import android.content.Intent;
import android.os.Bundle;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -31,22 +33,32 @@ import java.util.Date;
* for debugging purposes. Automatically trims itself over time.
*/
public class BroadcastHistory {
- static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
- static final int MAX_BROADCAST_SUMMARY_HISTORY
- = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+ private final int MAX_BROADCAST_HISTORY;
+ private final int MAX_BROADCAST_SUMMARY_HISTORY;
+
+ public BroadcastHistory(@NonNull BroadcastConstants constants) {
+ MAX_BROADCAST_HISTORY = constants.MAX_HISTORY_COMPLETE_SIZE;
+ MAX_BROADCAST_SUMMARY_HISTORY = constants.MAX_HISTORY_SUMMARY_SIZE;
+
+ mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+ mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+ mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+ mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+ mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+ }
/**
* Historical data of past broadcasts, for debugging. This is a ring buffer
* whose last element is at mHistoryNext.
*/
- final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+ final BroadcastRecord[] mBroadcastHistory;
int mHistoryNext = 0;
/**
* Summary of historical data of past broadcasts, for debugging. This is a
* ring buffer whose last element is at mSummaryHistoryNext.
*/
- final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+ final Intent[] mBroadcastSummaryHistory;
int mSummaryHistoryNext = 0;
/**
@@ -54,9 +66,9 @@ public class BroadcastHistory {
* buffer, also tracked via the mSummaryHistoryNext index. These are all in wall
* clock time, not elapsed.
*/
- final long[] mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
- final long[] mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
- final long[] mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+ final long[] mSummaryHistoryEnqueueTime;
+ final long[] mSummaryHistoryDispatchTime;
+ final long[] mSummaryHistoryFinishTime;
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
// Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
@@ -80,6 +92,7 @@ public class BroadcastHistory {
else return x;
}
+ @NeverCompile
public void dumpDebug(ProtoOutputStream proto) {
int lastIndex = mHistoryNext;
int ringIndex = lastIndex;
@@ -113,6 +126,7 @@ public class BroadcastHistory {
} while (ringIndex != lastIndex);
}
+ @NeverCompile
public boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
SimpleDateFormat sdf, boolean dumpAll, boolean needSep) {
int i;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 1369435dacdc..97635b53a6d4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -16,21 +16,28 @@
package com.android.server.am;
+import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
+import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
import android.content.pm.ResolveInfo;
+import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.text.format.DateUtils;
import android.util.IndentingPrintWriter;
import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
@@ -83,6 +90,12 @@ class BroadcastProcessQueue {
@Nullable String traceTrackName;
/**
+ * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
+ * used when deciding if we should extend the soft ANR timeout.
+ */
+ long lastCpuDelayTime;
+
+ /**
* Ordered collection of broadcasts that are waiting to be dispatched to
* this process, as a pair of {@link BroadcastRecord} and the index into
* {@link BroadcastRecord#receivers} that represents the receiver.
@@ -118,6 +131,7 @@ class BroadcastProcessQueue {
private int mCountForeground;
private int mCountOrdered;
private int mCountAlarm;
+ private int mCountPrioritized;
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
@@ -139,29 +153,48 @@ class BroadcastProcessQueue {
* Enqueue the given broadcast to be dispatched to this process at some
* future point in time. The target receiver is indicated by the given index
* into {@link BroadcastRecord#receivers}.
+ * <p>
+ * If the broadcast is marked as {@link BroadcastRecord#isReplacePending()},
+ * then this call will replace any pending dispatch; otherwise it will
+ * enqueue as a normal broadcast.
+ * <p>
+ * When defined, this receiver is considered "blocked" until at least the
+ * given count of other receivers have reached a terminal state; typically
+ * used for ordered broadcasts and priority traunches.
*/
- public void enqueueBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
- // Detect situations where the incoming broadcast should cause us to
- // recalculate when we'll be runnable
- if (mPending.isEmpty()) {
- invalidateRunnableAt();
- }
- if (record.isForeground()) {
- mCountForeground++;
- invalidateRunnableAt();
- }
- if (record.ordered) {
- mCountOrdered++;
- invalidateRunnableAt();
- }
- if (record.alarm) {
- mCountAlarm++;
- invalidateRunnableAt();
+ public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
+ int blockedUntilTerminalCount) {
+ // If caller wants to replace, walk backwards looking for any matches
+ if (record.isReplacePending()) {
+ final Iterator<SomeArgs> it = mPending.descendingIterator();
+ final Object receiver = record.receivers.get(recordIndex);
+ while (it.hasNext()) {
+ final SomeArgs args = it.next();
+ final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
+ final Object testReceiver = testRecord.receivers.get(args.argi1);
+ if ((record.callingUid == testRecord.callingUid)
+ && (record.userId == testRecord.userId)
+ && record.intent.filterEquals(testRecord.intent)
+ && isReceiverEquals(receiver, testReceiver)) {
+ // Exact match found; perform in-place swap
+ args.arg1 = record;
+ args.argi1 = recordIndex;
+ args.argi2 = blockedUntilTerminalCount;
+ onBroadcastDequeued(testRecord);
+ onBroadcastEnqueued(record);
+ return;
+ }
+ }
}
+
+ // Caller isn't interested in replacing, or we didn't find any pending
+ // item to replace above, so enqueue as a new broadcast
SomeArgs args = SomeArgs.obtain();
args.arg1 = record;
args.argi1 = recordIndex;
+ args.argi2 = blockedUntilTerminalCount;
mPending.addLast(args);
+ onBroadcastEnqueued(record);
}
/**
@@ -183,14 +216,15 @@ class BroadcastProcessQueue {
}
/**
- * Remove any broadcasts matching the given predicate.
+ * Invoke given consumer for any broadcasts matching given predicate. If
+ * requested, matching broadcasts will also be removed from this queue.
* <p>
* Predicates that choose to remove a broadcast <em>must</em> finish
* delivery of the matched broadcast, to ensure that situations like ordered
* broadcasts are handled consistently.
*/
- public boolean removeMatchingBroadcasts(@NonNull BroadcastPredicate predicate,
- @NonNull BroadcastConsumer consumer) {
+ public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
+ @NonNull BroadcastConsumer consumer, boolean andRemove) {
boolean didSomething = false;
final Iterator<SomeArgs> it = mPending.iterator();
while (it.hasNext()) {
@@ -199,8 +233,11 @@ class BroadcastProcessQueue {
final int index = args.argi1;
if (predicate.test(record, index)) {
consumer.accept(record, index);
- args.recycle();
- it.remove();
+ if (andRemove) {
+ args.recycle();
+ it.remove();
+ onBroadcastDequeued(record);
+ }
didSomething = true;
}
}
@@ -236,8 +273,10 @@ class BroadcastProcessQueue {
&& (mActive.isForeground() || mActive.ordered || mActive.alarm)) {
// We have an important broadcast right now, so boost priority
return ProcessList.SCHED_GROUP_DEFAULT;
- } else {
+ } else if (!isIdle()) {
return ProcessList.SCHED_GROUP_BACKGROUND;
+ } else {
+ return ProcessList.SCHED_GROUP_UNDEFINED;
}
}
@@ -268,16 +307,7 @@ class BroadcastProcessQueue {
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
- if (mActive.isForeground()) {
- mCountForeground--;
- }
- if (mActive.ordered) {
- mCountOrdered--;
- }
- if (mActive.alarm) {
- mCountAlarm--;
- }
- invalidateRunnableAt();
+ onBroadcastDequeued(mActive);
}
/**
@@ -291,6 +321,44 @@ class BroadcastProcessQueue {
invalidateRunnableAt();
}
+ /**
+ * Update summary statistics when the given record has been enqueued.
+ */
+ private void onBroadcastEnqueued(@NonNull BroadcastRecord record) {
+ if (record.isForeground()) {
+ mCountForeground++;
+ }
+ if (record.ordered) {
+ mCountOrdered++;
+ }
+ if (record.alarm) {
+ mCountAlarm++;
+ }
+ if (record.prioritized) {
+ mCountPrioritized++;
+ }
+ invalidateRunnableAt();
+ }
+
+ /**
+ * Update summary statistics when the given record has been dequeued.
+ */
+ private void onBroadcastDequeued(@NonNull BroadcastRecord record) {
+ if (record.isForeground()) {
+ mCountForeground--;
+ }
+ if (record.ordered) {
+ mCountOrdered--;
+ }
+ if (record.alarm) {
+ mCountAlarm--;
+ }
+ if (record.prioritized) {
+ mCountPrioritized--;
+ }
+ invalidateRunnableAt();
+ }
+
public void traceProcessStartingBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
traceTrackName, toShortString() + " starting", hashCode());
@@ -342,6 +410,30 @@ class BroadcastProcessQueue {
return mActive != null;
}
+ /**
+ * Quickly determine if this queue has broadcasts that are still waiting to
+ * be delivered at some point in the future.
+ */
+ public boolean isIdle() {
+ return !isActive() && isEmpty();
+ }
+
+ /**
+ * Quickly determine if this queue has broadcasts enqueued before the given
+ * barrier timestamp that are still waiting to be delivered.
+ */
+ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
+ if (mActive != null) {
+ return mActive.enqueueTime > barrierTime;
+ }
+ final SomeArgs next = mPending.peekFirst();
+ if (next != null) {
+ return ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
+ }
+ // Nothing running or runnable means we're past the barrier
+ return true;
+ }
+
public boolean isRunnable() {
if (mRunnableAtInvalidated) updateRunnableAt();
return mRunnableAt != Long.MAX_VALUE;
@@ -374,24 +466,26 @@ class BroadcastProcessQueue {
mRunnableAtInvalidated = true;
}
- private static final int REASON_EMPTY = 0;
- private static final int REASON_CONTAINS_FOREGROUND = 1;
- private static final int REASON_CONTAINS_ORDERED = 2;
- private static final int REASON_CONTAINS_ALARM = 3;
- private static final int REASON_CACHED = 4;
- private static final int REASON_NORMAL = 5;
- private static final int REASON_MAX_PENDING = 6;
- private static final int REASON_BLOCKED_ORDERED = 7;
+ static final int REASON_EMPTY = 0;
+ static final int REASON_CONTAINS_FOREGROUND = 1;
+ static final int REASON_CONTAINS_ORDERED = 2;
+ static final int REASON_CONTAINS_ALARM = 3;
+ static final int REASON_CONTAINS_PRIORITIZED = 4;
+ static final int REASON_CACHED = 5;
+ static final int REASON_NORMAL = 6;
+ static final int REASON_MAX_PENDING = 7;
+ static final int REASON_BLOCKED = 8;
@IntDef(flag = false, prefix = { "REASON_" }, value = {
REASON_EMPTY,
REASON_CONTAINS_FOREGROUND,
REASON_CONTAINS_ORDERED,
REASON_CONTAINS_ALARM,
+ REASON_CONTAINS_PRIORITIZED,
REASON_CACHED,
REASON_NORMAL,
REASON_MAX_PENDING,
- REASON_BLOCKED_ORDERED,
+ REASON_BLOCKED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -402,10 +496,11 @@ class BroadcastProcessQueue {
case REASON_CONTAINS_FOREGROUND: return "CONTAINS_FOREGROUND";
case REASON_CONTAINS_ORDERED: return "CONTAINS_ORDERED";
case REASON_CONTAINS_ALARM: return "CONTAINS_ALARM";
+ case REASON_CONTAINS_PRIORITIZED: return "CONTAINS_PRIORITIZED";
case REASON_CACHED: return "CACHED";
case REASON_NORMAL: return "NORMAL";
case REASON_MAX_PENDING: return "MAX_PENDING";
- case REASON_BLOCKED_ORDERED: return "BLOCKED_ORDERED";
+ case REASON_BLOCKED: return "BLOCKED";
default: return Integer.toString(reason);
}
}
@@ -418,13 +513,15 @@ class BroadcastProcessQueue {
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
+ final int blockedUntilTerminalCount = next.argi2;
final long runnableAt = r.enqueueTime;
- // If our next broadcast is ordered, and we're not the next receiver
- // in line, then we're not runnable at all
- if (r.ordered && r.finishedCount != index) {
+ // We might be blocked waiting for other receivers to finish,
+ // typically for an ordered broadcast or priority traunches
+ if (r.terminalCount < blockedUntilTerminalCount
+ && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
mRunnableAt = Long.MAX_VALUE;
- mRunnableAtReason = REASON_BLOCKED_ORDERED;
+ mRunnableAtReason = REASON_BLOCKED;
return;
}
@@ -445,6 +542,9 @@ class BroadcastProcessQueue {
} else if (mCountAlarm > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ALARM;
+ } else if (mCountPrioritized > 0) {
+ mRunnableAt = runnableAt;
+ mRunnableAtReason = REASON_CONTAINS_PRIORITIZED;
} else if (mProcessCached) {
mRunnableAt = runnableAt + constants.DELAY_CACHED_MILLIS;
mRunnableAtReason = REASON_CACHED;
@@ -459,6 +559,22 @@ class BroadcastProcessQueue {
}
/**
+ * Check overall health, confirming things are in a reasonable state and
+ * that we're not wedged.
+ */
+ public void checkHealthLocked() {
+ if (mRunnableAtReason == REASON_BLOCKED) {
+ final SomeArgs next = mPending.peekFirst();
+ Objects.requireNonNull(next, "peekFirst");
+
+ // If blocked more than 10 minutes, we're likely wedged
+ final BroadcastRecord r = (BroadcastRecord) next.arg1;
+ final long waitingTime = SystemClock.uptimeMillis() - r.enqueueTime;
+ checkState(waitingTime < (10 * DateUtils.MINUTE_IN_MILLIS), "waitingTime");
+ }
+ }
+
+ /**
* Insert the given queue into a sorted linked list of "runnable" queues.
*
* @param head the current linked list head
@@ -535,6 +651,7 @@ class BroadcastProcessQueue {
return mCachedToShortString;
}
+ @NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
if ((mActive == null) && mPending.isEmpty()) return;
@@ -547,6 +664,19 @@ class BroadcastProcessQueue {
}
pw.print(" because ");
pw.print(reasonToString(mRunnableAtReason));
+ if (mRunnableAtReason == REASON_BLOCKED) {
+ final SomeArgs next = mPending.peekFirst();
+ if (next != null) {
+ final BroadcastRecord r = (BroadcastRecord) next.arg1;
+ final int blockedUntilTerminalCount = next.argi2;
+ pw.print(" waiting for ");
+ pw.print(blockedUntilTerminalCount);
+ pw.print(" at ");
+ pw.print(r.terminalCount);
+ pw.print(" of ");
+ pw.print(r.receivers.size());
+ }
+ }
pw.println();
pw.increaseIndent();
if (mActive != null) {
@@ -560,6 +690,7 @@ class BroadcastProcessQueue {
pw.println();
}
+ @NeverCompile
private void dumpRecord(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw,
@NonNull BroadcastRecord record, int recordIndex) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index d14fd16b7a77..1e172fc92f40 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -18,16 +18,21 @@ package com.android.server.am;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
+import android.os.DropBoxManager;
import android.os.Handler;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.Set;
@@ -37,6 +42,7 @@ import java.util.Set;
*/
public abstract class BroadcastQueue {
public static final String TAG = "BroadcastQueue";
+ public static final String TAG_DUMP = "broadcast_queue_dump";
final @NonNull ActivityManagerService mService;
final @NonNull Handler mHandler;
@@ -54,20 +60,22 @@ public abstract class BroadcastQueue {
mHistory = Objects.requireNonNull(history);
}
- static void checkState(boolean state, String msg) {
- if (!state) {
- Slog.wtf(TAG, msg, new Throwable());
- }
- }
-
- static void logw(String msg) {
+ static void logw(@NonNull String msg) {
Slog.w(TAG, msg);
}
- static void logv(String msg) {
+ static void logv(@NonNull String msg) {
Slog.v(TAG, msg);
}
+ static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
+ logv(msg);
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
+ }
+
@Override
public String toString() {
return mQueueName;
@@ -167,6 +175,16 @@ public abstract class BroadcastQueue {
public abstract boolean isIdleLocked();
/**
+ * Quickly determine if this queue has broadcasts enqueued before the given
+ * barrier timestamp that are still waiting to be delivered.
+ *
+ * @see #waitForIdle
+ * @see #waitForBarrier
+ */
+ @GuardedBy("mService")
+ public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime);
+
+ /**
* Wait until this queue becomes completely idle.
* <p>
* Any broadcasts waiting to be delivered at some point in the future will
@@ -195,10 +213,27 @@ public abstract class BroadcastQueue {
@GuardedBy("mService")
public abstract @NonNull String describeStateLocked();
+ @GuardedBy("mService")
public abstract void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId);
@GuardedBy("mService")
public abstract boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
- @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
- boolean needSep);
+ @NonNull String[] args, int opti, boolean dumpConstants, boolean dumpHistory,
+ boolean dumpAll, @Nullable String dumpPackage, boolean needSep);
+
+ /**
+ * Execute {@link #dumpLocked} and store the output into
+ * {@link DropBoxManager} for later inspection.
+ */
+ public void dumpToDropBoxLocked(@Nullable String msg) {
+ LocalServices.getService(DropBoxManagerInternal.class).addEntry(TAG_DUMP, (fd) -> {
+ try (FileOutputStream out = new FileOutputStream(fd);
+ PrintWriter pw = new PrintWriter(out)) {
+ pw.print("Message: ");
+ pw.println(msg);
+ dumpLocked(fd, pw, null, 0, false, false, false, null, false);
+ pw.flush();
+ }
+ }, DropBoxManager.IS_TEXT);
+ }
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index a515e5ccb9cb..77300f7cf228 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -69,6 +69,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
@@ -78,6 +79,8 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@@ -163,7 +166,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
private final class BroadcastHandler extends Handler {
public BroadcastHandler(Looper looper) {
- super(looper, null, true);
+ super(looper, null);
}
@Override
@@ -188,7 +191,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
String name, BroadcastConstants constants, boolean allowDelayBehindServices,
int schedGroup) {
this(service, handler, name, constants, new BroadcastSkipPolicy(service),
- new BroadcastHistory(), allowDelayBehindServices, schedGroup);
+ new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
}
BroadcastQueueImpl(ActivityManagerService service, Handler handler,
@@ -1753,19 +1756,20 @@ public class BroadcastQueueImpl extends BroadcastQueue {
// If nothing active, we're beyond barrier
if (isIdleLocked()) return true;
- // Check if active broadcast is beyond barrier
- final BroadcastRecord active = getActiveBroadcastLocked();
- if (active != null && active.enqueueTime > barrierTime) {
- return true;
+ // Check if parallel broadcasts are beyond barrier
+ for (int i = 0; i < mParallelBroadcasts.size(); i++) {
+ if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
+ return false;
+ }
}
// Check if pending broadcast is beyond barrier
final BroadcastRecord pending = getPendingBroadcastLocked();
- if (pending != null && pending.enqueueTime > barrierTime) {
- return true;
+ if ((pending != null) && pending.enqueueTime <= barrierTime) {
+ return false;
}
- return false;
+ return mDispatcher.isBeyondBarrier(barrierTime);
}
public void waitForIdle(PrintWriter pw) {
@@ -1826,6 +1830,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
+ mDispatcher.describeStateLocked();
}
+ @NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
@@ -1842,8 +1847,10 @@ public class BroadcastQueueImpl extends BroadcastQueue {
proto.end(token);
}
+ @NeverCompile
public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+ int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
+ String dumpPackage, boolean needSep) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
|| mPendingBroadcast != null) {
@@ -1879,8 +1886,12 @@ public class BroadcastQueueImpl extends BroadcastQueue {
needSep = true;
}
}
- mConstants.dump(pw);
- needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+ if (dumpConstants) {
+ mConstants.dump(new IndentingPrintWriter(pw));
+ }
+ if (dumpHistory) {
+ needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+ }
return needSep;
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5154b5e00164..fe8d84feb557 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -25,12 +25,14 @@ import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVE
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
+import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
+import static com.android.server.am.BroadcastRecord.getReceiverPriority;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
@@ -39,6 +41,8 @@ import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
@@ -60,7 +64,11 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.text.format.DateUtils;
import android.util.IndentingPrintWriter;
+import android.util.MathUtils;
+import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -74,6 +82,8 @@ import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@@ -81,6 +91,7 @@ import java.util.ArrayList;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
/**
@@ -111,7 +122,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
BroadcastConstants fgConstants, BroadcastConstants bgConstants) {
this(service, handler, fgConstants, bgConstants, new BroadcastSkipPolicy(service),
- new BroadcastHistory());
+ new BroadcastHistory(fgConstants));
}
BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
@@ -140,13 +151,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// TODO: pause queues when processes are frozen
/**
- * When enabled, invoke {@link #checkConsistencyLocked()} periodically to
- * verify that our internal state is consistent. Checking consistency is
- * relatively expensive, so this should be typically disabled.
- */
- private static final boolean CHECK_CONSISTENCY = true;
-
- /**
* Map from UID to per-process broadcast queues. If a UID hosts more than
* one process, each additional process is stored as a linked list using
* {@link BroadcastProcessQueue#next}.
@@ -186,18 +190,30 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private @Nullable BroadcastProcessQueue mRunningColdStart;
/**
- * Collection of latches waiting for queue to go idle.
+ * Collection of latches waiting for device to reach specific state. The
+ * first argument is a function to test for the desired state, and the
+ * second argument is the latch to release once that state is reached.
+ * <p>
+ * This is commonly used for callers that are blocked waiting for an
+ * {@link #isIdleLocked} or {@link #isBeyondBarrierLocked} to be reached,
+ * without requiring that they periodically poll for the state change.
+ * <p>
+ * Finally, the presence of any waiting latches will cause all
+ * future-runnable processes to be runnable immediately, to aid in reaching
+ * the desired state as quickly as possible.
*/
@GuardedBy("mService")
- private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();
+ private final ArrayList<Pair<BooleanSupplier, CountDownLatch>> mWaitingFor = new ArrayList<>();
private final BroadcastConstants mConstants;
private final BroadcastConstants mFgConstants;
private final BroadcastConstants mBgConstants;
private static final int MSG_UPDATE_RUNNING_LIST = 1;
- private static final int MSG_DELIVERY_TIMEOUT = 2;
- private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
+ private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
+ private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
+ private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
+ private static final int MSG_CHECK_HEALTH = 5;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -210,14 +226,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
switch (msg.what) {
case MSG_UPDATE_RUNNING_LIST: {
synchronized (mService) {
- updateRunningList();
+ updateRunningListLocked();
+ }
+ return true;
+ }
+ case MSG_DELIVERY_TIMEOUT_SOFT: {
+ synchronized (mService) {
+ deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj);
}
return true;
}
- case MSG_DELIVERY_TIMEOUT: {
+ case MSG_DELIVERY_TIMEOUT_HARD: {
synchronized (mService) {
- finishReceiverLocked((BroadcastProcessQueue) msg.obj,
- BroadcastRecord.DELIVERY_TIMEOUT);
+ deliveryTimeoutHardLocked((BroadcastProcessQueue) msg.obj);
}
return true;
}
@@ -231,6 +252,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
return true;
}
+ case MSG_CHECK_HEALTH: {
+ synchronized (mService) {
+ checkHealthLocked();
+ }
+ return true;
+ }
}
return false;
};
@@ -300,8 +327,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
-
- if (CHECK_CONSISTENCY) checkConsistencyLocked();
}
/**
@@ -312,15 +337,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* processes and only one pending cold-start.
*/
@GuardedBy("mService")
- private void updateRunningList() {
+ private void updateRunningListLocked() {
int avail = mRunning.length - getRunningSize();
if (avail == 0) return;
final int cookie = traceBegin(TAG, "updateRunningList");
final long now = SystemClock.uptimeMillis();
- // If someone is waiting to go idle, everything is runnable now
- final boolean waitingForIdle = !mWaitingForIdle.isEmpty();
+ // If someone is waiting for a state, everything is runnable now
+ final boolean waitingFor = !mWaitingFor.isEmpty();
// We're doing an update now, so remove any future update requests;
// we'll repost below if needed
@@ -334,7 +359,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If queues beyond this point aren't ready to run yet, schedule
// another pass when they'll be runnable
- if (runnableAt > now && !waitingForIdle) {
+ if (runnableAt > now && !waitingFor) {
mLocalHandler.sendEmptyMessageAtTime(MSG_UPDATE_RUNNING_LIST, runnableAt);
break;
}
@@ -400,13 +425,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
}
- if (waitingForIdle && isIdleLocked()) {
- mWaitingForIdle.forEach((latch) -> latch.countDown());
- mWaitingForIdle.clear();
+ if (waitingFor) {
+ mWaitingFor.removeIf((pair) -> {
+ if (pair.first.getAsBoolean()) {
+ pair.second.countDown();
+ return true;
+ } else {
+ return false;
+ }
+ });
}
- if (CHECK_CONSISTENCY) checkConsistencyLocked();
-
traceEnd(TAG, cookie);
}
@@ -462,9 +491,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Skip any pending registered receivers, since the old process
// would never be around to receive them
- boolean didSomething = queue.removeMatchingBroadcasts((r, i) -> {
+ boolean didSomething = queue.forEachMatchingBroadcast((r, i) -> {
return (r.receivers.get(i) instanceof BroadcastFilter);
- }, mBroadcastConsumerSkip);
+ }, mBroadcastConsumerSkip, true);
if (didSomething || queue.isEmpty()) {
updateRunnableList(queue);
enqueueUpdateRunningList();
@@ -489,22 +518,59 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
? r.options.getRemoveMatchingFilter() : null;
if (removeMatchingFilter != null) {
final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
- skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> {
- // We only allow caller to clear broadcasts they enqueued
- return (testRecord.callingUid == r.callingUid)
+ forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+ // We only allow caller to remove broadcasts they enqueued
+ return (r.callingUid == testRecord.callingUid)
+ && (r.userId == testRecord.userId)
&& removeMatching.test(testRecord.intent);
- });
+ }, mBroadcastConsumerSkipAndCanceled, true);
+ }
+
+ if (r.isReplacePending()) {
+ // Leave the skipped broadcasts intact in queue, so that we can
+ // replace them at their current position during enqueue below
+ forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+ // We only allow caller to replace broadcasts they enqueued
+ return (r.callingUid == testRecord.callingUid)
+ && (r.userId == testRecord.userId)
+ && r.intent.filterEquals(testRecord.intent);
+ }, mBroadcastConsumerSkipAndCanceled, false);
}
r.enqueueTime = SystemClock.uptimeMillis();
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
+ int lastPriority = 0;
+ int lastPriorityIndex = 0;
+
for (int i = 0; i < r.receivers.size(); i++) {
final Object receiver = r.receivers.get(i);
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
getReceiverProcessName(receiver), getReceiverUid(receiver));
- queue.enqueueBroadcast(r, i);
+
+ final int blockedUntilTerminalCount;
+ if (r.ordered) {
+ // When sending an ordered broadcast, we need to block this
+ // receiver until all previous receivers have terminated
+ blockedUntilTerminalCount = i;
+ } else if (r.prioritized) {
+ // When sending a prioritized broadcast, we only need to wait
+ // for the previous traunch of receivers to be terminated
+ final int thisPriority = getReceiverPriority(receiver);
+ if ((i == 0) || (thisPriority != lastPriority)) {
+ lastPriority = thisPriority;
+ lastPriorityIndex = i;
+ blockedUntilTerminalCount = i;
+ } else {
+ blockedUntilTerminalCount = lastPriorityIndex;
+ }
+ } else {
+ // Otherwise we don't need to block at all
+ blockedUntilTerminalCount = 0;
+ }
+
+ queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -579,7 +645,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
- if (r.finishedCount == 0) {
+ if (r.terminalCount == 0) {
r.dispatchTime = SystemClock.uptimeMillis();
r.dispatchRealTime = SystemClock.elapsedRealtime();
r.dispatchClockTime = System.currentTimeMillis();
@@ -615,9 +681,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
if (mService.mProcessesReady && !r.timeoutExempt) {
+ queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
+
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
+ Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout);
}
if (r.allowBackgroundActivityStarts) {
@@ -697,6 +765,26 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
}
}
+ // Clear so both local and remote references can be GC'ed
+ r.resultTo = null;
+ }
+
+ private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) {
+ if (queue.app != null) {
+ // Instead of immediately triggering an ANR, extend the timeout by
+ // the amount of time the process was runnable-but-waiting; we're
+ // only willing to do this once before triggering an hard ANR
+ final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
+ final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT);
+ mLocalHandler.sendMessageDelayed(
+ Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout);
+ } else {
+ deliveryTimeoutHardLocked(queue);
+ }
+ }
+
+ private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
}
@Override
@@ -720,7 +808,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// When the caller aborted an ordered broadcast, we mark all remaining
// receivers as skipped
if (r.ordered && r.resultAbort) {
- for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
+ for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
setDeliveryState(null, null, r, i, r.receivers.get(i),
BroadcastRecord.DELIVERY_SKIPPED);
}
@@ -747,7 +835,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
.forBroadcastReceiver("Broadcast of " + r.toShortString()));
}
} else {
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
+ mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
+ mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
}
// Even if we have more broadcasts, if we've made reasonable progress
@@ -814,23 +903,30 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
+ deliveryStateToString(newDeliveryState));
}
- r.finishedCount++;
+ r.terminalCount++;
notifyFinishReceiver(queue, r, index, receiver);
- if (r.ordered) {
- if (r.finishedCount < r.receivers.size()) {
- // We just finished an ordered receiver, which means the
- // next receiver might now be runnable
- final Object nextReceiver = r.receivers.get(r.finishedCount);
- final BroadcastProcessQueue nextQueue = getProcessQueue(
- getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
- nextQueue.invalidateRunnableAt();
- updateRunnableList(nextQueue);
- enqueueUpdateRunningList();
- } else {
- // Everything finished, so deliver final result
- scheduleResultTo(r);
+ // When entire ordered broadcast finished, deliver final result
+ if (r.ordered && (r.terminalCount == r.receivers.size())) {
+ scheduleResultTo(r);
+ }
+
+ // Our terminal state here might be enough for another process
+ // blocked on us to now be runnable
+ if (r.ordered || r.prioritized) {
+ for (int i = 0; i < r.receivers.size(); i++) {
+ if (!isDeliveryStateTerminal(getDeliveryState(r, i)) || (i == index)) {
+ final Object otherReceiver = r.receivers.get(i);
+ final BroadcastProcessQueue otherQueue = getProcessQueue(
+ getReceiverProcessName(otherReceiver),
+ getReceiverUid(otherReceiver));
+ if (otherQueue != null) {
+ otherQueue.invalidateRunnableAt();
+ updateRunnableList(otherQueue);
+ }
+ }
}
+ enqueueUpdateRunningList();
}
}
}
@@ -879,7 +975,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
};
broadcastPredicate = BROADCAST_PREDICATE_ANY;
}
- return skipMatchingBroadcasts(queuePredicate, broadcastPredicate);
+ return forEachMatchingBroadcast(queuePredicate, broadcastPredicate,
+ mBroadcastConsumerSkip, true);
}
private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
@@ -895,19 +992,48 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
};
- private boolean skipMatchingBroadcasts(
+ /**
+ * Typical consumer that will both skip the given broadcast and mark it as
+ * cancelled, usually as a result of it matching a predicate.
+ */
+ private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> {
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ r.resultCode = Activity.RESULT_CANCELED;
+ r.resultData = null;
+ r.resultExtras = null;
+ };
+
+ /**
+ * Verify that all known {@link #mProcessQueues} are in the state tested by
+ * the given {@link Predicate}.
+ */
+ private boolean testAllProcessQueues(@NonNull Predicate<BroadcastProcessQueue> test,
+ @NonNull String label, @Nullable PrintWriter pw) {
+ for (int i = 0; i < mProcessQueues.size(); i++) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ if (!test.test(leaf)) {
+ logv("Test " + label + " failed due to " + leaf.toShortString(), pw);
+ return false;
+ }
+ leaf = leaf.processNameNext;
+ }
+ }
+ logv("Test " + label + " passed", pw);
+ return true;
+ }
+
+ private boolean forEachMatchingBroadcast(
@NonNull Predicate<BroadcastProcessQueue> queuePredicate,
- @NonNull BroadcastPredicate broadcastPredicate) {
- // Note that we carefully preserve any "skipped" broadcasts in their
- // queues so that we follow our normal flow for "finishing" a broadcast,
- // which is where we handle things like ordered broadcasts.
+ @NonNull BroadcastPredicate broadcastPredicate,
+ @NonNull BroadcastConsumer broadcastConsumer, boolean andRemove) {
boolean didSomething = false;
for (int i = 0; i < mProcessQueues.size(); i++) {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
if (queuePredicate.test(leaf)) {
- if (leaf.removeMatchingBroadcasts(broadcastPredicate,
- mBroadcastConsumerSkip)) {
+ if (leaf.forEachMatchingBroadcast(broadcastPredicate,
+ broadcastConsumer, andRemove)) {
updateRunnableList(leaf);
didSomething = true;
}
@@ -940,18 +1066,45 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
}, ActivityManager.UID_OBSERVER_CACHED, 0, "android");
+
+ // Kick off periodic health checks
+ checkHealthLocked();
}
@Override
public boolean isIdleLocked() {
- return (mRunnableHead == null) && (getRunningSize() == 0);
+ return isIdleLocked(null);
+ }
+
+ public boolean isIdleLocked(@Nullable PrintWriter pw) {
+ return testAllProcessQueues(q -> q.isIdle(), "idle", pw);
+ }
+
+ @Override
+ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
+ return isBeyondBarrierLocked(barrierTime, null);
+ }
+
+ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime,
+ @Nullable PrintWriter pw) {
+ return testAllProcessQueues(q -> q.isBeyondBarrierLocked(barrierTime), "barrier", pw);
}
@Override
public void waitForIdle(@Nullable PrintWriter pw) {
+ waitFor(() -> isIdleLocked(pw));
+ }
+
+ @Override
+ public void waitForBarrier(@Nullable PrintWriter pw) {
+ final long now = SystemClock.uptimeMillis();
+ waitFor(() -> isBeyondBarrierLocked(now, pw));
+ }
+
+ public void waitFor(@NonNull BooleanSupplier condition) {
final CountDownLatch latch = new CountDownLatch(1);
synchronized (mService) {
- mWaitingForIdle.add(latch);
+ mWaitingFor.add(Pair.create(condition, latch));
}
enqueueUpdateRunningList();
try {
@@ -962,12 +1115,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
@Override
- public void waitForBarrier(@Nullable PrintWriter pw) {
- // TODO: implement
- throw new UnsupportedOperationException();
- }
-
- @Override
public String describeStateLocked() {
return getRunningSize() + " running";
}
@@ -984,28 +1131,51 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
/**
- * Heavy verification of internal consistency.
+ * Check overall health, confirming things are in a reasonable state and
+ * that we're not wedged. If we determine we're in an unhealthy state, dump
+ * current state once and stop future health checks to avoid spamming.
*/
- private void checkConsistencyLocked() {
- // Verify all runnable queues are sorted
- BroadcastProcessQueue prev = null;
- BroadcastProcessQueue next = mRunnableHead;
- while (next != null) {
- checkState(next.runnableAtPrev == prev, "runnableAtPrev");
- checkState(next.isRunnable(), "isRunnable " + next);
- if (prev != null) {
- checkState(next.getRunnableAt() >= prev.getRunnableAt(),
- "getRunnableAt " + next + " vs " + prev);
+ @VisibleForTesting
+ void checkHealthLocked() {
+ try {
+ // Verify all runnable queues are sorted
+ BroadcastProcessQueue prev = null;
+ BroadcastProcessQueue next = mRunnableHead;
+ while (next != null) {
+ checkState(next.runnableAtPrev == prev, "runnableAtPrev");
+ checkState(next.isRunnable(), "isRunnable " + next);
+ if (prev != null) {
+ checkState(next.getRunnableAt() >= prev.getRunnableAt(),
+ "getRunnableAt " + next + " vs " + prev);
+ }
+ prev = next;
+ next = next.runnableAtNext;
}
- prev = next;
- next = next.runnableAtNext;
- }
- // Verify all running queues are active
- for (BroadcastProcessQueue queue : mRunning) {
- if (queue != null) {
- checkState(queue.isActive(), "isActive " + queue);
+ // Verify all running queues are active
+ for (BroadcastProcessQueue queue : mRunning) {
+ if (queue != null) {
+ checkState(queue.isActive(), "isActive " + queue);
+ }
+ }
+
+ // Verify health of all known process queues
+ for (int i = 0; i < mProcessQueues.size(); i++) {
+ BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+ while (leaf != null) {
+ leaf.checkHealthLocked();
+ leaf = leaf.processNameNext;
+ }
}
+
+ // If no health issues found above, check again in the future
+ mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_HEALTH, DateUtils.MINUTE_IN_MILLIS);
+
+ } catch (Exception e) {
+ // Throw up a message to indicate that something went wrong, and
+ // dump current state for later inspection
+ Slog.wtf(TAG, e);
+ dumpToDropBoxLocked(e.toString());
}
}
@@ -1148,7 +1318,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
receiverType, type, dispatchDelay, receiveDelay, finishDelay);
- final boolean recordFinished = (r.finishedCount == r.receivers.size());
+ final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
mHistory.addBroadcastToHistoryLocked(r);
@@ -1252,6 +1422,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
@Override
+ @NeverCompile
public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
@@ -1260,14 +1431,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
@Override
+ @NeverCompile
public boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
- @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
- boolean needSep) {
+ @NonNull String[] args, int opti, boolean dumpConstants, boolean dumpHistory,
+ boolean dumpAll, @Nullable String dumpPackage, boolean needSep) {
final long now = SystemClock.uptimeMillis();
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.increaseIndent();
-
ipw.println();
+
ipw.println("📋 Per-process queues:");
ipw.increaseIndent();
for (int i = 0; i < mProcessQueues.size(); i++) {
@@ -1278,8 +1450,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
ipw.decreaseIndent();
-
ipw.println();
+
ipw.println("🧍 Runnable:");
ipw.increaseIndent();
if (mRunnableHead == null) {
@@ -1297,8 +1469,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
ipw.decreaseIndent();
-
ipw.println();
+
ipw.println("🏃 Running:");
ipw.increaseIndent();
for (BroadcastProcessQueue queue : mRunning) {
@@ -1314,9 +1486,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
ipw.decreaseIndent();
+ ipw.println();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+ if (dumpConstants) {
+ mConstants.dump(ipw);
+ }
+ if (dumpHistory) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+ }
return needSep;
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 170a25a6f358..bcc76e949e5f 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -45,13 +45,14 @@ import android.os.IBinder;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.PrintWriterPrinter;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -60,6 +61,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
@@ -68,10 +70,10 @@ import java.util.function.BiFunction;
* An active intent broadcast.
*/
final class BroadcastRecord extends Binder {
- final Intent intent; // the original intent that generated us
- final ComponentName targetComp; // original component name set on the intent
- final ProcessRecord callerApp; // process that sent this
- final String callerPackage; // who sent this
+ final @NonNull Intent intent; // the original intent that generated us
+ final @Nullable ComponentName targetComp; // original component name set on the intent
+ final @Nullable ProcessRecord callerApp; // process that sent this
+ final @Nullable String callerPackage; // who sent this
final @Nullable String callerFeatureId; // which feature in the package sent this
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
@@ -82,16 +84,17 @@ final class BroadcastRecord extends Binder {
final boolean pushMessage; // originated from a push message?
final boolean pushMessageOverQuota; // originated from a push message which was over quota?
final boolean initialSticky; // initial broadcast from register to sticky?
+ final boolean prioritized; // contains more than one priority tranche
final int userId; // user id this broadcast was for
- final String resolvedType; // the resolved data type
- final String[] requiredPermissions; // permissions the caller has required
- final String[] excludedPermissions; // permissions to exclude
- final String[] excludedPackages; // packages to exclude
+ final @Nullable String resolvedType; // the resolved data type
+ final @Nullable String[] requiredPermissions; // permissions the caller has required
+ final @Nullable String[] excludedPermissions; // permissions to exclude
+ final @Nullable String[] excludedPackages; // packages to exclude
final int appOp; // an app op that is associated with this broadcast
- final BroadcastOptions options; // BroadcastOptions supplied by caller
+ final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
- IIntentReceiver resultTo; // who receives final result if non-null
+ @Nullable IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
int splitCount; // refcount for result callback, when split
int splitToken; // identifier for cross-BroadcastRecord refcount
@@ -105,18 +108,18 @@ final class BroadcastRecord extends Binder {
@UptimeMillisLong long finishTime; // when broadcast finished
final @UptimeMillisLong long[] scheduledTime; // when each receiver was scheduled
final @UptimeMillisLong long[] terminalTime; // when each receiver was terminal
- boolean timeoutExempt; // true if this broadcast is not subject to receiver timeouts
+ final boolean timeoutExempt; // true if this broadcast is not subject to receiver timeouts
int resultCode; // current result code value.
- String resultData; // current result data value.
- Bundle resultExtras; // current result extra data values.
+ @Nullable String resultData; // current result data value.
+ @Nullable Bundle resultExtras; // current result extra data values.
boolean resultAbort; // current result abortBroadcast value.
int nextReceiver; // next receiver to be executed.
int state;
int anrCount; // has this broadcast record hit any ANRs?
int manifestCount; // number of manifest receivers dispatched.
int manifestSkipCount; // number of manifest receivers skipped.
- int finishedCount; // number of receivers finished.
- BroadcastQueue queue; // the outbound queue handling this broadcast
+ int terminalCount; // number of receivers in terminal state.
+ @Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// if set to true, app's process will be temporarily allowed to start activities from background
// for the duration of the broadcast dispatch
@@ -130,8 +133,8 @@ final class BroadcastRecord extends Binder {
@Nullable
final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
- String cachedToString;
- String cachedToShortString;
+ private @Nullable String mCachedToString;
+ private @Nullable String mCachedToShortString;
/** Empty immutable list of receivers */
static final List<Object> EMPTY_RECEIVERS = List.of();
@@ -205,6 +208,7 @@ final class BroadcastRecord extends Binder {
// Private refcount-management bookkeeping; start > 0
static AtomicInteger sNextToken = new AtomicInteger(1);
+ @NeverCompile
void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
final long now = SystemClock.uptimeMillis();
@@ -350,7 +354,7 @@ final class BroadcastRecord extends Binder {
throw new NullPointerException("Can't construct with a null intent");
}
queue = _queue;
- intent = _intent;
+ intent = Objects.requireNonNull(_intent);
targetComp = _intent.getComponent();
callerApp = _callerApp;
callerPackage = _callerPackage;
@@ -375,6 +379,7 @@ final class BroadcastRecord extends Binder {
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
+ prioritized = isPrioritized(receivers);
userId = _userId;
nextReceiver = 0;
state = IDLE;
@@ -392,7 +397,7 @@ final class BroadcastRecord extends Binder {
* Only used by {@link #maybeStripForHistory}.
*/
private BroadcastRecord(BroadcastRecord from, Intent newIntent) {
- intent = newIntent;
+ intent = Objects.requireNonNull(newIntent);
targetComp = newIntent.getComponent();
callerApp = from.callerApp;
@@ -404,6 +409,7 @@ final class BroadcastRecord extends Binder {
ordered = from.ordered;
sticky = from.sticky;
initialSticky = from.initialSticky;
+ prioritized = from.prioritized;
userId = from.userId;
resolvedType = from.resolvedType;
requiredPermissions = from.requiredPermissions;
@@ -646,6 +652,24 @@ final class BroadcastRecord extends Binder {
return (newIntent != null) ? newIntent : intent;
}
+ /**
+ * Return if given receivers list has more than one traunch of priorities.
+ */
+ @VisibleForTesting
+ static boolean isPrioritized(@NonNull List<Object> receivers) {
+ int firstPriority = 0;
+ for (int i = 0; i < receivers.size(); i++) {
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if (i == 0) {
+ firstPriority = thisPriority;
+ } else if (thisPriority != firstPriority) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
@@ -670,6 +694,27 @@ final class BroadcastRecord extends Binder {
}
}
+ static int getReceiverPriority(@NonNull Object receiver) {
+ if (receiver instanceof BroadcastFilter) {
+ return ((BroadcastFilter) receiver).getPriority();
+ } else /* if (receiver instanceof ResolveInfo) */ {
+ return ((ResolveInfo) receiver).priority;
+ }
+ }
+
+ static boolean isReceiverEquals(@NonNull Object a, @NonNull Object b) {
+ if (a == b) {
+ return true;
+ } else if (a instanceof ResolveInfo && b instanceof ResolveInfo) {
+ final ResolveInfo infoA = (ResolveInfo) a;
+ final ResolveInfo infoB = (ResolveInfo) b;
+ return Objects.equals(infoA.activityInfo.packageName, infoB.activityInfo.packageName)
+ && Objects.equals(infoA.activityInfo.name, infoB.activityInfo.name);
+ } else {
+ return false;
+ }
+ }
+
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;
@@ -749,29 +794,30 @@ final class BroadcastRecord extends Binder {
@Override
public String toString() {
- if (cachedToString == null) {
+ if (mCachedToString == null) {
String label = intent.getAction();
if (label == null) {
label = intent.toString();
}
- cachedToString = "BroadcastRecord{"
+ mCachedToString = "BroadcastRecord{"
+ Integer.toHexString(System.identityHashCode(this))
+ " u" + userId + " " + label + "}";
}
- return cachedToString;
+ return mCachedToString;
}
public String toShortString() {
- if (cachedToShortString == null) {
+ if (mCachedToShortString == null) {
String label = intent.getAction();
if (label == null) {
label = intent.toString();
}
- cachedToShortString = label + "/u" + userId;
+ mCachedToShortString = label + "/u" + userId;
}
- return cachedToShortString;
+ return mCachedToShortString;
}
+ @NeverCompile
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(BroadcastRecordProto.USER_ID, userId);
diff --git a/services/core/java/com/android/server/am/BroadcastStats.java b/services/core/java/com/android/server/am/BroadcastStats.java
index fd2458222a89..9417473199e6 100644
--- a/services/core/java/com/android/server/am/BroadcastStats.java
+++ b/services/core/java/com/android/server/am/BroadcastStats.java
@@ -20,6 +20,8 @@ import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.TimeUtils;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -106,6 +108,7 @@ public final class BroadcastStats {
ve.mCount++;
}
+ @NeverCompile
public boolean dumpStats(PrintWriter pw, String prefix, String dumpPackage) {
boolean printedSomething = false;
ArrayList<ActionEntry> actions = new ArrayList<>(mActions.size());
@@ -155,6 +158,7 @@ public final class BroadcastStats {
return printedSomething;
}
+ @NeverCompile
public void dumpCheckinStats(PrintWriter pw, String dumpPackage) {
pw.print("broadcast-stats,1,");
pw.print(mStartRealtime);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbe80c8aee35..68e5a5df9562 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1641,11 +1641,10 @@ public class OomAdjuster {
boolean foregroundActivities = false;
boolean hasVisibleActivities = false;
- if (app == topApp && (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP
- || PROCESS_STATE_CUR_TOP == PROCESS_STATE_IMPORTANT_FOREGROUND)) {
+ if (app == topApp && PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
- if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
+ if (mService.mAtmInternal.useTopSchedGroupForTopProcess()) {
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
state.setAdjType("top-activity");
} else {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index cc9071afa1c7..3b04dbb1da98 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1336,6 +1336,10 @@ class ProcessRecord implements WindowProcessListener {
return mService.mAppProfiler.getCpuTimeForPid(mPid);
}
+ public long getCpuDelayTime() {
+ return mService.mAppProfiler.getCpuDelayTimeForPid(mPid);
+ }
+
@Override
public void onStartActivity(int topProcessState, boolean setProfileProc, String packageName,
long versionCode) {
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 9ccf74115ba0..060e3eef88ec 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -65,6 +65,7 @@
"file_patterns": ["Broadcast"],
"name": "FrameworksMockingServicesTests",
"options": [
+ { "include-filter": "com.android.server.am.BroadcastRecordTest" },
{ "include-filter": "com.android.server.am.BroadcastQueueTest" },
{ "include-filter": "com.android.server.am.BroadcastQueueModernImplTest" }
]
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0ec210090437..067c35f428e5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -6396,9 +6396,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
* @return {@code true} if userId has debugging privileges.
* i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}.
*/
- private boolean userHasDebugPriv(int userId, final ShellCommand shellCommand) {
- if (mUserManager.hasUserRestriction(
- UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) {
+ private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) {
+ if (mUserManagerInternal.hasUserRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, userId)) {
shellCommand.getErrPrintWriter().println("User #" + userId
+ " is restricted with DISALLOW_DEBUGGING_FEATURES.");
return false;
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 2a6ae44a99e8..cb6e43ce7d05 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -17,7 +17,6 @@
package com.android.server.locksettings;
import android.security.AndroidKeyStoreMaintenance;
-import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
@@ -223,13 +222,6 @@ public class SyntheticPasswordCrypto {
keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
Slog.i(TAG, "Using rollback-resistant key");
} catch (KeyStoreException e) {
- if (!(e.getCause() instanceof android.security.KeyStoreException)) {
- throw e;
- }
- int errorCode = ((android.security.KeyStoreException) e.getCause()).getErrorCode();
- if (errorCode != KeymasterDefs.KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE) {
- throw e;
- }
Slog.w(TAG, "Rollback-resistant keys unavailable. Falling back to "
+ "non-rollback-resistant key");
keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 8a8486038558..6e931717c1dd 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -270,6 +270,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+ private int[] mCpuPowerBracketMap;
+ private final CpuUsageDetails mCpuUsageDetails = new CpuUsageDetails();
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -478,7 +480,7 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
@VisibleForTesting
- public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+ public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
if (!initKernelSingleUidTimeReaderLocked()) {
return;
}
@@ -488,12 +490,20 @@ public class BatteryStatsImpl extends BatteryStats {
mNumSingleUidCpuTimeReads++;
LongArrayMultiStateCounter onBatteryCounter =
- u.getProcStateTimeCounter(timestampMs).getCounter();
+ u.getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
LongArrayMultiStateCounter onBatteryScreenOffCounter =
- u.getProcStateScreenOffTimeCounter(timestampMs).getCounter();
+ u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+ if (isUsageHistoryEnabled()) {
+ LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+ getCpuTimeInFreqContainer();
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs,
+ deltaContainer);
+ recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+ } else {
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, elapsedRealtimeMs);
+ }
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
if (u.mChildUids != null) {
LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
@@ -504,14 +514,27 @@ public class BatteryStatsImpl extends BatteryStats {
u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
if (cpuTimeInFreqCounter != null) {
mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
- cpuTimeInFreqCounter, timestampMs, deltaContainer);
+ cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
+ if (isUsageHistoryEnabled()) {
+ recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+ }
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
}
}
+ private void recordCpuUsage(int uid, LongArrayMultiStateCounter.LongArrayContainer cpuUsage,
+ long elapsedRealtimeMs, long uptimeMs) {
+ if (!cpuUsage.combineValues(mCpuUsageDetails.cpuUsageMs, mCpuPowerBracketMap)) {
+ return;
+ }
+
+ mCpuUsageDetails.uid = uid;
+ mHistory.recordCpuUsage(elapsedRealtimeMs, uptimeMs, mCpuUsageDetails);
+ }
+
/**
* Removes kernel CPU stats for removed UIDs, in the order they were added to the
* mPendingRemovedUids queue.
@@ -557,16 +580,26 @@ public class BatteryStatsImpl extends BatteryStats {
continue;
}
- final long timestampMs = mClock.elapsedRealtime();
+ final long elapsedRealtimeMs = mClock.elapsedRealtime();
+ final long uptimeMs = mClock.uptimeMillis();
final LongArrayMultiStateCounter onBatteryCounter =
- u.getProcStateTimeCounter(timestampMs).getCounter();
+ u.getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
final LongArrayMultiStateCounter onBatteryScreenOffCounter =
- u.getProcStateScreenOffTimeCounter(timestampMs).getCounter();
+ u.getProcStateScreenOffTimeCounter(elapsedRealtimeMs).getCounter();
if (uid == parentUid || Process.isSdkSandboxUid(uid)) {
- mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter, timestampMs);
+ if (isUsageHistoryEnabled()) {
+ LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+ getCpuTimeInFreqContainer();
+ mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+ elapsedRealtimeMs, deltaContainer);
+ recordCpuUsage(parentUid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+ } else {
+ mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryCounter,
+ elapsedRealtimeMs);
+ }
mKernelSingleUidTimeReader.addDelta(parentUid, onBatteryScreenOffCounter,
- timestampMs);
+ elapsedRealtimeMs);
} else {
Uid.ChildUid childUid = u.getChildUid(uid);
if (childUid != null) {
@@ -574,9 +607,12 @@ public class BatteryStatsImpl extends BatteryStats {
if (counter != null) {
final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+ mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
+ if (isUsageHistoryEnabled()) {
+ recordCpuUsage(uid, deltaContainer, elapsedRealtimeMs, uptimeMs);
+ }
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
@@ -585,17 +621,6 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- @VisibleForTesting
- public static long[] addCpuTimes(long[] timesA, long[] timesB) {
- if (timesA != null && timesB != null) {
- for (int i = timesA.length - 1; i >= 0; --i) {
- timesA[i] += timesB[i];
- }
- return timesA;
- }
- return timesA == null ? (timesB == null ? null : timesB) : timesA;
- }
-
@GuardedBy("this")
private boolean initKernelSingleUidTimeReaderLocked() {
if (mKernelSingleUidTimeReader == null) {
@@ -7422,8 +7447,10 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void recordMeasuredEnergyDetailsLocked(long elapsedRealtimeMs,
long uptimeMs, MeasuredEnergyDetails measuredEnergyDetails) {
- mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
- measuredEnergyDetails);
+ if (isUsageHistoryEnabled()) {
+ mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
+ measuredEnergyDetails);
+ }
}
@GuardedBy("this")
@@ -10285,7 +10312,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (mBsi.trackPerProcStateCpuTimes()) {
- mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+ mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs, uptimeMs);
LongArrayMultiStateCounter onBatteryCounter =
getProcStateTimeCounter(elapsedRealtimeMs).getCounter();
@@ -10784,6 +10811,10 @@ public class BatteryStatsImpl extends BatteryStats {
mBatteryLevel = 0;
}
+ /**
+ * Injects a power profile.
+ */
+ @GuardedBy("this")
public void setPowerProfileLocked(PowerProfile profile) {
mPowerProfile = profile;
@@ -10800,6 +10831,27 @@ public class BatteryStatsImpl extends BatteryStats {
firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
}
+ // Initialize CPU power bracket map, which combines CPU states (cluster/freq pairs)
+ // into a small number of brackets
+ mCpuPowerBracketMap = new int[getCpuFreqCount()];
+ int index = 0;
+ int numCpuClusters = mPowerProfile.getNumCpuClusters();
+ for (int cluster = 0; cluster < numCpuClusters; cluster++) {
+ int steps = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
+ for (int step = 0; step < steps; step++) {
+ mCpuPowerBracketMap[index++] =
+ mPowerProfile.getPowerBracketForCpuCore(cluster, step);
+ }
+ }
+
+ int cpuPowerBracketCount = mPowerProfile.getCpuPowerBracketCount();
+ mCpuUsageDetails.cpuBracketDescriptions = new String[cpuPowerBracketCount];
+ mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount];
+ for (int i = 0; i < cpuPowerBracketCount; i++) {
+ mCpuUsageDetails.cpuBracketDescriptions[i] =
+ mPowerProfile.getCpuPowerBracketDescription(i);
+ }
+
if (mEstimatedBatteryCapacityMah == -1) {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity();
@@ -14630,11 +14682,16 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- public boolean trackPerProcStateCpuTimes() {
+ private boolean trackPerProcStateCpuTimes() {
return mCpuUidFreqTimeReader.isFastCpuTimesReader();
}
@GuardedBy("this")
+ private boolean isUsageHistoryEnabled() {
+ return false;
+ }
+
+ @GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
registerUsbStateReceiver(context);
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 886e8e687e5a..99950a0c185b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -687,7 +687,7 @@ public class TrustManagerService extends SystemService {
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -2083,7 +2083,7 @@ public class TrustManagerService extends SystemService {
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index d131457cb9a2..8cbd9fc4cc8d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -50,6 +50,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationAdapter;
import android.view.WindowManager;
+import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -566,14 +567,39 @@ public class ActivityStartController {
return false;
}
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
+ final RemoteTransition remote = options.getRemoteTransition();
+ if (remote != null && rootTask.mTransitionController.isCollecting()) {
+ final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, rootTask.mTransitionController,
+ mService.mWindowManager.mSyncEngine);
+ // Special case: we are entering recents while an existing transition is running. In
+ // this case, we know it's safe to "defer" the activity launch, so lets do so now so
+ // that it can get its own transition and thus update launcher correctly.
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ () -> rootTask.mTransitionController.moveToCollecting(transition),
+ () -> {
+ final Task task = r.getTask();
+ task.mTransitionController.requestStartTransition(transition,
+ task, remote, null /* displayChange */);
+ task.mTransitionController.collect(task);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ });
+ } else {
+ final Task task = r.getTask();
+ task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, task, task /* readyGroupRef */,
+ options.getRemoteTransition(), null /* displayChange */);
+ startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ }
+ return true;
+ }
+
+ void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
+ ActivityRecord r, Task task, Task rootTask) {
final ActivityMetricsLogger.LaunchingState launchingState =
mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
- final Task task = r.getTask();
mService.deferWindowLayout();
try {
- task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
- 0 /* flags */, task, task /* readyGroupRef */,
- options.getRemoteTransition(), null /* displayChange */);
r.mTransitionController.setTransientLaunch(r,
TaskDisplayArea.getRootTaskAbove(rootTask));
task.moveToFront("startExistingRecents");
@@ -585,7 +611,6 @@ public class ActivityStartController {
task.mInResumeTopActivity = false;
mService.continueWindowLayout();
}
- return true;
}
void registerRemoteAnimationForNextActivityStart(String packageName,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index e7b62b0d66d3..4d970f057122 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -313,6 +313,7 @@ public abstract class ActivityTaskManagerInternal {
public abstract void onProcessRemoved(String name, int uid);
public abstract void onCleanUpApplicationRecord(WindowProcessController proc);
public abstract int getTopProcessState();
+ public abstract boolean useTopSchedGroupForTopProcess();
public abstract void clearHeavyWeightProcessIfEquals(WindowProcessController proc);
public abstract void finishHeavyWeightApp();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4ecc62923c5d..9f2791098418 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5759,17 +5759,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
public int getTopProcessState() {
- final int topState = mTopProcessState;
- if (mDemoteTopAppReasons != 0 && topState == ActivityManager.PROCESS_STATE_TOP) {
- // There may be a more important UI/animation than the top app.
- return ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
- }
if (mRetainPowerModeAndTopProcessState) {
// There is a launching app while device may be sleeping, force the top state so
// the launching process can have top-app scheduling group.
return ActivityManager.PROCESS_STATE_TOP;
}
- return topState;
+ return mTopProcessState;
+ }
+
+ @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+ @Override
+ public boolean useTopSchedGroupForTopProcess() {
+ // If it is non-zero, there may be a more important UI/animation than the top app.
+ return mDemoteTopAppReasons == 0;
}
@HotPath(caller = HotPath.PROCESS_CHANGE)
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index e09b80e196b1..86915da75467 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -28,6 +28,7 @@ import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
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.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
@@ -60,6 +61,7 @@ public class BroadcastQueueModernImplTest {
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
@Mock ActivityManagerService mAms;
+ @Mock ProcessRecord mProcess;
@Mock BroadcastProcessQueue mQueue1;
@Mock BroadcastProcessQueue mQueue2;
@@ -137,7 +139,7 @@ public class BroadcastQueueModernImplTest {
private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
List receivers, boolean ordered) {
- return new BroadcastRecord(mImpl, intent, null, PACKAGE_RED, null, 21, 42, false, null,
+ return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
null, null, null, AppOpsManager.OP_NONE, options, receivers, null,
Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
false, null, false, null);
@@ -250,10 +252,11 @@ public class BroadcastQueueModernImplTest {
*/
@Test
public void testRunnableAt_Empty() {
- BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
assertFalse(queue.isRunnable());
assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
}
/**
@@ -262,18 +265,19 @@ public class BroadcastQueueModernImplTest {
*/
@Test
public void testRunnableAt_Normal() {
- BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueBroadcast(airplaneRecord, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
queue.setProcessCached(false);
final long notCachedRunnableAt = queue.getRunnableAt();
queue.setProcessCached(true);
final long cachedRunnableAt = queue.getRunnableAt();
assertTrue(cachedRunnableAt > notCachedRunnableAt);
+ assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
}
/**
@@ -282,21 +286,71 @@ public class BroadcastQueueModernImplTest {
*/
@Test
public void testRunnableAt_Foreground() {
- BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueBroadcast(airplaneRecord, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
queue.setProcessCached(false);
assertTrue(queue.isRunnable());
assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+ assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
queue.setProcessCached(true);
assertTrue(queue.isRunnable());
assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
+ assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+ }
+
+ /**
+ * Queue with ordered broadcast is runnable only once we've made enough
+ * progress on earlier blocking items.
+ */
+ @Test
+ public void testRunnableAt_Ordered() {
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+
+ assertFalse(queue.isRunnable());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
+
+ // Bumping past barrier makes us now runnable
+ airplaneRecord.terminalCount++;
+ queue.invalidateRunnableAt();
+ assertTrue(queue.isRunnable());
+ assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
+ }
+
+ /**
+ * Queue with too many pending broadcasts is runnable.
+ */
+ @Test
+ public void testRunnableAt_Huge() {
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+
+ mConstants.MAX_PENDING_BROADCASTS = 128;
+ queue.invalidateRunnableAt();
+ assertTrue(queue.getRunnableAt() > airplaneRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+
+ mConstants.MAX_PENDING_BROADCASTS = 1;
+ queue.invalidateRunnableAt();
+ assertTrue(queue.getRunnableAt() == airplaneRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason());
}
/**
@@ -321,6 +375,9 @@ public class BroadcastQueueModernImplTest {
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+ // While we're here, give our health check some test coverage
+ mImpl.checkHealthLocked();
+
// Marching through the queue we should only have one SCREEN_OFF
// broadcast, since that's the last state we dispatched
final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index f89d37807cdd..076fce959a77 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -18,8 +18,13 @@ package com.android.server.am;
import static android.os.UserHandle.USER_SYSTEM;
+import static com.android.server.am.BroadcastProcessQueue.reasonToString;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -65,14 +70,18 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerExemptionManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.DropBoxManagerInternal;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.Injector;
import com.android.server.appop.AppOpsService;
@@ -98,7 +107,9 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -109,6 +120,7 @@ import java.util.function.UnaryOperator;
*/
@MediumTest
@RunWith(Parameterized.class)
+@SuppressWarnings("GuardedBy")
public class BroadcastQueueTest {
private static final String TAG = "BroadcastQueueTest";
@@ -132,6 +144,8 @@ public class BroadcastQueueTest {
@Mock
private ProcessList mProcessList;
@Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
private PackageManagerInternal mPackageManagerInt;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInt;
@@ -155,6 +169,11 @@ public class BroadcastQueueTest {
*/
private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
+ /**
+ * Collection of scheduled broadcasts, in the order they were dispatched.
+ */
+ private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
+
@Parameters(name = "impl={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
@@ -174,6 +193,8 @@ public class BroadcastQueueTest {
mHandlerThread.start();
mNextPid = new AtomicInteger(100);
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
@@ -228,14 +249,17 @@ public class BroadcastQueueTest {
constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
+ // Ignored
return false;
}
public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) {
+ // Ignored
return false;
}
};
- final BroadcastHistory emptyHistory = new BroadcastHistory() {
+ final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+ // Ignored
}
};
@@ -358,6 +382,8 @@ public class BroadcastQueueTest {
UserHandle.getUserId(r.info.uid), receiver);
mRegisteredReceivers.put(r.getPid(), receiverList);
+ doReturn(42L).when(r).getCpuDelayTime();
+
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting killLocked() for "
+ Arrays.toString(invocation.getArguments()));
@@ -372,11 +398,13 @@ public class BroadcastQueueTest {
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
+ Arrays.toString(invocation.getArguments()));
+ final Intent intent = invocation.getArgument(0);
final Bundle extras = invocation.getArgument(5);
+ mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
if (!wedge) {
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
- assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
- != ProcessList.SCHED_GROUP_UNDEFINED);
+ assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
+ mQueue.getPreferredSchedulingGroupLocked(r));
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
@@ -391,12 +419,14 @@ public class BroadcastQueueTest {
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
+ Arrays.toString(invocation.getArguments()));
+ final Intent intent = invocation.getArgument(1);
final Bundle extras = invocation.getArgument(4);
final boolean ordered = invocation.getArgument(5);
+ mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent));
if (!wedge && ordered) {
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
- assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
- != ProcessList.SCHED_GROUP_UNDEFINED);
+ assertNotEquals(ProcessList.SCHED_GROUP_UNDEFINED,
+ mQueue.getPreferredSchedulingGroupLocked(r));
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
@@ -411,8 +441,8 @@ public class BroadcastQueueTest {
return r;
}
- static ResolveInfo makeManifestReceiver(String packageName, String name) {
- return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
+ private Pair<Integer, String> makeScheduledBroadcast(ProcessRecord app, Intent intent) {
+ return Pair.create(app.getPid(), intent.getAction());
}
static ApplicationInfo makeApplicationInfo(String packageName) {
@@ -427,6 +457,10 @@ public class BroadcastQueueTest {
return ai;
}
+ static ResolveInfo makeManifestReceiver(String packageName, String name) {
+ return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
+ }
+
static ResolveInfo makeManifestReceiver(String packageName, String name, int userId) {
return makeManifestReceiver(packageName, packageName, name, userId);
}
@@ -443,8 +477,13 @@ public class BroadcastQueueTest {
}
private BroadcastFilter makeRegisteredReceiver(ProcessRecord app) {
+ return makeRegisteredReceiver(app, 0);
+ }
+
+ private BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid());
final IntentFilter filter = new IntentFilter();
+ filter.setPriority(priority);
final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
receiverList.app.info.packageName, null, null, null, receiverList.uid,
receiverList.userId, false, false, true);
@@ -486,6 +525,32 @@ public class BroadcastQueueTest {
false, null);
}
+ private static Map<String, Object> asMap(Bundle bundle) {
+ final Map<String, Object> map = new HashMap<>();
+ if (bundle != null) {
+ for (String key : bundle.keySet()) {
+ map.put(key, bundle.get(key));
+ }
+ }
+ return map;
+ }
+
+ private ArgumentMatcher<Intent> componentEquals(String packageName, String className) {
+ return (test) -> {
+ final ComponentName cn = test.getComponent();
+ return (cn != null)
+ && Objects.equals(cn.getPackageName(), packageName)
+ && Objects.equals(cn.getClassName(), className);
+ };
+ }
+
+ private ArgumentMatcher<Intent> filterAndExtrasEquals(Intent intent) {
+ return (test) -> {
+ return intent.filterEquals(test)
+ && Objects.equals(asMap(intent.getExtras()), asMap(test.getExtras()));
+ };
+ }
+
private ArgumentMatcher<Intent> filterEquals(Intent intent) {
return (test) -> {
return intent.filterEquals(test);
@@ -587,12 +652,33 @@ public class BroadcastQueueTest {
return UserHandle.getUid(userId, getUidForPackage(packageName));
}
+ /**
+ * Baseline verification of common debugging infrastructure, mostly to make
+ * sure it doesn't crash.
+ */
@Test
- public void testDump() throws Exception {
+ public void testDebugging() throws Exception {
// To maximize test coverage, dump current state; we're not worried
// about the actual output, just that we don't crash
+ mQueue.dumpDebug(new ProtoOutputStream(),
+ ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
- null, 0, true, null, false);
+ null, 0, true, true, true, null, false);
+ mQueue.dumpToDropBoxLocked(TAG);
+
+ BroadcastQueue.logv(TAG);
+ BroadcastQueue.logv(TAG, null);
+ BroadcastQueue.logv(TAG, new PrintWriter(new ByteArrayOutputStream()));
+
+ BroadcastQueue.logw(TAG);
+
+ assertNotNull(mQueue.toString());
+ assertNotNull(mQueue.describeStateLocked());
+
+ for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {
+ assertNotNull(deliveryStateToString(i));
+ assertNotNull(reasonToString(i));
+ }
}
/**
@@ -912,7 +998,8 @@ public class BroadcastQueueTest {
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
try (SyncBarrier b = new SyncBarrier()) {
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
- List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
+ List.of(makeRegisteredReceiver(receiverApp),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
@@ -929,11 +1016,14 @@ public class BroadcastQueueTest {
// To maximize test coverage, dump current state; we're not worried
// about the actual output, just that we don't crash
+ mQueue.dumpDebug(new ProtoOutputStream(),
+ ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()),
- null, 0, true, null, false);
+ null, 0, true, true, true, null, false);
}
waitForIdle();
+ verifyScheduleRegisteredReceiver(receiverApp, airplane);
verifyScheduleReceiver(times(1), receiverApp, airplane,
new ComponentName(PACKAGE_GREEN, CLASS_RED));
verifyScheduleReceiver(never(), receiverApp, airplane,
@@ -1240,4 +1330,158 @@ public class BroadcastQueueTest {
verifyScheduleReceiver(times(1), systemApp, airplane, USER_SYSTEM);
verifyScheduleReceiver(times(1), systemApp, airplane, USER_GUEST);
}
+
+ /**
+ * Verify that when dispatching we respect tranches of priority.
+ */
+ @Test
+ public void testPriority() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ // Enqueue a normal broadcast that will go to several processes, and
+ // then enqueue a foreground broadcast that risks reordering
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ try (SyncBarrier b = new SyncBarrier()) {
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 10),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeRegisteredReceiver(receiverYellowApp, -10))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp))));
+ }
+
+ waitForIdle();
+
+ // Ignore the final foreground broadcast
+ mScheduledBroadcasts.remove(makeScheduledBroadcast(receiverBlueApp, airplane));
+ assertEquals(5, mScheduledBroadcasts.size());
+
+ // We're only concerned about enforcing ordering between tranches;
+ // within a tranche we're okay with reordering
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverGreenApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0)));
+ }
+
+ /**
+ * Verify that we handle replacing a pending broadcast.
+ */
+ @Test
+ public void testReplacePending() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final IApplicationThread callerThread = callerApp.getThread();
+
+ final Intent timezoneFirst = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ timezoneFirst.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ timezoneFirst.putExtra(Intent.EXTRA_TIMEZONE, "GMT+5");
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final Intent timezoneSecond = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ timezoneSecond.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ timezoneSecond.putExtra(Intent.EXTRA_TIMEZONE, "GMT-5");
+
+ final IIntentReceiver resultToFirst = mock(IIntentReceiver.class);
+ final IIntentReceiver resultToSecond = mock(IIntentReceiver.class);
+
+ try (SyncBarrier b = new SyncBarrier()) {
+ enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+ resultToFirst, null));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
+ enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+ resultToSecond, null));
+ }
+
+ waitForIdle();
+ final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE)).getThread();
+ final InOrder inOrder = inOrder(callerThread, blueThread);
+
+ // First broadcast is canceled
+ inOrder.verify(callerThread).scheduleRegisteredReceiver(any(),
+ argThat(filterAndExtrasEquals(timezoneFirst)), eq(Activity.RESULT_CANCELED), any(),
+ any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+
+ // We deliver second broadcast to app
+ timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
+ inOrder.verify(blueThread).scheduleReceiver(
+ argThat(filterAndExtrasEquals(timezoneSecond)),
+ any(), any(), anyInt(), any(), any(), eq(true), anyInt(), anyInt());
+
+ // Second broadcast is finished
+ timezoneSecond.setComponent(null);
+ inOrder.verify(callerThread).scheduleRegisteredReceiver(any(),
+ argThat(filterAndExtrasEquals(timezoneSecond)), eq(Activity.RESULT_OK), any(),
+ any(), eq(false), anyBoolean(), eq(UserHandle.USER_SYSTEM), anyInt());
+
+ // Since we "replaced" the first broadcast in its original position,
+ // only now do we see the airplane broadcast
+ airplane.setClassName(PACKAGE_BLUE, CLASS_RED);
+ inOrder.verify(blueThread).scheduleReceiver(
+ argThat(filterEquals(airplane)),
+ any(), any(), anyInt(), any(), any(), eq(false), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testIdleAndBarrier() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final long beforeFirst;
+ final long afterFirst;
+ final long afterSecond;
+
+ beforeFirst = SystemClock.uptimeMillis() - 10;
+ assertTrue(mQueue.isIdleLocked());
+ assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+
+ try (SyncBarrier b = new SyncBarrier()) {
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp))));
+ afterFirst = SystemClock.uptimeMillis();
+
+ assertFalse(mQueue.isIdleLocked());
+ assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+ assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp))));
+ afterSecond = SystemClock.uptimeMillis() + 10;
+
+ assertFalse(mQueue.isIdleLocked());
+ assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+ assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+ assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
+ }
+
+ mQueue.waitForBarrier(null);
+ assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
+
+ mQueue.waitForIdle(null);
+ assertTrue(mQueue.isIdleLocked());
+ assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+ assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
+ assertTrue(mQueue.isBeyondBarrierLocked(afterSecond));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6be37302ea..161dfa0566fd 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,11 @@ import static android.content.Intent.ACTION_TIME_CHANGED;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
+import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -37,21 +39,26 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
+import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
+import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
+
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.BiFunction;
/**
* Test class for {@link BroadcastRecord}.
@@ -60,7 +67,7 @@ import java.util.List;
* atest FrameworksServicesTests:BroadcastRecordTest
*/
@SmallTest
-@Presubmit
+@RunWith(MockitoJUnitRunner.class)
public class BroadcastRecordTest {
private static final int USER0 = UserHandle.USER_SYSTEM;
@@ -73,8 +80,9 @@ public class BroadcastRecordTest {
private static final String[] PACKAGE_LIST = new String[] {PACKAGE1, PACKAGE2, PACKAGE3,
PACKAGE4};
- @Mock
- ActivityManagerInternal mActivityManagerInternal;
+ @Mock ActivityManagerInternal mActivityManagerInternal;
+ @Mock BroadcastQueue mQueue;
+ @Mock ProcessRecord mProcess;
@Before
public void setUp() throws Exception {
@@ -82,6 +90,91 @@ public class BroadcastRecordTest {
}
@Test
+ public void testIsPrioritized_Empty() {
+ assertFalse(isPrioritized(List.of()));
+ }
+
+ @Test
+ public void testIsPrioritized_Single() {
+ assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
+ assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
+ assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+ }
+
+ @Test
+ public void testIsPrioritized_No() {
+ assertFalse(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+ assertFalse(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ }
+
+ @Test
+ public void testIsPrioritized_Yes() {
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ }
+
+ @Test
+ public void testGetReceiverIntent_Simple() {
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastRecord r = createBroadcastRecord(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent);
+ final Intent actual = r.getReceiverIntent(r.receivers.get(0));
+ assertEquals(PACKAGE1, actual.getComponent().getPackageName());
+ assertNull(r.intent.getComponent());
+ }
+
+ @Test
+ public void testGetReceiverIntent_Filtered_Partial() {
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra(Intent.EXTRA_INDEX, 42);
+ final BroadcastRecord r = createBroadcastRecord(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
+ (uid, extras) -> {
+ return Bundle.EMPTY;
+ });
+ final Intent actual = r.getReceiverIntent(r.receivers.get(0));
+ assertEquals(PACKAGE1, actual.getComponent().getPackageName());
+ assertEquals(-1, actual.getIntExtra(Intent.EXTRA_INDEX, -1));
+ assertNull(r.intent.getComponent());
+ assertEquals(42, r.intent.getIntExtra(Intent.EXTRA_INDEX, -1));
+ }
+
+ @Test
+ public void testGetReceiverIntent_Filtered_Complete() {
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra(Intent.EXTRA_INDEX, 42);
+ final BroadcastRecord r = createBroadcastRecord(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1))), UserHandle.USER_ALL, intent,
+ (uid, extras) -> {
+ return null;
+ });
+ final Intent actual = r.getReceiverIntent(r.receivers.get(0));
+ assertNull(actual);
+ assertNull(r.intent.getComponent());
+ assertEquals(42, r.intent.getIntExtra(Intent.EXTRA_INDEX, -1));
+ }
+
+ @Test
+ public void testIsReceiverEquals() {
+ final ResolveInfo info = createResolveInfo(PACKAGE1, getAppId(1));
+ assertTrue(isReceiverEquals(info, info));
+ assertTrue(isReceiverEquals(info, createResolveInfo(PACKAGE1, getAppId(1))));
+ assertFalse(isReceiverEquals(info, createResolveInfo(PACKAGE2, getAppId(2))));
+ }
+
+ @Test
public void testCleanupDisabledPackageReceivers() {
final int user0 = UserHandle.USER_SYSTEM;
final int user1 = user0 + 1;
@@ -360,13 +453,20 @@ public class BroadcastRecordTest {
}
private static ResolveInfo createResolveInfo(String packageName, int uid) {
+ return createResolveInfo(packageName, uid, 0);
+ }
+
+ private static ResolveInfo createResolveInfo(String packageName, int uid, int priority) {
final ResolveInfo resolveInfo = new ResolveInfo();
final ActivityInfo activityInfo = new ActivityInfo();
final ApplicationInfo appInfo = new ApplicationInfo();
appInfo.packageName = packageName;
appInfo.uid = uid;
activityInfo.applicationInfo = appInfo;
+ activityInfo.packageName = packageName;
+ activityInfo.name = packageName + ".MyReceiver";
resolveInfo.activityInfo = activityInfo;
+ resolveInfo.priority = priority;
return resolveInfo;
}
@@ -402,13 +502,18 @@ public class BroadcastRecordTest {
return excludedList;
}
- private static BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+ private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent) {
+ return createBroadcastRecord(receivers, userId, intent, null);
+ }
+
+ private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+ Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
return new BroadcastRecord(
- null /* queue */,
+ mQueue /* queue */,
intent,
- null /* callerApp */,
- null /* callerPackage */,
+ mProcess /* callerApp */,
+ PACKAGE1 /* callerPackage */,
null /* callerFeatureId */,
0 /* callingPid */,
0 /* callingUid */,
@@ -431,7 +536,7 @@ public class BroadcastRecordTest {
false /* allowBackgroundActivityStarts */,
null /* activityStartsToken */,
false /* timeoutExempt */,
- null /* filterExtrasForReceiver */);
+ filterExtrasForReceiver);
}
private static int getAppId(int i) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index e165f063e2fa..464fee2bfc11 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -24,7 +24,6 @@ import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEA
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
-import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_SOFTWARE_CURSOR;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
@@ -53,7 +52,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
-import com.android.server.accessibility.cursor.SoftwareCursorGestureHandler;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
@@ -85,7 +83,6 @@ public class AccessibilityInputFilterTest {
private final ArrayList<Display> mDisplayList = new ArrayList<>();
private final int mFeatures = FLAG_FEATURE_AUTOCLICK
| FLAG_FEATURE_TOUCH_EXPLORATION
- | FLAG_FEATURE_SOFTWARE_CURSOR
| FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
| FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
| FLAG_FEATURE_INJECT_MOTION_EVENTS
@@ -94,8 +91,8 @@ public class AccessibilityInputFilterTest {
// The expected order of EventStreamTransformations.
private final Class[] mExpectedEventHandlerTypes =
{KeyboardInterceptor.class, MotionEventInjector.class,
- SoftwareCursorGestureHandler.class, FullScreenMagnificationGestureHandler.class,
- TouchExplorer.class, AutoclickController.class, AccessibilityInputFilter.class};
+ FullScreenMagnificationGestureHandler.class, TouchExplorer.class,
+ AutoclickController.class, AccessibilityInputFilter.class};
@Mock private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
@Mock private WindowManagerInternal mMockWindowManagerService;
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 9475d0f0c0aa..ed0336a5a4ea 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -149,7 +149,6 @@ public class AccessibilityUserStateTest {
mUserState.setTargetAssignedToAccessibilityButton(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setDisplayMagnificationEnabledLocked(true);
- mUserState.setSoftwareCursorEnabledLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -172,7 +171,6 @@ public class AccessibilityUserStateTest {
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
- assertFalse(mUserState.isSoftwareCursorEnabledLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 4100ff1ea977..3f5d33167ad3 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.BatteryStats.CpuUsageDetails;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.MeasuredEnergyDetails;
import android.os.Parcel;
@@ -315,8 +316,7 @@ public class BatteryStatsHistoryTest {
String dump = toString(item, /* checkin */ false);
assertThat(dump).contains("+200ms");
- assertThat(dump).contains("ext=E");
- assertThat(dump).contains("Energy: A=0 B/0=100 B/1=200");
+ assertThat(dump).contains("ext=energy:A=0 B/0=100 B/1=200");
assertThat(dump).doesNotContain("C=");
String checkin = toString(item, /* checkin */ true);
@@ -325,6 +325,55 @@ public class BatteryStatsHistoryTest {
assertThat(checkin).doesNotContain("C=");
}
+ @Test
+ public void cpuUsageDetails() {
+ mHistory.forceRecordAllHistory();
+ mHistory.startRecordingHistory(0, 0, /* reset */ true);
+ mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
+ 1234);
+
+ CpuUsageDetails details = new CpuUsageDetails();
+ details.cpuBracketDescriptions = new String[] {"low", "Med", "HIGH"};
+ details.uid = 10123;
+ details.cpuUsageMs = new long[] { 100, 200, 300};
+ mHistory.recordCpuUsage(200, 200, details);
+
+ details.uid = 10321;
+ details.cpuUsageMs = new long[] { 400, 500, 600};
+ mHistory.recordCpuUsage(300, 300, details);
+
+ BatteryStatsHistoryIterator iterator = mHistory.iterate();
+ BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+ assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+
+ assertThat(iterator.next(item)).isTrue();
+
+ String dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+200ms");
+ assertThat(dump).contains("ext=cpu:u0a123: 100, 200, 300");
+ assertThat(dump).contains("ext=cpu-bracket:0:low");
+ assertThat(dump).contains("ext=cpu-bracket:1:Med");
+ assertThat(dump).contains("ext=cpu-bracket:2:HIGH");
+
+ String checkin = toString(item, /* checkin */ true);
+ assertThat(checkin).contains("XB,3,0,low");
+ assertThat(checkin).contains("XB,3,1,Med");
+ assertThat(checkin).contains("XB,3,2,HIGH");
+ assertThat(checkin).contains("XC,10123,100,200,300");
+
+ assertThat(iterator.next(item)).isTrue();
+
+ dump = toString(item, /* checkin */ false);
+ assertThat(dump).contains("+300ms");
+ assertThat(dump).contains("ext=cpu:u0a321: 400, 500, 600");
+ // Power bracket descriptions are written only once
+ assertThat(dump).doesNotContain("ext=cpu-bracket");
+
+ checkin = toString(item, /* checkin */ true);
+ assertThat(checkin).doesNotContain("XB");
+ assertThat(checkin).contains("XC,10321,400,500,600");
+ }
+
private String toString(BatteryStats.HistoryItem item, boolean checkin) {
BatteryStats.HistoryPrinter printer = new BatteryStats.HistoryPrinter();
StringWriter writer = new StringWriter();
@@ -333,4 +382,68 @@ public class BatteryStatsHistoryTest {
pw.flush();
return writer.toString();
}
+
+ @Test
+ public void testVarintParceler() {
+ long[] values = {
+ 0,
+ 1,
+ 42,
+ 0x1234,
+ 0x10000000,
+ 0x12345678,
+ 0x7fffffff,
+ 0xffffffffL,
+ 0x100000000000L,
+ 0x123456789012L,
+ 0x1000000000000000L,
+ 0x1234567890123456L,
+ 0x7fffffffffffffffL,
+ 0xffffffffffffffffL};
+
+ // Parcel subarrays of different lengths and assert the size of the resulting parcel
+ testVarintParceler(Arrays.copyOfRange(values, 0, 1), 4); // v. 8
+ testVarintParceler(Arrays.copyOfRange(values, 0, 2), 4); // v. 16
+ testVarintParceler(Arrays.copyOfRange(values, 0, 3), 4); // v. 24
+ testVarintParceler(Arrays.copyOfRange(values, 0, 4), 8); // v. 32
+ testVarintParceler(Arrays.copyOfRange(values, 0, 5), 12); // v. 40
+ testVarintParceler(Arrays.copyOfRange(values, 0, 6), 16); // v. 48
+ testVarintParceler(Arrays.copyOfRange(values, 0, 7), 20); // v. 56
+ testVarintParceler(Arrays.copyOfRange(values, 0, 8), 28); // v. 64
+ testVarintParceler(Arrays.copyOfRange(values, 0, 9), 32); // v. 72
+ testVarintParceler(Arrays.copyOfRange(values, 0, 10), 40); // v. 80
+ testVarintParceler(Arrays.copyOfRange(values, 0, 11), 48); // v. 88
+ testVarintParceler(Arrays.copyOfRange(values, 0, 12), 60); // v. 96
+ testVarintParceler(Arrays.copyOfRange(values, 0, 13), 68); // v. 104
+ testVarintParceler(Arrays.copyOfRange(values, 0, 14), 76); // v. 112
+ }
+
+ private void testVarintParceler(long[] values, int expectedLength) {
+ BatteryStatsHistory.VarintParceler parceler = new BatteryStatsHistory.VarintParceler();
+ Parcel parcel = Parcel.obtain();
+ parcel.writeString("begin");
+ int pos = parcel.dataPosition();
+ parceler.writeLongArray(parcel, values);
+ int length = parcel.dataPosition() - pos;
+ parcel.writeString("end");
+
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+
+ parcel = Parcel.obtain();
+ parcel.unmarshall(bytes, 0, bytes.length);
+ parcel.setDataPosition(0);
+
+ assertThat(parcel.readString()).isEqualTo("begin");
+
+ long[] result = new long[values.length];
+ parceler.readLongArray(parcel, result);
+
+ assertThat(result).isEqualTo(values);
+ assertThat(length).isEqualTo(expectedLength);
+
+ assertThat(parcel.readString()).isEqualTo("end");
+
+ parcel.recycle();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 54032696e5e6..c6a7fbcfb454 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -28,7 +28,6 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -117,6 +116,7 @@ public class BatteryStatsImplTest {
// Initialize time-in-freq counters
mMockClock.realtime = 1000;
+ mMockClock.uptime = 1000;
for (int i = 0; i < testUids.length; ++i) {
mockKernelSingleUidTimeReader(testUids[i], new long[5]);
mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
@@ -142,11 +142,12 @@ public class BatteryStatsImplTest {
};
mMockClock.realtime += 1000;
+ mMockClock.uptime += 1000;
for (int i = 0; i < testUids.length; ++i) {
mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
- mMockClock.realtime);
+ mMockClock.realtime, mMockClock.uptime);
}
for (int i = 0; i < testUids.length; ++i) {
@@ -171,6 +172,7 @@ public class BatteryStatsImplTest {
};
mMockClock.realtime += 1000;
+ mMockClock.uptime += 1000;
for (int i = 0; i < testUids.length; ++i) {
long[] newCpuTimes = new long[cpuTimes[i].length];
@@ -179,7 +181,7 @@ public class BatteryStatsImplTest {
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
- mMockClock.realtime);
+ mMockClock.realtime, mMockClock.uptime);
}
for (int i = 0; i < testUids.length; ++i) {
@@ -200,7 +202,7 @@ public class BatteryStatsImplTest {
}
// Validate the on-battery-screen-off counter
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, mMockClock.uptime * 1000,
mMockClock.realtime * 1000);
final long[][] delta2 = {
@@ -211,6 +213,7 @@ public class BatteryStatsImplTest {
};
mMockClock.realtime += 1000;
+ mMockClock.uptime += 1000;
for (int i = 0; i < testUids.length; ++i) {
long[] newCpuTimes = new long[cpuTimes[i].length];
@@ -219,7 +222,7 @@ public class BatteryStatsImplTest {
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
- mMockClock.realtime);
+ mMockClock.realtime, mMockClock.uptime);
}
for (int i = 0; i < testUids.length; ++i) {
@@ -253,6 +256,7 @@ public class BatteryStatsImplTest {
};
mMockClock.realtime += 1000;
+ mMockClock.uptime += 1000;
final int parentUid = testUids[1];
final int childUid = 99099;
@@ -267,7 +271,7 @@ public class BatteryStatsImplTest {
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
- mMockClock.realtime);
+ mMockClock.realtime, mMockClock.uptime);
}
for (int i = 0; i < testUids.length; ++i) {
@@ -302,6 +306,7 @@ public class BatteryStatsImplTest {
mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
mMockClock.realtime = 1000;
+ mMockClock.uptime = 1000;
final int[] testUids = {10032, 10048, 10145, 10139};
final int[] testProcStates = {
@@ -316,7 +321,7 @@ public class BatteryStatsImplTest {
uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
- mMockClock.elapsedRealtime());
+ mMockClock.elapsedRealtime(), mMockClock.uptime);
}
final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
@@ -341,6 +346,7 @@ public class BatteryStatsImplTest {
}
mMockClock.realtime += 1000;
+ mMockClock.uptime += 1000;
mBatteryStatsImpl.updateCpuTimesForAllUids();
@@ -394,27 +400,6 @@ public class BatteryStatsImplTest {
}
@Test
- public void testAddCpuTimes() {
- long[] timesA = null;
- long[] timesB = null;
- assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
- timesA = new long[] {34, 23, 45, 24};
- assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
- timesB = timesA;
- timesA = null;
- assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
-
- final long[] expected = {434, 6784, 34987, 9984};
- timesA = new long[timesB.length];
- for (int i = 0; i < timesA.length; ++i) {
- timesA[i] = expected[i] - timesB[i];
- }
- assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
- }
-
- @Test
public void testMulticastWakelockAcqRel() {
final int testUid = 10032;
final int acquireTimeMs = 1000;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a1d6a5006fef..a2b4cb867d6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -114,6 +114,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
assertTrue(recentActivity.mVisibleRequested);
assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
mAtm.mDemoteTopAppReasons);
+ assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
// Simulate the animation is cancelled without changing the stack order.
recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
diff --git a/tools/preload/loadclass/LoadClass.java b/tools/preload/loadclass/LoadClass.java
index a71b6a8b145e..3f6658ab8c65 100644
--- a/tools/preload/loadclass/LoadClass.java
+++ b/tools/preload/loadclass/LoadClass.java
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import android.util.Log;
import android.os.Debug;
+import android.util.Log;
/**
* Loads a class, runs the garbage collector, and prints showmap output.
@@ -28,7 +28,7 @@ class LoadClass {
System.loadLibrary("android_runtime");
if (registerNatives() < 0) {
- throw new RuntimeException("Error registering natives.");
+ throw new RuntimeException("Error registering natives.");
}
Debug.startAllocCounting();
@@ -46,7 +46,7 @@ class LoadClass {
}
}
- System.gc();
+ Runtime.getRuntime().gc();
int allocCount = Debug.getGlobalAllocCount();
int allocSize = Debug.getGlobalAllocSize();
@@ -73,7 +73,7 @@ class LoadClass {
response.append(',').append(freedCount);
response.append(',').append(freedSize);
response.append(',').append(nativeHeapSize);
-
+
System.out.println(response.toString());
}