summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--StubLibraries.bp6
-rw-r--r--apex/extservices/Android.bp2
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java13
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java41
-rw-r--r--cmds/statsd/src/atoms.proto45
-rw-r--r--cmds/statsd/src/shell/ShellSubscriber.cpp190
-rw-r--r--cmds/statsd/src/shell/ShellSubscriber.h36
-rw-r--r--cmds/statsd/tests/shell/ShellSubscriber_test.cpp46
-rw-r--r--core/java/android/os/UserManager.java5
-rw-r--r--core/java/android/permission/PermissionControllerManager.java37
-rw-r--r--core/java/android/permission/PermissionControllerService.java7
-rw-r--r--core/java/android/provider/DocumentsProvider.java2
-rw-r--r--core/java/android/service/autofill/IInlineSuggestionUi.aidl29
-rw-r--r--core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl6
-rw-r--r--core/java/android/service/autofill/ISurfacePackageResultCallback.aidl28
-rw-r--r--core/java/android/service/autofill/InlineSuggestionRenderService.java121
-rw-r--r--core/java/android/service/dreams/DreamService.java8
-rw-r--r--core/java/android/view/InsetsController.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/view/ViewRootInsetsControllerHost.java6
-rw-r--r--core/java/android/view/inputmethod/InlineSuggestion.java270
-rw-r--r--core/java/android/widget/inline/InlineContentView.java137
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java67
-rw-r--r--core/java/com/android/internal/app/ChooserGridLayoutManager.java70
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java12
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java16
-rw-r--r--core/java/com/android/internal/app/chooser/SelectableTargetInfo.java5
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl2
-rw-r--r--core/java/com/android/internal/view/inline/IInlineContentProvider.aidl2
-rw-r--r--core/java/com/android/internal/widget/GridLayoutManager.java6
-rw-r--r--core/java/com/android/internal/widget/ResolverDrawerLayout.java13
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp29
-rw-r--r--core/res/res/layout/chooser_list_per_profile.xml4
-rw-r--r--core/res/res/layout/resolver_list.xml1
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTests.java15
-rw-r--r--core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java4
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java40
-rw-r--r--media/java/android/media/AudioMetadata.java34
-rw-r--r--media/java/android/media/MediaRouter2Manager.java82
-rw-r--r--media/java/android/media/RoutingSessionInfo.java28
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java23
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java15
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java17
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java165
-rw-r--r--packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml43
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java45
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java76
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java15
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java424
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java25
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java11
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java47
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java22
-rw-r--r--packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml2
-rw-r--r--packages/SystemUI/res/layout/bubble_overflow_view.xml2
-rw-r--r--packages/SystemUI/res/layout/global_screenshot.xml2
-rw-r--r--packages/SystemUI/res/layout/screen_record_dialog.xml28
-rw-r--r--packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml37
-rw-r--r--packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml35
-rw-r--r--packages/SystemUI/res/values-night/colors.xml1
-rw-r--r--packages/SystemUI/res/values-night/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/colors.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/ids.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java242
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java290
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java248
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAudioSource.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingMuxer.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java3
-rw-r--r--packages/Tethering/Android.bp3
-rw-r--r--packages/Tethering/apex/Android.bp9
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/Tethering.java8
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java14
-rw-r--r--packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java40
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java6
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java4
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java2
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java140
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java102
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java291
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java125
-rw-r--r--services/core/java/com/android/server/RescueParty.java27
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java46
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java13
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java3
-rw-r--r--services/core/java/com/android/server/location/LocationRequestStatistics.java22
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java19
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java59
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java9
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java50
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java64
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java176
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java36
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java1
-rw-r--r--services/core/java/com/android/server/pm/UserSystemPackageInstaller.java171
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java7
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java10
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java8
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java72
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java62
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java21
-rw-r--r--services/core/java/com/android/server/wm/Task.java27
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java40
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java22
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp62
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java10
-rw-r--r--services/java/com/android/server/SystemServer.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java64
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java3
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java49
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java2
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java1
-rw-r--r--telephony/common/com/android/internal/telephony/CellBroadcastUtils.java65
156 files changed, 4622 insertions, 1423 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 91efb05b50f4..50524998d416 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -48,7 +48,6 @@ stubs_defaults {
":opt-telephony-srcs",
":opt-net-voip-srcs",
":art-module-public-api-stubs-source",
- ":conscrypt.module.public.api.stubs.source",
":android_icu4j_public_api_files",
],
// TODO(b/147699819): remove below aidl includes.
@@ -69,7 +68,10 @@ stubs_defaults {
stubs_defaults {
name: "metalava-full-api-stubs-default",
defaults: ["metalava-base-api-stubs-default"],
- srcs: [":framework-updatable-sources"],
+ srcs: [
+ ":conscrypt.module.public.api.stubs.source",
+ ":framework-updatable-sources",
+ ],
sdk_version: "core_platform",
}
diff --git a/apex/extservices/Android.bp b/apex/extservices/Android.bp
index 68350afdac85..0c6c4c23dce1 100644
--- a/apex/extservices/Android.bp
+++ b/apex/extservices/Android.bp
@@ -21,7 +21,7 @@ apex {
apex_defaults {
name: "com.android.extservices-defaults",
updatable: true,
- min_sdk_version: "R",
+ min_sdk_version: "current",
key: "com.android.extservices.key",
certificate: ":com.android.extservices.certificate",
apps: ["ExtServices"],
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 6d9e3eddf616..887d82c6413f 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -45,6 +45,14 @@ public interface AppStandbyInternal {
boolean idle, int bucket, int reason);
/**
+ * Callback to inform listeners that the parole state has changed. This means apps are
+ * allowed to do work even if they're idle or in a low bucket.
+ */
+ public void onParoleStateChanged(boolean isParoleOn) {
+ // No-op by default
+ }
+
+ /**
* Optional callback to inform the listener that the app has transitioned into
* an active state due to user interaction.
*/
@@ -92,6 +100,11 @@ public interface AppStandbyInternal {
boolean isAppIdleFiltered(String packageName, int appId, int userId,
long elapsedRealtime);
+ /**
+ * @return true if currently app idle parole mode is on.
+ */
+ boolean isInParole();
+
int[] getIdleUidsForUser(int userId);
void setAppIdleAsync(String packageName, boolean idle, int userId);
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 24728dd8edca..cb5cb175ff24 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -214,8 +214,7 @@ public class AppStandbyController implements AppStandbyInternal {
private AppIdleHistory mAppIdleHistory;
@GuardedBy("mPackageAccessListeners")
- private ArrayList<AppIdleStateChangeListener>
- mPackageAccessListeners = new ArrayList<>();
+ private final ArrayList<AppIdleStateChangeListener> mPackageAccessListeners = new ArrayList<>();
/** Whether we've queried the list of carrier privileged apps. */
@GuardedBy("mAppIdleLock")
@@ -235,6 +234,7 @@ public class AppStandbyController implements AppStandbyInternal {
static final int MSG_FORCE_IDLE_STATE = 4;
static final int MSG_CHECK_IDLE_STATES = 5;
static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
+ static final int MSG_PAROLE_STATE_CHANGED = 9;
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
/** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */
static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11;
@@ -390,7 +390,16 @@ public class AppStandbyController implements AppStandbyInternal {
@VisibleForTesting
void setAppIdleEnabled(boolean enabled) {
- mAppIdleEnabled = enabled;
+ synchronized (mAppIdleLock) {
+ if (mAppIdleEnabled != enabled) {
+ final boolean oldParoleState = isInParole();
+ mAppIdleEnabled = enabled;
+ if (isInParole() != oldParoleState) {
+ postParoleStateChanged();
+ }
+ }
+ }
+
}
@Override
@@ -563,11 +572,23 @@ public class AppStandbyController implements AppStandbyInternal {
if (mIsCharging != isCharging) {
if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
mIsCharging = isCharging;
+ postParoleStateChanged();
}
}
}
@Override
+ public boolean isInParole() {
+ return !mAppIdleEnabled || mIsCharging;
+ }
+
+ private void postParoleStateChanged() {
+ if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
+ mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
+ }
+
+ @Override
public void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
}
@@ -1502,6 +1523,15 @@ public class AppStandbyController implements AppStandbyInternal {
}
}
+ private void informParoleStateChanged() {
+ final boolean paroled = isInParole();
+ synchronized (mPackageAccessListeners) {
+ for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ listener.onParoleStateChanged(paroled);
+ }
+ }
+ }
+
@Override
public void flushToDisk(int userId) {
synchronized (mAppIdleLock) {
@@ -1920,6 +1950,11 @@ public class AppStandbyController implements AppStandbyInternal {
args.recycle();
break;
+ case MSG_PAROLE_STATE_CHANGED:
+ if (DEBUG) Slog.d(TAG, "Parole state: " + isInParole());
+ informParoleStateChanged();
+ break;
+
case MSG_CHECK_PACKAGE_IDLE_STATE:
checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2,
mInjector.elapsedRealtime());
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 650545f939cc..504890f6cf52 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3021,14 +3021,14 @@ message LauncherUIChanged {
optional string component_name = 11;
// (x, y) coordinate and the index information of the target on the container
- optional int32 grid_x = 12;
- optional int32 grid_y = 13;
- optional int32 page_id = 14;
+ optional int32 grid_x = 12 [default = -1];
+ optional int32 grid_y = 13 [default = -1];
+ optional int32 page_id = 14 [default = -2];
// e.g., folder icon's (x, y) location and index information on the workspace
- optional int32 grid_x_parent = 15;
- optional int32 grid_y_parent = 16;
- optional int32 page_id_parent = 17;
+ optional int32 grid_x_parent = 15 [default = -1];
+ optional int32 grid_y_parent = 16 [default = -1];
+ optional int32 page_id_parent = 17 [default = -2];
// e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE
optional int32 hierarchy = 18;
@@ -3036,7 +3036,7 @@ message LauncherUIChanged {
optional bool is_work_profile = 19;
// Used to store the predicted rank of the target
- optional int32 rank = 20;
+ optional int32 rank = 20 [default = -1];
// e.g., folderLabelState can be captured in the following two fields
optional int32 from_state = 21;
@@ -3044,6 +3044,9 @@ message LauncherUIChanged {
// e.g., autofilled or suggested texts that are not user entered
optional string edittext = 23;
+
+ // e.g., number of contents inside a container (e.g., icons inside a folder)
+ optional int32 cardinality = 24;
}
/**
@@ -3064,22 +3067,34 @@ message LauncherStaticLayout {
optional string component_name = 6;
// (x, y) coordinate and the index information of the target on the container
- optional int32 grid_x = 7;
- optional int32 grid_y = 8;
- optional int32 page_id = 9;
+ optional int32 grid_x = 7 [default = -1];
+ optional int32 grid_y = 8 [default = -1];
+ optional int32 page_id = 9 [default = -2];
// e.g., folder icon's (x, y) location and index information on the workspace
- optional int32 grid_x_parent = 10;
- optional int32 grid_y_parent = 11;
- optional int32 page_id_parent = 12;
-
- // e.g., WORKSPACE, HOTSEAT, FOLDER_WORKSPACE, FOLDER_HOTSEAT
+ // e.g., when used with widgets target, use these values for (span_x, span_y)
+ optional int32 grid_x_parent = 10 [default = -1];
+ optional int32 grid_y_parent = 11 [default = -1];
+ optional int32 page_id_parent = 12 [default = -2];
+
+ // UNKNOWN = 0
+ // HOTSEAT = 1
+ // WORKSPACE = 2
+ // FOLDER_HOTSEAT = 3
+ // FOLDER_WORKSPACE = 4
optional int32 hierarchy = 13;
optional bool is_work_profile = 14;
// e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION
optional int32 origin = 15;
+
+ // e.g., number of icons inside a folder
+ optional int32 cardinality = 16;
+
+ // e.g., (x, y) span of the widget inside homescreen grid system
+ optional int32 span_x = 17 [default = 1];
+ optional int32 span_y = 18 [default = 1];
}
/**
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index bed836a1bd90..7b687210ce33 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -19,6 +19,7 @@
#include "ShellSubscriber.h"
#include <android-base/file.h>
+
#include "matchers/matcher_util.h"
#include "stats_log_util.h"
@@ -32,42 +33,53 @@ const static int FIELD_ID_ATOM = 1;
void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) {
int myToken = claimToken();
+ VLOG("ShellSubscriber: new subscription %d has come in", myToken);
mSubscriptionShouldEnd.notify_one();
shared_ptr<SubscriptionInfo> mySubscriptionInfo = make_shared<SubscriptionInfo>(in, out);
- if (!readConfig(mySubscriptionInfo)) {
- return;
- }
+ if (!readConfig(mySubscriptionInfo)) return;
+
+ {
+ std::unique_lock<std::mutex> lock(mMutex);
+ if (myToken != mToken) {
+ // Some other subscription has already come in. Stop.
+ return;
+ }
+ mSubscriptionInfo = mySubscriptionInfo;
+
+ spawnHelperThreadsLocked(mySubscriptionInfo, myToken);
+ waitForSubscriptionToEndLocked(mySubscriptionInfo, myToken, lock, timeoutSec);
+
+ if (mSubscriptionInfo == mySubscriptionInfo) {
+ mSubscriptionInfo = nullptr;
+ }
- // critical-section
- std::unique_lock<std::mutex> lock(mMutex);
- if (myToken != mToken) {
- // Some other subscription has already come in. Stop.
- return;
}
- mSubscriptionInfo = mySubscriptionInfo;
+}
- if (mySubscriptionInfo->mPulledInfo.size() > 0 && mySubscriptionInfo->mPullIntervalMin > 0) {
- // This thread terminates after it detects that mToken has changed.
+void ShellSubscriber::spawnHelperThreadsLocked(shared_ptr<SubscriptionInfo> myInfo, int myToken) {
+ if (!myInfo->mPulledInfo.empty() && myInfo->mPullIntervalMin > 0) {
std::thread puller([this, myToken] { startPull(myToken); });
puller.detach();
}
- // Block until subscription has ended.
+ std::thread heartbeatSender([this, myToken] { sendHeartbeats(myToken); });
+ heartbeatSender.detach();
+}
+
+void ShellSubscriber::waitForSubscriptionToEndLocked(shared_ptr<SubscriptionInfo> myInfo,
+ int myToken,
+ std::unique_lock<std::mutex>& lock,
+ int timeoutSec) {
if (timeoutSec > 0) {
- mSubscriptionShouldEnd.wait_for(
- lock, timeoutSec * 1s, [this, myToken, &mySubscriptionInfo] {
- return mToken != myToken || !mySubscriptionInfo->mClientAlive;
- });
+ mSubscriptionShouldEnd.wait_for(lock, timeoutSec * 1s, [this, myToken, &myInfo] {
+ return mToken != myToken || !myInfo->mClientAlive;
+ });
} else {
- mSubscriptionShouldEnd.wait(lock, [this, myToken, &mySubscriptionInfo] {
- return mToken != myToken || !mySubscriptionInfo->mClientAlive;
+ mSubscriptionShouldEnd.wait(lock, [this, myToken, &myInfo] {
+ return mToken != myToken || !myInfo->mClientAlive;
});
}
-
- if (mSubscriptionInfo == mySubscriptionInfo) {
- mSubscriptionInfo = nullptr;
- }
}
// Atomically claim the next token. Token numbers denote subscriber ordering.
@@ -129,51 +141,55 @@ bool ShellSubscriber::readConfig(shared_ptr<SubscriptionInfo> subscriptionInfo)
return true;
}
-void ShellSubscriber::startPull(int64_t myToken) {
+void ShellSubscriber::startPull(int myToken) {
+ VLOG("ShellSubscriber: pull thread %d starting", myToken);
while (true) {
- std::lock_guard<std::mutex> lock(mMutex);
- if (!mSubscriptionInfo || mToken != myToken) {
- VLOG("Pulling thread %lld done!", (long long)myToken);
- return;
- }
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!mSubscriptionInfo || mToken != myToken) {
+ VLOG("ShellSubscriber: pulling thread %d done!", myToken);
+ return;
+ }
- int64_t nowMillis = getElapsedRealtimeMillis();
- for (auto& pullInfo : mSubscriptionInfo->mPulledInfo) {
- if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval < nowMillis) {
- vector<std::shared_ptr<LogEvent>> data;
- vector<int32_t> uids;
- uids.insert(uids.end(), pullInfo.mPullUids.begin(), pullInfo.mPullUids.end());
- // This is slow. Consider storing the uids per app and listening to uidmap updates.
- for (const string& pkg : pullInfo.mPullPackages) {
- set<int32_t> uidsForPkg = mUidMap->getAppUid(pkg);
- uids.insert(uids.end(), uidsForPkg.begin(), uidsForPkg.end());
+ int64_t nowMillis = getElapsedRealtimeMillis();
+ for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) {
+ if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) {
+ continue;
}
- uids.push_back(DEFAULT_PULL_UID);
+
+ vector<int32_t> uids;
+ getUidsForPullAtom(&uids, pullInfo);
+
+ vector<std::shared_ptr<LogEvent>> data;
mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, &data);
- VLOG("pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id());
+ VLOG("Pulled %zu atoms with id %d", data.size(), pullInfo.mPullerMatcher.atom_id());
+ writePulledAtomsLocked(data, pullInfo.mPullerMatcher);
- if (!writePulledAtomsLocked(data, pullInfo.mPullerMatcher)) {
- mSubscriptionInfo->mClientAlive = false;
- mSubscriptionShouldEnd.notify_one();
- return;
- }
pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
}
}
- VLOG("Pulling thread %lld sleep....", (long long)myToken);
+ VLOG("ShellSubscriber: pulling thread %d sleeping for %d ms", myToken,
+ mSubscriptionInfo->mPullIntervalMin);
std::this_thread::sleep_for(std::chrono::milliseconds(mSubscriptionInfo->mPullIntervalMin));
}
}
-// \return boolean indicating if writes were successful (will return false if
-// client dies)
-bool ShellSubscriber::writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
+void ShellSubscriber::getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo) {
+ uids->insert(uids->end(), pullInfo.mPullUids.begin(), pullInfo.mPullUids.end());
+ // This is slow. Consider storing the uids per app and listening to uidmap updates.
+ for (const string& pkg : pullInfo.mPullPackages) {
+ set<int32_t> uidsForPkg = mUidMap->getAppUid(pkg);
+ uids->insert(uids->end(), uidsForPkg.begin(), uidsForPkg.end());
+ }
+ uids->push_back(DEFAULT_PULL_UID);
+}
+
+void ShellSubscriber::writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
const SimpleAtomMatcher& matcher) {
mProto.clear();
int count = 0;
for (const auto& event : data) {
- VLOG("%s", event->ToString().c_str());
if (matchesSimple(*mUidMap, matcher, *event)) {
count++;
uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
@@ -183,55 +199,67 @@ bool ShellSubscriber::writePulledAtomsLocked(const vector<std::shared_ptr<LogEve
}
}
- if (count > 0) {
- // First write the payload size.
- size_t bufferSize = mProto.size();
- if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &bufferSize,
- sizeof(bufferSize))) {
- return false;
- }
-
- VLOG("%d atoms, proto size: %zu", count, bufferSize);
- // Then write the payload.
- if (!mProto.flush(mSubscriptionInfo->mOutputFd)) {
- return false;
- }
- }
-
- return true;
+ if (count > 0) attemptWriteToSocketLocked(mProto.size());
}
void ShellSubscriber::onLogEvent(const LogEvent& event) {
std::lock_guard<std::mutex> lock(mMutex);
- if (!mSubscriptionInfo) {
- return;
- }
+ if (!mSubscriptionInfo) return;
mProto.clear();
for (const auto& matcher : mSubscriptionInfo->mPushedMatchers) {
if (matchesSimple(*mUidMap, matcher, event)) {
- VLOG("%s", event.ToString().c_str());
uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
event.ToProto(mProto);
mProto.end(atomToken);
+ attemptWriteToSocketLocked(mProto.size());
+ }
+ }
+}
- // First write the payload size.
- size_t bufferSize = mProto.size();
- if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &bufferSize,
- sizeof(bufferSize))) {
- mSubscriptionInfo->mClientAlive = false;
- mSubscriptionShouldEnd.notify_one();
+// Tries to write the atom encoded in mProto to the socket. If the write fails
+// because the read end of the pipe has closed, signals to other threads that
+// the subscription should end.
+void ShellSubscriber::attemptWriteToSocketLocked(size_t dataSize) {
+ // First write the payload size.
+ if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &dataSize, sizeof(dataSize))) {
+ mSubscriptionInfo->mClientAlive = false;
+ mSubscriptionShouldEnd.notify_one();
+ return;
+ }
+
+ if (dataSize == 0) return;
+
+ // Then, write the payload.
+ if (!mProto.flush(mSubscriptionInfo->mOutputFd)) {
+ mSubscriptionInfo->mClientAlive = false;
+ mSubscriptionShouldEnd.notify_one();
+ return;
+ }
+
+ mLastWriteMs = getElapsedRealtimeMillis();
+}
+
+// Send a heartbeat, consisting solely of a data size of 0, if perfd has not
+// recently received any writes from statsd. When it receives the data size of
+// 0, perfd will not expect any data and recheck whether the shell command is
+// still running.
+void ShellSubscriber::sendHeartbeats(int myToken) {
+ while (true) {
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!mSubscriptionInfo || myToken != mToken) {
+ VLOG("ShellSubscriber: heartbeat thread %d done!", myToken);
return;
}
- // Then write the payload.
- if (!mProto.flush(mSubscriptionInfo->mOutputFd)) {
- mSubscriptionInfo->mClientAlive = false;
- mSubscriptionShouldEnd.notify_one();
- return;
+ if (getElapsedRealtimeMillis() - mLastWriteMs > kMsBetweenHeartbeats) {
+ VLOG("ShellSubscriber: sending a heartbeat to perfd");
+ attemptWriteToSocketLocked(/*dataSize=*/0);
}
}
+ std::this_thread::sleep_for(std::chrono::milliseconds(kMsBetweenHeartbeats));
}
}
diff --git a/cmds/statsd/src/shell/ShellSubscriber.h b/cmds/statsd/src/shell/ShellSubscriber.h
index 61457d89f224..26c8a2a0b683 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.h
+++ b/cmds/statsd/src/shell/ShellSubscriber.h
@@ -38,11 +38,11 @@ namespace statsd {
*
* A shell subscription lasts *until shell exits*. Unlike config based clients, a shell client
* communicates with statsd via file descriptors. They can subscribe pushed and pulled atoms.
- * The atoms are sent back to the client in real time, as opposed to
- * keeping the data in memory. Shell clients do not subscribe aggregated metrics, as they are
- * responsible for doing the aggregation after receiving the atom events.
+ * The atoms are sent back to the client in real time, as opposed to keeping the data in memory.
+ * Shell clients do not subscribe aggregated metrics, as they are responsible for doing the
+ * aggregation after receiving the atom events.
*
- * Shell client pass ShellSubscription in the proto binary format. Client can update the
+ * Shell clients pass ShellSubscription in the proto binary format. Clients can update the
* subscription by sending a new subscription. The new subscription would replace the old one.
* Input data stream format is:
*
@@ -54,7 +54,7 @@ namespace statsd {
* The stream would be in the following format:
* |size_t|shellData proto|size_t|shellData proto|....
*
- * Only one shell subscriber allowed at a time, because each shell subscriber blocks one thread
+ * Only one shell subscriber is allowed at a time because each shell subscriber blocks one thread
* until it exits.
*/
class ShellSubscriber : public virtual RefBase {
@@ -100,11 +100,28 @@ private:
bool readConfig(std::shared_ptr<SubscriptionInfo> subscriptionInfo);
- void startPull(int64_t myToken);
+ void spawnHelperThreadsLocked(std::shared_ptr<SubscriptionInfo> myInfo, int myToken);
- bool writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
+ void waitForSubscriptionToEndLocked(std::shared_ptr<SubscriptionInfo> myInfo,
+ int myToken,
+ std::unique_lock<std::mutex>& lock,
+ int timeoutSec);
+
+ void startPull(int myToken);
+
+ void writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
const SimpleAtomMatcher& matcher);
+ void getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo);
+
+ void attemptWriteToSocketLocked(size_t dataSize);
+
+ // Send ocassional heartbeats for two reasons: (a) for statsd to detect when
+ // the read end of the pipe has closed and (b) for perfd to escape a
+ // blocking read call and recheck if the user has terminated the
+ // subscription.
+ void sendHeartbeats(int myToken);
+
sp<UidMap> mUidMap;
sp<StatsPullerManager> mPullerMgr;
@@ -120,6 +137,11 @@ private:
int mToken = 0;
const int32_t DEFAULT_PULL_UID = AID_SYSTEM;
+
+ // Tracks when we last send data to perfd. We need that time to determine
+ // when next to send a heartbeat.
+ int64_t mLastWriteMs = 0;
+ const int64_t kMsBetweenHeartbeats = 1000;
};
} // namespace statsd
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index 7b952d7a392e..363fcb4bf193 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -86,28 +86,34 @@ void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap,
// wait for the data to be written.
std::this_thread::sleep_for(100ms);
- int expected_data_size = expectedData.ByteSize();
-
- // now read from the pipe. firstly read the atom size.
- size_t dataSize = 0;
- EXPECT_EQ((int)sizeof(dataSize), read(fds_data[0], &dataSize, sizeof(dataSize)));
-
- EXPECT_EQ(expected_data_size, (int)dataSize);
-
- // then read that much data which is the atom in proto binary format
- vector<uint8_t> dataBuffer(dataSize);
- EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize));
-
- // make sure the received bytes can be parsed to an atom
- ShellData receivedAtom;
- EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0);
+ // Because we might receive heartbeats from statsd, consisting of data sizes
+ // of 0, encapsulate reads within a while loop.
+ bool readAtom = false;
+ while (!readAtom) {
+ // Read the atom size.
+ size_t dataSize = 0;
+ read(fds_data[0], &dataSize, sizeof(dataSize));
+ if (dataSize == 0) continue;
+ EXPECT_EQ(expectedData.ByteSize(), int(dataSize));
+
+ // Read that much data in proto binary format.
+ vector<uint8_t> dataBuffer(dataSize);
+ EXPECT_EQ((int)dataSize, read(fds_data[0], dataBuffer.data(), dataSize));
+
+ // Make sure the received bytes can be parsed to an atom.
+ ShellData receivedAtom;
+ EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0);
+
+ // Serialize the expected atom to byte array and compare to make sure
+ // they are the same.
+ vector<uint8_t> expectedAtomBuffer(expectedData.ByteSize());
+ expectedData.SerializeToArray(expectedAtomBuffer.data(), expectedData.ByteSize());
+ EXPECT_EQ(expectedAtomBuffer, dataBuffer);
+
+ readAtom = true;
+ }
- // serialze the expected atom to bytes. and compare. to make sure they are the same.
- vector<uint8_t> atomBuffer(expected_data_size);
- expectedData.SerializeToArray(&atomBuffer[0], expected_data_size);
- EXPECT_EQ(atomBuffer, dataBuffer);
close(fds_data[0]);
-
if (reader.joinable()) {
reader.join();
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 187274a837a0..7912dacac377 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -635,10 +635,11 @@ public class UserManager {
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
- * owners, profile owners on the primary user or profile owners of organization-owned managed
- * profiles on the parent profile. The default value is <code>false</code>.
+ * owners or profile owners on the primary user. The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can add other users.
+ * <p> When the device is an organization-owned device provisioned with a managed profile,
+ * this restriction will be set as a base restriction which cannot be removed by any admin.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index ed429dd835c3..06caa03e3cb4 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -56,10 +56,15 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.RemoteStream;
import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.TransferPipe;
import com.android.internal.util.CollectionUtils;
import libcore.util.EmptyArray;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -67,7 +72,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@@ -476,6 +483,36 @@ public final class PermissionControllerManager {
}
/**
+ * Dump permission controller state.
+ *
+ * @hide
+ */
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) {
+ CompletableFuture<Throwable> dumpResult = new CompletableFuture<>();
+ mRemoteService.postForResult(
+ service -> TransferPipe.dumpAsync(service.asBinder(), args))
+ .whenComplete(
+ (dump, err) -> {
+ try (FileOutputStream out = new FileOutputStream(fd)) {
+ out.write(dump);
+ } catch (IOException | NullPointerException e) {
+ Log.e(TAG, "Could for forwards permission controller dump", e);
+ }
+
+ dumpResult.complete(err);
+ });
+
+ try {
+ Throwable err = dumpResult.get(UNBIND_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ if (err != null) {
+ throw err;
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "Could not dump permission controller state", e);
+ }
+ }
+
+ /**
* Gets the runtime permissions for an app.
*
* @param packageName The package for which to query.
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 82a7d788100d..c6ede32d0864 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -50,9 +50,11 @@ import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -494,6 +496,11 @@ public abstract class PermissionControllerService extends Service {
"packageName cannot be null");
onOneTimePermissionSessionTimeout(packageName);
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ PermissionControllerService.this.dump(fd, writer, args);
+ }
};
}
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 2e00c0c9d2a4..327bca268a7b 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1274,8 +1274,6 @@ public abstract class DocumentsProvider extends ContentProvider {
out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
} else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
- enforceReadPermissionInner(documentUri, getCallingPackage(),
- getCallingAttributionTag(), null);
return getDocumentMetadata(documentId);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
diff --git a/core/java/android/service/autofill/IInlineSuggestionUi.aidl b/core/java/android/service/autofill/IInlineSuggestionUi.aidl
new file mode 100644
index 000000000000..7289853064f8
--- /dev/null
+++ b/core/java/android/service/autofill/IInlineSuggestionUi.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.service.autofill.ISurfacePackageResultCallback;
+
+/**
+ * Interface to interact with a remote inline suggestion UI.
+ *
+ * @hide
+ */
+oneway interface IInlineSuggestionUi {
+ void getSurfacePackage(ISurfacePackageResultCallback callback);
+ void releaseSurfaceControlViewHost();
+}
diff --git a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
index 172cfef9fee2..97eb790b9acc 100644
--- a/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionUiCallback.aidl
@@ -18,17 +18,19 @@ package android.service.autofill;
import android.content.IntentSender;
import android.os.IBinder;
+import android.service.autofill.IInlineSuggestionUi;
import android.view.SurfaceControlViewHost;
/**
- * Interface to receive events from inline suggestions.
+ * Interface to receive events from a remote inline suggestion UI.
*
* @hide
*/
oneway interface IInlineSuggestionUiCallback {
void onClick();
void onLongClick();
- void onContent(in SurfaceControlViewHost.SurfacePackage surface, int width, int height);
+ void onContent(in IInlineSuggestionUi content, in SurfaceControlViewHost.SurfacePackage surface,
+ int width, int height);
void onError();
void onTransferTouchFocusToImeWindow(in IBinder sourceInputToken, int displayId);
void onStartIntentSender(in IntentSender intentSender);
diff --git a/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl b/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl
new file mode 100644
index 000000000000..0c2c624952eb
--- /dev/null
+++ b/core/java/android/service/autofill/ISurfacePackageResultCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.view.SurfaceControlViewHost;
+
+/**
+ * Interface to receive a SurfaceControlViewHost.SurfacePackage.
+ *
+ * @hide
+ */
+oneway interface ISurfacePackageResultCallback {
+ void onResult(in SurfaceControlViewHost.SurfacePackage result);
+}
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index 6c22b1936d74..3ea443bab3f8 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -33,6 +33,7 @@ import android.os.Looper;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
+import android.util.LruCache;
import android.util.Size;
import android.view.Display;
import android.view.SurfaceControlViewHost;
@@ -40,6 +41,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import java.lang.ref.WeakReference;
+
/**
* A service that renders an inline presentation view given the {@link InlinePresentation}.
*
@@ -65,6 +68,27 @@ public abstract class InlineSuggestionRenderService extends Service {
private IInlineSuggestionUiCallback mCallback;
+
+ /**
+ * A local LRU cache keeping references to the inflated {@link SurfaceControlViewHost}s, so
+ * they can be released properly when no longer used. Each view needs to be tracked separately,
+ * therefore for simplicity we use the hash code of the value object as key in the cache.
+ */
+ private final LruCache<InlineSuggestionUiImpl, Boolean> mActiveInlineSuggestions =
+ new LruCache<InlineSuggestionUiImpl, Boolean>(30) {
+ @Override
+ public void entryRemoved(boolean evicted, InlineSuggestionUiImpl key,
+ Boolean oldValue,
+ Boolean newValue) {
+ if (evicted) {
+ Log.w(TAG,
+ "Hit max=100 entries in the cache. Releasing oldest one to make "
+ + "space.");
+ key.releaseSurfaceControlViewHost();
+ }
+ }
+ };
+
/**
* If the specified {@code width}/{@code height} is an exact value, then it will be returned as
* is, otherwise the method tries to measure a size that is just large enough to fit the view
@@ -169,8 +193,14 @@ public abstract class InlineSuggestionRenderService extends Service {
return true;
});
- sendResult(callback, host.getSurfacePackage(), measuredSize.getWidth(),
- measuredSize.getHeight());
+ try {
+ InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mHandler);
+ mActiveInlineSuggestions.put(uiImpl, true);
+ callback.onContent(new InlineSuggestionUiWrapper(uiImpl), host.getSurfacePackage(),
+ measuredSize.getWidth(), measuredSize.getHeight());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onContent()");
+ }
} finally {
updateDisplay(Display.DEFAULT_DISPLAY);
}
@@ -181,12 +211,87 @@ public abstract class InlineSuggestionRenderService extends Service {
callback.sendResult(rendererInfo);
}
- private void sendResult(@NonNull IInlineSuggestionUiCallback callback,
- @Nullable SurfaceControlViewHost.SurfacePackage surface, int width, int height) {
- try {
- callback.onContent(surface, width, height);
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException calling onContent(" + surface + ")");
+ /**
+ * A wrapper class around the {@link InlineSuggestionUiImpl} to ensure it's not strongly
+ * reference by the remote system server process.
+ */
+ private static final class InlineSuggestionUiWrapper extends
+ android.service.autofill.IInlineSuggestionUi.Stub {
+
+ private final WeakReference<InlineSuggestionUiImpl> mUiImpl;
+
+ InlineSuggestionUiWrapper(InlineSuggestionUiImpl uiImpl) {
+ mUiImpl = new WeakReference<>(uiImpl);
+ }
+
+ @Override
+ public void releaseSurfaceControlViewHost() {
+ final InlineSuggestionUiImpl uiImpl = mUiImpl.get();
+ if (uiImpl != null) {
+ uiImpl.releaseSurfaceControlViewHost();
+ }
+ }
+
+ @Override
+ public void getSurfacePackage(ISurfacePackageResultCallback callback) {
+ final InlineSuggestionUiImpl uiImpl = mUiImpl.get();
+ if (uiImpl != null) {
+ uiImpl.getSurfacePackage(callback);
+ }
+ }
+ }
+
+ /**
+ * Keeps track of a SurfaceControlViewHost to ensure it's released when its lifecycle ends.
+ *
+ * <p>This class is thread safe, because all the outside calls are piped into a single
+ * handler thread to be processed.
+ */
+ private final class InlineSuggestionUiImpl {
+
+ @Nullable
+ private SurfaceControlViewHost mViewHost;
+ @NonNull
+ private final Handler mHandler;
+
+ InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler) {
+ this.mViewHost = viewHost;
+ this.mHandler = handler;
+ }
+
+ /**
+ * Call {@link SurfaceControlViewHost#release()} to release it. After this, this view is
+ * not usable, and any further calls to the
+ * {@link #getSurfacePackage(ISurfacePackageResultCallback)} will get {@code null} result.
+ */
+ public void releaseSurfaceControlViewHost() {
+ mHandler.post(() -> {
+ if (mViewHost == null) {
+ return;
+ }
+ Log.v(TAG, "Releasing inline suggestion view host");
+ mViewHost.release();
+ mViewHost = null;
+ InlineSuggestionRenderService.this.mActiveInlineSuggestions.remove(
+ InlineSuggestionUiImpl.this);
+ Log.v(TAG, "Removed the inline suggestion from the cache, current size="
+ + InlineSuggestionRenderService.this.mActiveInlineSuggestions.size());
+ });
+ }
+
+ /**
+ * Sends back a new {@link android.view.SurfaceControlViewHost.SurfacePackage} if the view
+ * is not released, {@code null} otherwise.
+ */
+ public void getSurfacePackage(ISurfacePackageResultCallback callback) {
+ Log.d(TAG, "getSurfacePackage");
+ mHandler.post(() -> {
+ try {
+ callback.onResult(mViewHost == null ? null : mViewHost.getSurfacePackage());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException calling onSurfacePackage");
+ }
+ });
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 4e9e3d49694e..d2dfb29ba25c 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1075,8 +1075,12 @@ public class DreamService extends Service implements Window.Callback {
@Override
public void onViewDetachedFromWindow(View v) {
- mActivity = null;
- finish();
+ if (mActivity == null || !mActivity.isChangingConfigurations()) {
+ // Only stop the dream if the view is not detached by relaunching
+ // activity for configuration changes.
+ mActivity = null;
+ finish();
+ }
}
});
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a135b0ca148b..fcc4a6ec4d92 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1117,7 +1117,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
* Cancel on-going animation to show/hide {@link InsetsType}.
*/
@VisibleForTesting
- public void cancelExistingAnimation() {
+ public void cancelExistingAnimations() {
cancelExistingControllers(all());
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42f11c162473..a17af6c90617 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4624,6 +4624,8 @@ public final class ViewRootImpl implements ViewParent,
setAccessibilityFocus(null, null);
+ mInsetsController.cancelExistingAnimations();
+
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index d8bf58f78339..9674a80c8159 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -104,10 +104,10 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
@Override
public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+ if (mViewRoot.mView == null) {
+ throw new IllegalStateException("View of the ViewRootImpl is not initiated.");
+ }
if (mApplier == null) {
- if (mViewRoot.mView == null) {
- throw new IllegalStateException("View of the ViewRootImpl is not initiated.");
- }
mApplier = new SyncRtSurfaceTransactionApplier(mViewRoot.mView);
}
if (mViewRoot.mView.isHardwareAccelerated()) {
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 6b1a480986c8..4c72474435a4 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -18,11 +18,13 @@ package android.view.inputmethod;
import android.annotation.BinderThread;
import android.annotation.CallbackExecutor;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Context;
-import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
@@ -42,26 +44,26 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
- * This class represents an inline suggestion which is made by one app
- * and can be embedded into the UI of another. Suggestions may contain
- * sensitive information not known to the host app which needs to be
- * protected from spoofing. To address that the suggestion view inflated
- * on demand for embedding is created in such a way that the hosting app
- * cannot introspect its content and cannot interact with it.
+ * This class represents an inline suggestion which is made by one app and can be embedded into the
+ * UI of another. Suggestions may contain sensitive information not known to the host app which
+ * needs to be protected from spoofing. To address that the suggestion view inflated on demand for
+ * embedding is created in such a way that the hosting app cannot introspect its content and cannot
+ * interact with it.
*/
-@DataClass(
- genEqualsHashCode = true,
- genToString = true,
- genHiddenConstDefs = true,
+@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstDefs = true,
genHiddenConstructor = true)
-@DataClass.Suppress({"getContentProvider"})
public final class InlineSuggestion implements Parcelable {
private static final String TAG = "InlineSuggestion";
- private final @NonNull InlineSuggestionInfo mInfo;
+ @NonNull
+ private final InlineSuggestionInfo mInfo;
- private final @Nullable IInlineContentProvider mContentProvider;
+ /**
+ * @hide
+ */
+ @Nullable
+ private final IInlineContentProvider mContentProvider;
/**
* Used to keep a strong reference to the callback so it doesn't get garbage collected.
@@ -69,7 +71,8 @@ public final class InlineSuggestion implements Parcelable {
* @hide
*/
@DataClass.ParcelWith(InlineContentCallbackImplParceling.class)
- private @Nullable InlineContentCallbackImpl mInlineContentCallback;
+ @Nullable
+ private InlineContentCallbackImpl mInlineContentCallback;
/**
* Creates a new {@link InlineSuggestion}, for testing purpose.
@@ -87,8 +90,7 @@ public final class InlineSuggestion implements Parcelable {
*
* @hide
*/
- public InlineSuggestion(
- @NonNull InlineSuggestionInfo info,
+ public InlineSuggestion(@NonNull InlineSuggestionInfo info,
@Nullable IInlineContentProvider contentProvider) {
this(info, contentProvider, /* inlineContentCallback */ null);
}
@@ -96,25 +98,30 @@ public final class InlineSuggestion implements Parcelable {
/**
* Inflates a view with the content of this suggestion at a specific size.
*
- * <p> The size must be either 1) between the
- * {@link android.widget.inline.InlinePresentationSpec#getMinSize() min size} and the
- * {@link android.widget.inline.InlinePresentationSpec#getMaxSize() max size} of the
- * presentation spec returned by {@link InlineSuggestionInfo#getInlinePresentationSpec()},
- * or 2) {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If the size is set to
- * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just
- * large enough to fit the content, while still conforming to the min / max size specified by
- * the {@link android.widget.inline.InlinePresentationSpec}.
+ * <p> Each dimension of the size must satisfy one of the following conditions:
+ *
+ * <ol>
+ * <li>between {@link android.widget.inline.InlinePresentationSpec#getMinSize()} and
+ * {@link android.widget.inline.InlinePresentationSpec#getMaxSize()} of the presentation spec
+ * from {@code mInfo}
+ * <li>{@link ViewGroup.LayoutParams#WRAP_CONTENT}
+ * </ol>
+ *
+ * If the size is set to {@link
+ * ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just large
+ * enough to fit the content, while still conforming to the min / max size specified by the
+ * {@link android.widget.inline.InlinePresentationSpec}.
*
* <p> The caller can attach an {@link android.view.View.OnClickListener} and/or an
- * {@link android.view.View.OnLongClickListener} to the view in the
- * {@code callback} to receive click and long click events on the view.
+ * {@link android.view.View.OnLongClickListener} to the view in the {@code callback} to receive
+ * click and long click events on the view.
*
* @param context Context in which to inflate the view.
- * @param size The size at which to inflate the suggestion. For each dimension, it maybe
- * an exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
- * @param callback Callback for receiving the inflated view, where the
- * {@link ViewGroup.LayoutParams} of the view is set as the actual size of
- * the underlying remote view.
+ * @param size The size at which to inflate the suggestion. For each dimension, it maybe an
+ * exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * @param callback Callback for receiving the inflated view, where the {@link
+ * ViewGroup.LayoutParams} of the view is set as the actual size of the
+ * underlying remote view.
* @throws IllegalArgumentException If an invalid argument is passed.
* @throws IllegalStateException If this method is already called.
*/
@@ -130,19 +137,17 @@ public final class InlineSuggestion implements Parcelable {
+ ", nor wrap_content");
}
mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback);
- AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- if (mContentProvider == null) {
- callback.accept(/* view */ null);
- return;
- }
- try {
- mContentProvider.provideContent(size.getWidth(), size.getHeight(),
- new InlineContentCallbackWrapper(mInlineContentCallback));
- } catch (RemoteException e) {
- Slog.w(TAG, "Error creating suggestion content surface: " + e);
- callback.accept(/* view */ null);
- }
- });
+ if (mContentProvider == null) {
+ callbackExecutor.execute(() -> callback.accept(/* view */ null));
+ return;
+ }
+ try {
+ mContentProvider.provideContent(size.getWidth(), size.getHeight(),
+ new InlineContentCallbackWrapper(mInlineContentCallback));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error creating suggestion content surface: " + e);
+ callbackExecutor.execute(() -> callback.accept(/* view */ null));
+ }
}
/**
@@ -161,9 +166,14 @@ public final class InlineSuggestion implements Parcelable {
if (mInlineContentCallback != null) {
throw new IllegalStateException("Already called #inflate()");
}
- return new InlineContentCallbackImpl(context, callbackExecutor, callback);
+ return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor,
+ callback);
}
+ /**
+ * A wrapper class around the {@link InlineContentCallbackImpl} to ensure it's not strongly
+ * reference by the remote system server process.
+ */
private static final class InlineContentCallbackWrapper extends IInlineContentCallback.Stub {
private final WeakReference<InlineContentCallbackImpl> mCallbackImpl;
@@ -201,17 +211,68 @@ public final class InlineSuggestion implements Parcelable {
}
}
+ /**
+ * Handles the communication between the inline suggestion view in current (IME) process and
+ * the remote view provided from the system server.
+ *
+ * <p>This class is thread safe, because all the outside calls are piped into a single
+ * handler thread to be processed.
+ */
private static final class InlineContentCallbackImpl {
- private final @NonNull Context mContext;
- private final @NonNull Executor mCallbackExecutor;
- private final @NonNull Consumer<InlineContentView> mCallback;
- private @Nullable InlineContentView mView;
+ @NonNull
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+ @NonNull
+ private final Context mContext;
+ @Nullable
+ private final IInlineContentProvider mInlineContentProvider;
+ @NonNull
+ private final Executor mCallbackExecutor;
+
+ /**
+ * Callback from the client (IME) that will receive the inflated suggestion view. It'll
+ * only be called once when the view SurfacePackage is first sent back to the client. Any
+ * updates to the view due to attach to window and detach from window events will be
+ * handled under the hood, transparent from the client.
+ */
+ @NonNull
+ private final Consumer<InlineContentView> mCallback;
+
+ /**
+ * Indicates whether the first content has been received or not.
+ */
+ private boolean mFirstContentReceived = false;
+
+ /**
+ * The client (IME) side view which internally wraps a remote view. It'll be set when
+ * {@link #onContent(SurfaceControlViewHost.SurfacePackage, int, int)} is called, which
+ * should only happen once in the lifecycle of this inline suggestion instance.
+ */
+ @Nullable
+ private InlineContentView mView;
+
+ /**
+ * The SurfacePackage pointing to the remote view. It's cached here to be sent to the next
+ * available consumer.
+ */
+ @Nullable
+ private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+
+ /**
+ * The callback (from the {@link InlineContentView}) which consumes the surface package.
+ * It's cached here to be called when the SurfacePackage is returned from the remote
+ * view owning process.
+ */
+ @Nullable
+ private Consumer<SurfaceControlViewHost.SurfacePackage> mSurfacePackageConsumer;
InlineContentCallbackImpl(@NonNull Context context,
+ @Nullable IInlineContentProvider inlineContentProvider,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Consumer<InlineContentView> callback) {
mContext = context;
+ mInlineContentProvider = inlineContentProvider;
mCallbackExecutor = callbackExecutor;
mCallback = callback;
}
@@ -219,28 +280,110 @@ public final class InlineSuggestion implements Parcelable {
@BinderThread
public void onContent(SurfaceControlViewHost.SurfacePackage content, int width,
int height) {
- if (content == null) {
+ mMainHandler.post(() -> handleOnContent(content, width, height));
+ }
+
+ @MainThread
+ private void handleOnContent(SurfaceControlViewHost.SurfacePackage content, int width,
+ int height) {
+ if (!mFirstContentReceived) {
+ handleOnFirstContentReceived(content, width, height);
+ mFirstContentReceived = true;
+ } else {
+ handleOnSurfacePackage(content);
+ }
+ }
+
+ /**
+ * Called when the view content is returned for the first time.
+ */
+ @MainThread
+ private void handleOnFirstContentReceived(SurfaceControlViewHost.SurfacePackage content,
+ int width, int height) {
+ mSurfacePackage = content;
+ if (mSurfacePackage == null) {
mCallbackExecutor.execute(() -> mCallback.accept(/* view */null));
} else {
mView = new InlineContentView(mContext);
mView.setLayoutParams(new ViewGroup.LayoutParams(width, height));
- mView.setChildSurfacePackage(content);
+ mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater());
mCallbackExecutor.execute(() -> mCallback.accept(mView));
}
}
+ /**
+ * Called when any subsequent SurfacePackage is returned from the remote view owning
+ * process.
+ */
+ @MainThread
+ private void handleOnSurfacePackage(SurfaceControlViewHost.SurfacePackage surfacePackage) {
+ mSurfacePackage = surfacePackage;
+ if (mSurfacePackage != null && mSurfacePackageConsumer != null) {
+ mSurfacePackageConsumer.accept(mSurfacePackage);
+ mSurfacePackageConsumer = null;
+ }
+ }
+
+ @MainThread
+ private void handleOnSurfacePackageReleased() {
+ mSurfacePackage = null;
+ try {
+ mInlineContentProvider.onSurfacePackageReleased();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+ }
+ }
+
+ @MainThread
+ private void handleGetSurfacePackage(
+ Consumer<SurfaceControlViewHost.SurfacePackage> consumer) {
+ if (mSurfacePackage != null) {
+ consumer.accept(mSurfacePackage);
+ } else {
+ mSurfacePackageConsumer = consumer;
+ try {
+ mInlineContentProvider.requestSurfacePackage();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling getSurfacePackage(): " + e);
+ consumer.accept(null);
+ mSurfacePackageConsumer = null;
+ }
+ }
+ }
+
+ private InlineContentView.SurfacePackageUpdater getSurfacePackageUpdater() {
+ return new InlineContentView.SurfacePackageUpdater() {
+ @Override
+ public void onSurfacePackageReleased() {
+ mMainHandler.post(
+ () -> InlineContentCallbackImpl.this.handleOnSurfacePackageReleased());
+ }
+
+ @Override
+ public void getSurfacePackage(
+ Consumer<SurfaceControlViewHost.SurfacePackage> consumer) {
+ mMainHandler.post(
+ () -> InlineContentCallbackImpl.this.handleGetSurfacePackage(consumer));
+ }
+ };
+ }
+
@BinderThread
public void onClick() {
- if (mView != null && mView.hasOnClickListeners()) {
- mView.callOnClick();
- }
+ mMainHandler.post(() -> {
+ if (mView != null && mView.hasOnClickListeners()) {
+ mView.callOnClick();
+ }
+ });
}
@BinderThread
public void onLongClick() {
- if (mView != null && mView.hasOnLongClickListeners()) {
- mView.performLongClick();
- }
+ mMainHandler.post(() -> {
+ if (mView != null && mView.hasOnLongClickListeners()) {
+ mView.performLongClick();
+ }
+ });
}
}
@@ -262,6 +405,7 @@ public final class InlineSuggestion implements Parcelable {
+
// Code below generated by codegen v1.0.15.
//
// DO NOT MODIFY!
@@ -302,6 +446,14 @@ public final class InlineSuggestion implements Parcelable {
}
/**
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @Nullable IInlineContentProvider getContentProvider() {
+ return mContentProvider;
+ }
+
+ /**
* Used to keep a strong reference to the callback so it doesn't get garbage collected.
*
* @hide
@@ -421,7 +573,7 @@ public final class InlineSuggestion implements Parcelable {
};
@DataClass.Generated(
- time = 1587771173367L,
+ time = 1588308946517L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java
index 4f2af63626cf..8657e828a3f6 100644
--- a/core/java/android/widget/inline/InlineContentView.java
+++ b/core/java/android/widget/inline/InlineContentView.java
@@ -21,40 +21,45 @@ import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
+import java.util.function.Consumer;
+
/**
- * This class represents a view that holds opaque content from another app that
- * you can inline in your UI.
+ * This class represents a view that holds opaque content from another app that you can inline in
+ * your UI.
*
* <p>Since the content presented by this view is from another security domain,it is
- * shown on a remote surface preventing the host application from accessing that content.
- * Also the host application cannot interact with the inlined content by injecting touch
- * events or clicking programmatically.
+ * shown on a remote surface preventing the host application from accessing that content. Also the
+ * host application cannot interact with the inlined content by injecting touch events or clicking
+ * programmatically.
*
* <p>This view can be overlaid by other windows, i.e. redressed, but if this is the case
- * the inined UI would not be interactive. Sometimes this is desirable, e.g. animating
- * transitions.
+ * the inlined UI would not be interactive. Sometimes this is desirable, e.g. animating transitions.
*
* <p>By default the surface backing this view is shown on top of the hosting window such
- * that the inlined content is interactive. However, you can temporarily move the surface
- * under the hosting window which could be useful in some cases, e.g. animating transitions.
- * At this point the inlined content will not be interactive and the touch events would
- * be delivered to your app.
- * <p>
- * Instances of this class are created by the platform and can be programmatically attached
- * to your UI. Once you attach and detach this view it can not longer be reused and you
- * should obtain a new view from the platform via the dedicated APIs.
+ * that the inlined content is interactive. However, you can temporarily move the surface under the
+ * hosting window which could be useful in some cases, e.g. animating transitions. At this point the
+ * inlined content will not be interactive and the touch events would be delivered to your app.
+ *
+ * <p> Instances of this class are created by the platform and can be programmatically attached to
+ * your UI. Once the view is attached to the window, you may detach and reattach it to the window.
+ * It should work seamlessly from the hosting process's point of view.
*/
public class InlineContentView extends ViewGroup {
+ private static final String TAG = "InlineContentView";
+
+ private static final boolean DEBUG = false;
+
/**
- * Callback for observing the lifecycle of the surface control
- * that manipulates the backing secure embedded UI surface.
+ * Callback for observing the lifecycle of the surface control that manipulates the backing
+ * secure embedded UI surface.
*/
public interface SurfaceControlCallback {
/**
@@ -72,15 +77,41 @@ public class InlineContentView extends ViewGroup {
void onDestroyed(@NonNull SurfaceControl surfaceControl);
}
- private final @NonNull SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
+ /**
+ * Callback for sending an updated surface package in case the previous one is released
+ * from the detached from window event, and for getting notified of such event.
+ *
+ * This is expected to be provided to the {@link InlineContentView} so it can get updates
+ * from and send updates to the remote content (i.e. surface package) provider.
+ *
+ * @hide
+ */
+ public interface SurfacePackageUpdater {
+
+ /**
+ * Called when the previous surface package is released due to view being detached
+ * from the window.
+ */
+ void onSurfacePackageReleased();
+
+ /**
+ * Called to request an updated surface package.
+ *
+ * @param consumer consumes the updated surface package.
+ */
+ void getSurfacePackage(Consumer<SurfaceControlViewHost.SurfacePackage> consumer);
+ }
+
+ @NonNull
+ private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl());
}
@Override
- public void surfaceChanged(@NonNull SurfaceHolder holder,
- int format, int width, int height) {
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
/* do nothing */
}
@@ -90,13 +121,17 @@ public class InlineContentView extends ViewGroup {
}
};
- private final @NonNull SurfaceView mSurfaceView;
+ @NonNull
+ private final SurfaceView mSurfaceView;
+
+ @Nullable
+ private SurfaceControlCallback mSurfaceControlCallback;
- private @Nullable SurfaceControlCallback mSurfaceControlCallback;
+ @Nullable
+ private SurfacePackageUpdater mSurfacePackageUpdater;
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context) {
@@ -105,7 +140,6 @@ public class InlineContentView extends ViewGroup {
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs) {
@@ -114,7 +148,6 @@ public class InlineContentView extends ViewGroup {
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -123,20 +156,18 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Gets the surface control. If the surface is not created this method
- * returns {@code null}.
+ * Gets the surface control. If the surface is not created this method returns {@code null}.
*
* @return The surface control.
- *
* @see #setSurfaceControlCallback(SurfaceControlCallback)
*/
- public @Nullable SurfaceControl getSurfaceControl() {
+ @Nullable
+ public SurfaceControl getSurfaceControl() {
return mSurfaceView.getSurfaceControl();
}
/**
* @inheritDoc
- *
* @hide
*/
public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -149,14 +180,35 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Sets the embedded UI.
- * @param surfacePackage The embedded UI.
+ * Sets the embedded UI provider.
*
* @hide
*/
- public void setChildSurfacePackage(
- @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) {
- mSurfaceView.setChildSurfacePackage(surfacePackage);
+ public void setChildSurfacePackageUpdater(
+ @Nullable SurfacePackageUpdater surfacePackageUpdater) {
+ mSurfacePackageUpdater = surfacePackageUpdater;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ if (DEBUG) Log.v(TAG, "onAttachedToWindow");
+ super.onAttachedToWindow();
+ if (mSurfacePackageUpdater != null) {
+ mSurfacePackageUpdater.getSurfacePackage(
+ sp -> {
+ if (DEBUG) Log.v(TAG, "Received new SurfacePackage");
+ mSurfaceView.setChildSurfacePackage(sp);
+ });
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (DEBUG) Log.v(TAG, "onDetachedFromWindow");
+ super.onDetachedFromWindow();
+ if (mSurfacePackageUpdater != null) {
+ mSurfacePackageUpdater.onSurfacePackageReleased();
+ }
}
@Override
@@ -165,8 +217,8 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Sets a callback to observe the lifecycle of the surface control for
- * managing the backing surface.
+ * Sets a callback to observe the lifecycle of the surface control for managing the backing
+ * surface.
*
* @param callback The callback to set or {@code null} to clear.
*/
@@ -182,7 +234,6 @@ public class InlineContentView extends ViewGroup {
/**
* @return Whether the surface backing this view appears on top of its parent.
- *
* @see #setZOrderedOnTop(boolean)
*/
public boolean isZOrderedOnTop() {
@@ -190,17 +241,15 @@ public class InlineContentView extends ViewGroup {
}
/**
- * Controls whether the backing surface is placed on top of this view's window.
- * Normally, it is placed on top of the window, to allow interaction
- * with the inlined UI. Via this method, you can place the surface below the
- * window. This means that all of the contents of the window this view is in
- * will be visible on top of its surface.
+ * Controls whether the backing surface is placed on top of this view's window. Normally, it is
+ * placed on top of the window, to allow interaction with the inlined UI. Via this method, you
+ * can place the surface below the window. This means that all of the contents of the window
+ * this view is in will be visible on top of its surface.
*
* <p> The Z ordering can be changed dynamically if the backing surface is
* created, otherwise the ordering would be applied at surface construction time.
*
* @param onTop Whether to show the surface on top of this view's window.
- *
* @see #isZOrderedOnTop()
*/
public boolean setZOrderedOnTop(boolean onTop) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 11ac3881ee38..3fc3f3e65d37 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -158,6 +158,7 @@ public class ChooserActivity extends ResolverActivity implements
private static final String TAG = "ChooserActivity";
private AppPredictor mPersonalAppPredictor;
private AppPredictor mWorkAppPredictor;
+ private boolean mShouldDisplayLandscape;
@UnsupportedAppUsage
public ChooserActivity() {
@@ -716,6 +717,8 @@ public class ChooserActivity extends ResolverActivity implements
mCallerChooserTargets = targets;
}
+ mShouldDisplayLandscape = shouldDisplayLandscape(
+ getResources().getConfiguration().orientation);
setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
null, false);
@@ -1073,6 +1076,7 @@ public class ChooserActivity extends ResolverActivity implements
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
adjustPreviewWidth(newConfig.orientation, null);
updateStickyContentPreview();
}
@@ -1086,7 +1090,7 @@ public class ChooserActivity extends ResolverActivity implements
private void adjustPreviewWidth(int orientation, View parent) {
int width = -1;
- if (shouldDisplayLandscape(orientation)) {
+ if (mShouldDisplayLandscape) {
width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
}
@@ -2940,6 +2944,19 @@ public class ChooserActivity extends ResolverActivity implements
.setSubtype(previewType));
}
+ class ViewHolderBase extends RecyclerView.ViewHolder {
+ private int mViewType;
+
+ ViewHolderBase(View itemView, int viewType) {
+ super(itemView);
+ this.mViewType = viewType;
+ }
+
+ int getViewType() {
+ return mViewType;
+ }
+ }
+
/**
* Used to bind types of individual item including
* {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
@@ -2947,12 +2964,12 @@ public class ChooserActivity extends ResolverActivity implements
* {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
* and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
*/
- final class ItemViewHolder extends RecyclerView.ViewHolder {
+ final class ItemViewHolder extends ViewHolderBase {
ResolverListAdapter.ViewHolder mWrappedViewHolder;
int mListPosition = ChooserListAdapter.NO_POSITION;
- ItemViewHolder(View itemView, boolean isClickable) {
- super(itemView);
+ ItemViewHolder(View itemView, boolean isClickable, int viewType) {
+ super(itemView, viewType);
mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
if (isClickable) {
itemView.setOnClickListener(v -> startSelected(mListPosition,
@@ -2970,9 +2987,9 @@ public class ChooserActivity extends ResolverActivity implements
/**
* Add a footer to the list, to support scrolling behavior below the navbar.
*/
- final class FooterViewHolder extends RecyclerView.ViewHolder {
- FooterViewHolder(View itemView) {
- super(itemView);
+ final class FooterViewHolder extends ViewHolderBase {
+ FooterViewHolder(View itemView, int viewType) {
+ super(itemView, viewType);
}
}
@@ -3083,7 +3100,7 @@ public class ChooserActivity extends ResolverActivity implements
int getMaxTargetsPerRow() {
int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
- if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
+ if (mShouldDisplayLandscape) {
maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
}
return maxTargets;
@@ -3191,13 +3208,14 @@ public class ChooserActivity extends ResolverActivity implements
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_CONTENT_PREVIEW:
- return new ItemViewHolder(createContentPreviewView(parent), false);
+ return new ItemViewHolder(createContentPreviewView(parent), false, viewType);
case VIEW_TYPE_PROFILE:
- return new ItemViewHolder(createProfileView(parent), false);
+ return new ItemViewHolder(createProfileView(parent), false, viewType);
case VIEW_TYPE_AZ_LABEL:
- return new ItemViewHolder(createAzLabelView(parent), false);
+ return new ItemViewHolder(createAzLabelView(parent), false, viewType);
case VIEW_TYPE_NORMAL:
- return new ItemViewHolder(mChooserListAdapter.createView(parent), true);
+ return new ItemViewHolder(
+ mChooserListAdapter.createView(parent), true, viewType);
case VIEW_TYPE_DIRECT_SHARE:
case VIEW_TYPE_CALLER_AND_RANK:
return createItemGroupViewHolder(viewType, parent);
@@ -3205,7 +3223,7 @@ public class ChooserActivity extends ResolverActivity implements
Space sp = new Space(parent.getContext());
sp.setLayoutParams(new RecyclerView.LayoutParams(
LayoutParams.MATCH_PARENT, mFooterHeight));
- return new FooterViewHolder(sp);
+ return new FooterViewHolder(sp, viewType);
default:
// Since we catch all possible viewTypes above, no chance this is being called.
return null;
@@ -3214,7 +3232,7 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
- int viewType = getItemViewType(position);
+ int viewType = ((ViewHolderBase) holder).getViewType();
switch (viewType) {
case VIEW_TYPE_DIRECT_SHARE:
case VIEW_TYPE_CALLER_AND_RANK:
@@ -3325,7 +3343,6 @@ public class ChooserActivity extends ResolverActivity implements
}
viewGroup.setTag(holder);
-
return holder;
}
@@ -3352,14 +3369,15 @@ public class ChooserActivity extends ResolverActivity implements
parentGroup.addView(row2);
mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
- Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
+ Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType);
loadViewsIntoGroup(mDirectShareViewHolder);
return mDirectShareViewHolder;
} else {
ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
false);
- ItemGroupViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
+ ItemGroupViewHolder holder =
+ new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType);
loadViewsIntoGroup(holder);
return holder;
@@ -3521,14 +3539,14 @@ public class ChooserActivity extends ResolverActivity implements
* {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
* and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
*/
- abstract class ItemGroupViewHolder extends RecyclerView.ViewHolder {
+ abstract class ItemGroupViewHolder extends ViewHolderBase {
protected int mMeasuredRowHeight;
private int[] mItemIndices;
protected final View[] mCells;
private final int mColumnCount;
- ItemGroupViewHolder(int cellCount, View itemView) {
- super(itemView);
+ ItemGroupViewHolder(int cellCount, View itemView, int viewType) {
+ super(itemView, viewType);
this.mCells = new View[cellCount];
this.mItemIndices = new int[cellCount];
this.mColumnCount = cellCount;
@@ -3574,8 +3592,8 @@ public class ChooserActivity extends ResolverActivity implements
class SingleRowViewHolder extends ItemGroupViewHolder {
private final ViewGroup mRow;
- SingleRowViewHolder(ViewGroup row, int cellCount) {
- super(cellCount, row);
+ SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) {
+ super(cellCount, row, viewType);
this.mRow = row;
}
@@ -3617,8 +3635,9 @@ public class ChooserActivity extends ResolverActivity implements
private final boolean[] mCellVisibility;
- DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
- super(rows.size() * cellCountPerRow, parent);
+ DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow,
+ int viewType) {
+ super(rows.size() * cellCountPerRow, parent, viewType);
this.mParent = parent;
this.mRows = rows;
diff --git a/core/java/com/android/internal/app/ChooserGridLayoutManager.java b/core/java/com/android/internal/app/ChooserGridLayoutManager.java
new file mode 100644
index 000000000000..317a987cf359
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserGridLayoutManager.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.internal.widget.GridLayoutManager;
+import com.android.internal.widget.RecyclerView;
+
+/**
+ * For a11y and per {@link RecyclerView#onInitializeAccessibilityNodeInfo}, override
+ * methods to ensure proper row counts.
+ */
+public class ChooserGridLayoutManager extends GridLayoutManager {
+
+ /**
+ * Constructor used when layout manager is set in XML by RecyclerView attribute
+ * "layoutManager". If spanCount is not specified in the XML, it defaults to a
+ * single column.
+ *
+ */
+ public ChooserGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Creates a vertical GridLayoutManager
+ *
+ * @param context Current context, will be used to access resources.
+ * @param spanCount The number of columns in the grid
+ */
+ public ChooserGridLayoutManager(Context context, int spanCount) {
+ super(context, spanCount);
+ }
+
+ /**
+ * @param context Current context, will be used to access resources.
+ * @param spanCount The number of columns or rows in the grid
+ * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
+ * #VERTICAL}.
+ * @param reverseLayout When set to true, layouts from end to start.
+ */
+ public ChooserGridLayoutManager(Context context, int spanCount, int orientation,
+ boolean reverseLayout) {
+ super(context, spanCount, orientation, reverseLayout);
+ }
+
+ @Override
+ public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ // Do not count the footer view in the official count
+ return super.getRowCountForAccessibility(recycler, state) - 1;
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 2f62f8e7a0c9..83dabe8d0525 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -182,6 +182,8 @@ public class ResolverActivity extends Activity implements
private BroadcastReceiver mWorkProfileStateReceiver;
private UserHandle mHeaderCreatorUser;
+ private UserHandle mWorkProfileUserHandle;
+
/**
* Get the string resource to be used as a label for the link to the resolver activity for an
* action.
@@ -363,6 +365,7 @@ public class ResolverActivity extends Activity implements
// a more complicated UI that the current voice interaction flow is not able
// to handle.
boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction();
+ mWorkProfileUserHandle = fetchWorkProfileUserProfile();
mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
if (configureContentView()) {
return;
@@ -527,13 +530,18 @@ public class ResolverActivity extends Activity implements
return UserHandle.of(ActivityManager.getCurrentUser());
}
protected @Nullable UserHandle getWorkProfileUserHandle() {
+ return mWorkProfileUserHandle;
+ }
+
+ protected @Nullable UserHandle fetchWorkProfileUserProfile() {
+ mWorkProfileUserHandle = null;
UserManager userManager = getSystemService(UserManager.class);
for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) {
if (userInfo.isManagedProfile()) {
- return userInfo.getUserHandle();
+ mWorkProfileUserHandle = userInfo.getUserHandle();
}
}
- return null;
+ return mWorkProfileUserHandle;
}
private boolean hasWorkProfile() {
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 2fd938f45291..24bf98b6502c 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -54,6 +54,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
@@ -549,6 +550,15 @@ public class ResolverListAdapter extends BaseAdapter {
getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
} else {
holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo());
+ if (info instanceof SelectableTargetInfo) {
+ // direct share targets should append the application name for a better readout
+ DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo();
+ CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
+ CharSequence extendedInfo = info.getExtendedInfo();
+ String contentDescription = String.join(" ", info.getDisplayLabel(),
+ extendedInfo != null ? extendedInfo : "", appName);
+ holder.updateContentDescription(contentDescription);
+ }
}
if (info.isSuspended()) {
@@ -697,6 +707,12 @@ public class ResolverListAdapter extends BaseAdapter {
text2.setVisibility(View.VISIBLE);
text2.setText(subLabel);
}
+
+ itemView.setContentDescription(null);
+ }
+
+ public void updateContentDescription(String description) {
+ itemView.setContentDescription(description);
}
}
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 246a07d3d0fe..900e18d468bb 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -44,7 +44,6 @@ import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGett
import com.android.internal.app.SimpleIconFactory;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -136,6 +135,10 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
return mIsSuspended;
}
+ public DisplayResolveInfo getDisplayResolveInfo() {
+ return mSourceInfo;
+ }
+
private Drawable getChooserTargetIconDrawable(ChooserTarget target,
@Nullable ShortcutInfo shortcutInfo) {
Drawable directShareIcon = null;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 38f5f3279c8e..8c5fdf5822e8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -137,7 +137,7 @@ oneway interface IStatusBar
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
- long operationId);
+ long operationId, int sysUiSessionId);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 24fe0638b091..c32082418bc5 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -106,7 +106,7 @@ interface IStatusBarService
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
- long operationId);
+ long operationId, int sysUiSessionId);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
// Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
diff --git a/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl b/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl
index 08a349c21c8b..78df3eb660a5 100644
--- a/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl
+++ b/core/java/com/android/internal/view/inline/IInlineContentProvider.aidl
@@ -24,4 +24,6 @@ import com.android.internal.view.inline.IInlineContentCallback;
*/
oneway interface IInlineContentProvider {
void provideContent(int width, int height, in IInlineContentCallback callback);
+ void requestSurfacePackage();
+ void onSurfacePackageReleased();
}
diff --git a/core/java/com/android/internal/widget/GridLayoutManager.java b/core/java/com/android/internal/widget/GridLayoutManager.java
index e0502f129f7f..09e6a991b1ac 100644
--- a/core/java/com/android/internal/widget/GridLayoutManager.java
+++ b/core/java/com/android/internal/widget/GridLayoutManager.java
@@ -153,13 +153,11 @@ public class GridLayoutManager extends LinearLayoutManager {
if (mOrientation == HORIZONTAL) {
info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
glp.getSpanIndex(), glp.getSpanSize(),
- spanGroupIndex, 1,
- mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
+ spanGroupIndex, 1, false, false));
} else { // VERTICAL
info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
spanGroupIndex, 1,
- glp.getSpanIndex(), glp.getSpanSize(),
- mSpanCount > 1 && glp.getSpanSize() == mSpanCount, false));
+ glp.getSpanIndex(), glp.getSpanSize(), false, false));
}
}
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index fb2ecf3a478f..3f708f84750c 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -825,18 +825,6 @@ public class ResolverDrawerLayout extends ViewGroup {
return true;
}
break;
- case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
- case R.id.accessibilityActionScrollUp:
- if (mCollapseOffset < mCollapsibleHeight) {
- smoothScrollTo(mCollapsibleHeight, 0);
- return true;
- } else if ((mCollapseOffset < mCollapsibleHeight + mUncollapsibleHeight)
- && isDismissable()) {
- smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, 0);
- mDismissOnScrollerFinished = true;
- return true;
- }
- break;
case AccessibilityNodeInfo.ACTION_COLLAPSE:
if (mCollapseOffset < mCollapsibleHeight) {
smoothScrollTo(mCollapsibleHeight, 0);
@@ -886,7 +874,6 @@ public class ResolverDrawerLayout extends ViewGroup {
}
if ((mCollapseOffset < mCollapsibleHeight + mUncollapsibleHeight)
&& ((mCollapseOffset < mCollapsibleHeight) || isDismissable())) {
- info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
info.addAction(AccessibilityAction.ACTION_SCROLL_UP);
info.setScrollable(true);
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3a5720fd8c4c..c5bc083dfabf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1552,8 +1552,8 @@ static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list,
}
}
-static void BindMountStorageToLowerFs(const userid_t user_id, const char* dir_name,
- const char* package, fail_fn_t fail_fn) {
+static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid,
+ const char* dir_name, const char* package, fail_fn_t fail_fn) {
bool hasSdcardFs = IsFilesystemSupported("sdcardfs");
std::string source;
@@ -1565,6 +1565,9 @@ static void BindMountStorageToLowerFs(const userid_t user_id, const char* dir_na
}
std::string target = StringPrintf("/storage/emulated/%d/%s/%s", user_id, dir_name, package);
+ // As the parent is mounted as tmpfs, we need to create the target dir here.
+ PrepareDirIfNotPresent(target, 0700, uid, uid, fail_fn);
+
if (access(source.c_str(), F_OK) != 0) {
fail_fn(CREATE_ERROR("Error accessing %s: %s", source.c_str(), strerror(errno)));
}
@@ -1574,9 +1577,8 @@ static void BindMountStorageToLowerFs(const userid_t user_id, const char* dir_na
BindMount(source, target, fail_fn);
}
-// Bind mount all obb & data directories that are visible to this app.
-// If app data isolation is not enabled for this process, bind mount the whole obb
-// and data directory instead.
+// Mount tmpfs on Android/data and Android/obb, then bind mount all app visible package
+// directories in data and obb directories.
static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list,
uid_t uid, const char* process_name, jstring managed_nice_name, fail_fn_t fail_fn) {
@@ -1590,12 +1592,18 @@ static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list,
fail_fn(CREATE_ERROR("Data package list cannot be empty"));
}
+ // Create tmpfs on Android/obb and Android/data so these 2 dirs won't enter fuse anymore.
+ std::string androidObbDir = StringPrintf("/storage/emulated/%d/Android/obb", user_id);
+ MountAppDataTmpFs(androidObbDir, fail_fn);
+ std::string androidDataDir = StringPrintf("/storage/emulated/%d/Android/data", user_id);
+ MountAppDataTmpFs(androidDataDir, fail_fn);
+
// Bind mount each package obb directory
for (int i = 0; i < size; i += 3) {
jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
std::string packageName = extract_fn(package_str).value();
- BindMountStorageToLowerFs(user_id, "Android/obb", packageName.c_str(), fail_fn);
- BindMountStorageToLowerFs(user_id, "Android/data", packageName.c_str(), fail_fn);
+ BindMountStorageToLowerFs(user_id, uid, "Android/obb", packageName.c_str(), fail_fn);
+ BindMountStorageToLowerFs(user_id, uid, "Android/data", packageName.c_str(), fail_fn);
}
}
@@ -1648,9 +1656,10 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
uid, process_name, managed_nice_name, fail_fn);
isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
- if (mount_external != MOUNT_EXTERNAL_INSTALLER &&
- mount_external != MOUNT_EXTERNAL_PASS_THROUGH &&
- mount_storage_dirs) {
+ // MOUNT_EXTERNAL_INSTALLER, MOUNT_EXTERNAL_PASS_THROUGH, MOUNT_EXTERNAL_ANDROID_WRITABLE apps
+ // will have mount_storage_dirs == false here (set by ProcessList.needsStorageDataIsolation()),
+ // and hence they won't bind mount storage dirs.
+ if (mount_storage_dirs) {
BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml
index 6b1b002267cb..86dc71cbbfb8 100644
--- a/core/res/res/layout/chooser_list_per_profile.xml
+++ b/core/res/res/layout/chooser_list_per_profile.xml
@@ -20,7 +20,7 @@
<com.android.internal.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layoutManager="com.android.internal.widget.GridLayoutManager"
+ android:layoutManager="com.android.internal.app.ChooserGridLayoutManager"
android:id="@+id/resolver_list"
android:clipToPadding="false"
android:background="?attr/colorBackgroundFloating"
@@ -29,4 +29,4 @@
android:nestedScrollingEnabled="true" />
<include layout="@layout/resolver_empty_states" />
-</RelativeLayout> \ No newline at end of file
+</RelativeLayout>
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 4d0837f495df..446ce3fbaf4b 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -83,6 +83,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
+ android:accessibilityTraversalAfter="@id/title"
android:background="?attr/colorBackgroundFloating">
<LinearLayout
android:orientation="vertical"
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 6720ed6b7bf9..04906788f4cb 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -2548,9 +2548,18 @@ public class PackageManagerTests extends AndroidTestCase {
} else {
installFromRawResource(apk2Name, apk2, 0, false, false, -1,
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- int match = mContext.getPackageManager().checkSignatures(pkg1.getPackageName(),
- pkg2.getPackageName());
- assertEquals(expMatchResult, match);
+ // TODO: All checkSignatures tests should return the same result regardless of
+ // querying by package name or uid; however if there are any edge cases where
+ // individual packages within a shareduid are compared with signatures that do not
+ // match the full lineage of the shareduid this method should be overloaded to
+ // accept the expected response for the uid query.
+ PackageManager pm = getPm();
+ int matchByName = pm.checkSignatures(pkg1.getPackageName(), pkg2.getPackageName());
+ int pkg1Uid = pm.getApplicationInfo(pkg1.getPackageName(), 0).uid;
+ int pkg2Uid = pm.getApplicationInfo(pkg2.getPackageName(), 0).uid;
+ int matchByUid = pm.checkSignatures(pkg1Uid, pkg2Uid);
+ assertEquals(expMatchResult, matchByName);
+ assertEquals(expMatchResult, matchByUid);
}
} finally {
if (cleanUp) {
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 164c372768c0..bfcf52af80bf 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -100,12 +100,12 @@ public class ImeInsetsSourceConsumerTest {
// test if setVisibility can show IME
mImeConsumer.onWindowFocusGained();
mImeConsumer.applyImeVisibility(true);
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test if setVisibility can hide IME
mImeConsumer.applyImeVisibility(false);
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index cc85332590ba..d4c256972b28 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -245,14 +245,14 @@ public class InsetsControllerTest {
mController.applyImeVisibility(true /* setVisible */);
mController.show(Type.all());
// quickly jump to final state by cancelling it.
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.applyImeVisibility(false /* setVisible */);
mController.hide(Type.all());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -268,10 +268,10 @@ public class InsetsControllerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
mController.applyImeVisibility(true);
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.applyImeVisibility(false);
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
});
@@ -291,7 +291,7 @@ public class InsetsControllerTest {
mController.hide(types);
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
@@ -302,7 +302,7 @@ public class InsetsControllerTest {
mController.show(types);
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -321,21 +321,21 @@ public class InsetsControllerTest {
int types = Type.navigationBars() | Type.systemBars();
// test show select types.
mController.show(types);
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test hide all
mController.hide(Type.all());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single show
mController.show(Type.navigationBars());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -363,7 +363,7 @@ public class InsetsControllerTest {
mController.hide(Type.systemBars());
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -372,7 +372,7 @@ public class InsetsControllerTest {
mController.show(Type.systemBars());
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -383,7 +383,7 @@ public class InsetsControllerTest {
mController.hide(Type.navigationBars());
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -391,7 +391,7 @@ public class InsetsControllerTest {
mController.hide(Type.systemBars());
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -411,13 +411,13 @@ public class InsetsControllerTest {
// show two at a time and hide one by one.
mController.show(types);
mController.hide(Type.navigationBars());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.hide(Type.systemBars());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -431,7 +431,7 @@ public class InsetsControllerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.hide(Type.statusBars());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
@@ -446,7 +446,7 @@ public class InsetsControllerTest {
// Gaining control
mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
});
@@ -468,7 +468,7 @@ public class InsetsControllerTest {
mController.onControlsChanged(createSingletonControl(ITYPE_IME));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
});
@@ -489,7 +489,7 @@ public class InsetsControllerTest {
mController.show(ime(), true /* fromIme */);
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME));
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ITYPE_IME).isRequestedVisible());
assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
});
@@ -658,7 +658,7 @@ public class InsetsControllerTest {
mController.getState().getSource(ITYPE_IME).getFrame());
assertNotEquals(new Rect(4, 5, 6, 7),
mController.getState().getSource(ITYPE_IME).getVisibleFrame());
- mController.cancelExistingAnimation();
+ mController.cancelExistingAnimations();
assertEquals(new Rect(0, 1, 2, 3),
mController.getState().getSource(ITYPE_IME).getFrame());
assertEquals(new Rect(4, 5, 6, 7),
diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java
index c91ff0d099cf..ff9fd4187272 100644
--- a/media/java/android/media/AudioMetadata.java
+++ b/media/java/android/media/AudioMetadata.java
@@ -166,10 +166,25 @@ public final class AudioMetadata {
*
* A Boolean value which is true if Atmos is present in an E-AC3 stream.
*/
+
+ // Since Boolean isn't handled by Parceling, we translate
+ // internally to KEY_HAS_ATMOS when sending through JNI.
+ // Consider deprecating this key for KEY_HAS_ATMOS in the future.
+ //
@NonNull public static final Key<Boolean> KEY_ATMOS_PRESENT =
createKey("atmos-present", Boolean.class);
/**
+ * A key representing the presence of Atmos in an E-AC3 stream.
+ *
+ * An Integer value which is nonzero if Atmos is present in an E-AC3 stream.
+ * The integer representation is used for communication to the native side.
+ * @hide
+ */
+ @NonNull public static final Key<Integer> KEY_HAS_ATMOS =
+ createKey("has-atmos", Integer.class);
+
+ /**
* A key representing the audio encoding used for the stream.
* This is the same encoding used in {@link AudioFormat#getEncoding()}.
*
@@ -731,6 +746,15 @@ public final class AudioMetadata {
Log.e(TAG, "Failed to unpack value for map");
return null;
}
+
+ // Special handling of KEY_ATMOS_PRESENT.
+ if (key.equals(Format.KEY_HAS_ATMOS.getName())
+ && value.first == Format.KEY_HAS_ATMOS.getValueClass()) {
+ ret.set(Format.KEY_ATMOS_PRESENT,
+ (Boolean) ((int) value.second != 0)); // Translate Integer to Boolean
+ continue; // Should we store both keys in the java table?
+ }
+
ret.set(createKey(key, value.first), value.first.cast(value.second));
}
return ret;
@@ -746,11 +770,19 @@ public final class AudioMetadata {
return false;
}
for (Key<?> key : obj.keySet()) {
+ Object value = obj.get(key);
+
+ // Special handling of KEY_ATMOS_PRESENT.
+ if (key == Format.KEY_ATMOS_PRESENT) {
+ key = Format.KEY_HAS_ATMOS;
+ value = (Integer) ((boolean) value ? 1 : 0); // Translate Boolean to Integer
+ }
+
if (!strDataPackage.pack(output, key.getName())) {
Log.i(TAG, "Failed to pack key: " + key.getName());
return false;
}
- if (!OBJECT_PACKAGE.pack(output, new Pair<>(key.getValueClass(), obj.get(key)))) {
+ if (!OBJECT_PACKAGE.pack(output, new Pair<>(key.getValueClass(), value))) {
Log.i(TAG, "Failed to pack value: " + obj.get(key));
return false;
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 6c9013fe37c4..e3c7336905d1 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -170,8 +170,7 @@ public final class MediaRouter2Manager {
public MediaController getMediaControllerForRoutingSession(
@NonNull RoutingSessionInfo sessionInfo) {
for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
- String volumeControlId = controller.getPlaybackInfo().getVolumeControlId();
- if (TextUtils.equals(sessionInfo.getId(), volumeControlId)) {
+ if (areSessionsMatched(controller, sessionInfo)) {
return controller;
}
}
@@ -206,6 +205,37 @@ public final class MediaRouter2Manager {
}
/**
+ * Gets available routes for the given routing session.
+ * The returned routes can be passed to
+ * {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} for transferring the routing session.
+ *
+ * @param sessionInfo the routing session that would be transferred
+ */
+ @NonNull
+ public List<MediaRoute2Info> getAvailableRoutesForRoutingSession(
+ @NonNull RoutingSessionInfo sessionInfo) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ List<MediaRoute2Info> routes = new ArrayList<>();
+
+ String packageName = sessionInfo.getClientPackageName();
+ List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+ if (preferredFeatures == null) {
+ preferredFeatures = Collections.emptyList();
+ }
+ synchronized (mRoutesLock) {
+ for (MediaRoute2Info route : mRoutes.values()) {
+ if (route.isSystemRoute() || route.hasAnyFeatures(preferredFeatures)
+ || sessionInfo.getSelectedRoutes().contains(route.getId())
+ || sessionInfo.getTransferableRoutes().contains(route.getId())) {
+ routes.add(route);
+ }
+ }
+ }
+ return routes;
+ }
+
+ /**
* Gets the system routing session associated with no specific application.
*/
@NonNull
@@ -219,6 +249,33 @@ public final class MediaRouter2Manager {
}
/**
+ * Gets the routing session of a media session.
+ * If the session is using {#link PlaybackInfo#PLAYBACK_TYPE_LOCAL local playback},
+ * the system routing session is returned.
+ * If the session is using {#link PlaybackInfo#PLAYBACK_TYPE_REMOTE remote playback},
+ * it returns the corresponding routing session or {@code null} if it's unavailable.
+ */
+ @Nullable
+ public RoutingSessionInfo getRoutingSessionForMediaController(MediaController mediaController) {
+ MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo();
+ if (playbackInfo == null) {
+ return null;
+ }
+ if (playbackInfo.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+ return new RoutingSessionInfo.Builder(getSystemRoutingSession())
+ .setClientPackageName(mediaController.getPackageName())
+ .build();
+ }
+ for (RoutingSessionInfo sessionInfo : getActiveSessions()) {
+ if (!sessionInfo.isSystemSession()
+ && areSessionsMatched(mediaController, sessionInfo)) {
+ return sessionInfo;
+ }
+ }
+ return null;
+ }
+
+ /**
* Gets routing sessions of an application with the given package name.
* The first element of the returned list is the system routing session.
*
@@ -762,6 +819,27 @@ public final class MediaRouter2Manager {
}
}
+ private boolean areSessionsMatched(MediaController mediaController,
+ RoutingSessionInfo sessionInfo) {
+ MediaController.PlaybackInfo playbackInfo = mediaController.getPlaybackInfo();
+ if (playbackInfo == null) {
+ return false;
+ }
+
+ String volumeControlId = playbackInfo.getVolumeControlId();
+ if (volumeControlId == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(volumeControlId, sessionInfo.getId())) {
+ return true;
+ }
+ // Workaround for provider not being able to know the unique session ID.
+ return TextUtils.equals(volumeControlId, sessionInfo.getOriginalId())
+ && TextUtils.equals(mediaController.getPackageName(),
+ sessionInfo.getOwnerPackageName());
+ }
+
private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
synchronized (sLock) {
return routeIds.stream().map(mRoutes::get)
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 608e29a7a6ca..edf1fc58ecf5 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -50,6 +50,7 @@ public final class RoutingSessionInfo implements Parcelable {
final String mId;
final CharSequence mName;
+ final String mOwnerPackageName;
final String mClientPackageName;
@Nullable
final String mProviderId;
@@ -71,6 +72,7 @@ public final class RoutingSessionInfo implements Parcelable {
mId = builder.mId;
mName = builder.mName;
+ mOwnerPackageName = builder.mOwnerPackageName;
mClientPackageName = builder.mClientPackageName;
mProviderId = builder.mProviderId;
@@ -96,6 +98,7 @@ public final class RoutingSessionInfo implements Parcelable {
mId = ensureString(src.readString());
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
+ mOwnerPackageName = src.readString();
mClientPackageName = ensureString(src.readString());
mProviderId = src.readString();
@@ -159,6 +162,15 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
+ * Gets the package name of the session owner.
+ * @hide
+ */
+ @Nullable
+ public String getOwnerPackageName() {
+ return mOwnerPackageName;
+ }
+
+ /**
* Gets the client package name of the session
*/
@NonNull
@@ -263,6 +275,7 @@ public final class RoutingSessionInfo implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeCharSequence(mName);
+ dest.writeString(mOwnerPackageName);
dest.writeString(mClientPackageName);
dest.writeString(mProviderId);
dest.writeStringList(mSelectedRoutes);
@@ -288,6 +301,7 @@ public final class RoutingSessionInfo implements Parcelable {
RoutingSessionInfo other = (RoutingSessionInfo) obj;
return Objects.equals(mId, other.mId)
&& Objects.equals(mName, other.mName)
+ && Objects.equals(mOwnerPackageName, other.mOwnerPackageName)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
&& Objects.equals(mProviderId, other.mProviderId)
&& Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
@@ -301,7 +315,7 @@ public final class RoutingSessionInfo implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mId, mName, mClientPackageName, mProviderId,
+ return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId,
mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
mVolumeMax, mVolumeHandling, mVolume);
}
@@ -356,6 +370,7 @@ public final class RoutingSessionInfo implements Parcelable {
// TODO: Reorder these (important ones first)
final String mId;
CharSequence mName;
+ String mOwnerPackageName;
String mClientPackageName;
String mProviderId;
final List<String> mSelectedRoutes;
@@ -440,6 +455,17 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
+ * Sets the package name of the session owner. It is expected to be called by the system.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setOwnerPackageName(@Nullable String packageName) {
+ mOwnerPackageName = packageName;
+ return this;
+ }
+
+ /**
* Sets the client package name of the session.
*
* @hide
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
index 9e194fb49d3a..288e5cf13c2e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
@@ -73,7 +73,7 @@ public class CarNavigationBarController {
}
/**
- * Hides all navigation bars.
+ * Hides all system bars.
*/
public void hideBars() {
if (mTopView != null) {
@@ -85,7 +85,7 @@ public class CarNavigationBarController {
}
/**
- * Shows all navigation bars.
+ * Shows all system bars.
*/
public void showBars() {
if (mTopView != null) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
index 20fcca0d0220..aeb1d39599db 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
@@ -29,41 +29,40 @@ import android.widget.FrameLayout;
import com.android.car.notification.R;
import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
* A controller for SysUI's HUN display.
*/
@Singleton
public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer {
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
- private final Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
+ private final OverlayViewGlobalStateController mOverlayViewGlobalStateController;
private final ViewGroup mWindow;
private final FrameLayout mHeadsUpContentFrame;
- private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
-
@Inject
CarHeadsUpNotificationSystemContainer(Context context,
@Main Resources resources,
CarDeviceProvisionedController deviceProvisionedController,
WindowManager windowManager,
- Lazy<NotificationPanelViewController> notificationPanelViewControllerLazy) {
+ OverlayViewGlobalStateController overlayViewGlobalStateController) {
mCarDeviceProvisionedController = deviceProvisionedController;
- mNotificationPanelViewControllerLazy = notificationPanelViewControllerLazy;
+ mOverlayViewGlobalStateController = overlayViewGlobalStateController;
boolean showOnBottom = resources.getBoolean(R.bool.config_showHeadsUpNotificationOnBottom);
+ // Use TYPE_STATUS_BAR_SUB_PANEL window type since we need to find a window that is above
+ // status bar but below navigation bar.
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
@@ -78,15 +77,11 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica
windowManager.addView(mWindow, lp);
mWindow.setVisibility(View.INVISIBLE);
mHeadsUpContentFrame = mWindow.findViewById(R.id.headsup_content);
-
- mEnableHeadsUpNotificationWhenNotificationShadeOpen = resources.getBoolean(
- R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
}
private void animateShow() {
- if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen
- || !mNotificationPanelViewControllerLazy.get().isPanelExpanded())
- && mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
+ if (mCarDeviceProvisionedController.isCurrentUserFullySetup()
+ && mOverlayViewGlobalStateController.shouldShowHUN()) {
mWindow.setVisibility(View.VISIBLE);
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index cb9539ad5b1d..1738091d14c9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -73,6 +73,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
private final CarNotificationListener mCarNotificationListener;
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
private final StatusBarStateController mStatusBarStateController;
+ private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
private float mInitialBackgroundAlpha;
private float mBackgroundAlphaDiff;
@@ -144,6 +145,10 @@ public class NotificationPanelViewController extends OverlayPanelViewController
+ " percentage");
}
mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
+
+ mEnableHeadsUpNotificationWhenNotificationShadeOpen = mResources.getBoolean(
+ com.android.car.notification.R.bool
+ .config_enableHeadsUpNotificationWhenNotificationShadeOpen);
}
@Override
@@ -151,6 +156,16 @@ public class NotificationPanelViewController extends OverlayPanelViewController
reinflate();
}
+ @Override
+ protected boolean shouldShowNavigationBar() {
+ return true;
+ }
+
+ @Override
+ protected boolean shouldShowHUN() {
+ return mEnableHeadsUpNotificationWhenNotificationShadeOpen;
+ }
+
/** Reinflates the view. */
public void reinflate() {
ViewGroup container = (ViewGroup) getLayout();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
index 0fe985684543..45808a8a0b3e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayPanelViewController.java
@@ -375,10 +375,10 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
}
if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
- getOverlayViewGlobalStateController().setWindowVisible(true);
+ getOverlayViewGlobalStateController().showView(/* panelViewController= */ this);
}
if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
- getOverlayViewGlobalStateController().setWindowVisible(false);
+ getOverlayViewGlobalStateController().hideView(/* panelViewController= */ this);
}
getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
getOverlayViewGlobalStateController().setWindowFocusable(visible);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
index 87f20208476b..30e26578bd73 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
@@ -54,7 +54,6 @@ public class OverlayViewController {
mOverlayViewGlobalStateController.hideView(/* viewController= */ this, this::hide);
}
-
/**
* Inflate layout owned by controller.
*/
@@ -72,7 +71,7 @@ public class OverlayViewController {
}
/**
- * Returns [@code true} if layout owned by controller has been inflated.
+ * Returns {@code true} if layout owned by controller has been inflated.
*/
public final boolean isInflated() {
return mLayout != null;
@@ -125,4 +124,18 @@ public class OverlayViewController {
protected final OverlayViewGlobalStateController getOverlayViewGlobalStateController() {
return mOverlayViewGlobalStateController;
}
+
+ /**
+ * Returns {@code true} if heads up notifications should be displayed over this view.
+ */
+ protected boolean shouldShowHUN() {
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if navigation bar should be displayed over this view.
+ */
+ protected boolean shouldShowNavigationBar() {
+ return false;
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index 290505f5042a..70260b0d4cef 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -16,14 +16,17 @@
package com.android.systemui.car.window;
+import android.annotation.Nullable;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -39,11 +42,17 @@ import javax.inject.Singleton;
*/
@Singleton
public class OverlayViewGlobalStateController {
+ private static final boolean DEBUG = false;
private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName();
+ private static final int UNKNOWN_Z_ORDER = -1;
private final SystemUIOverlayWindowController mSystemUIOverlayWindowController;
private final CarNavigationBarController mCarNavigationBarController;
@VisibleForTesting
- Set<String> mShownSet;
+ Map<OverlayViewController, Integer> mZOrderMap;
+ @VisibleForTesting
+ SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap;
+ @VisibleForTesting
+ OverlayViewController mHighestZOrder;
@Inject
public OverlayViewGlobalStateController(
@@ -52,7 +61,8 @@ public class OverlayViewGlobalStateController {
mSystemUIOverlayWindowController = systemUIOverlayWindowController;
mSystemUIOverlayWindowController.attach();
mCarNavigationBarController = carNavigationBarController;
- mShownSet = new HashSet<>();
+ mZOrderMap = new HashMap<>();
+ mZOrderVisibleSortedMap = new TreeMap<>();
}
/**
@@ -66,51 +76,127 @@ public class OverlayViewGlobalStateController {
}
/**
- * Show content in Overlay Window.
+ * Show content in Overlay Window using {@link OverlayPanelViewController}.
+ *
+ * This calls {@link OverlayViewGlobalStateController#showView(OverlayViewController, Runnable)}
+ * where the runnable is nullified since the actual showing of the panel is handled by the
+ * controller itself.
*/
- public void showView(OverlayViewController viewController, Runnable show) {
- if (mShownSet.isEmpty()) {
- mCarNavigationBarController.hideBars();
+ public void showView(OverlayPanelViewController panelViewController) {
+ showView(panelViewController, /* show= */ null);
+ }
+
+ /**
+ * Show content in Overlay Window using {@link OverlayViewController}.
+ */
+ public void showView(OverlayViewController viewController, @Nullable Runnable show) {
+ debugLog();
+ if (mZOrderVisibleSortedMap.isEmpty()) {
setWindowVisible(true);
}
+ if (!(viewController instanceof OverlayPanelViewController)) {
+ inflateView(viewController);
+ }
- inflateView(viewController);
+ if (show != null) {
+ show.run();
+ }
- show.run();
- mShownSet.add(viewController.getClass().getName());
+ updateInternalsWhenShowingView(viewController);
+ refreshNavigationBarVisibility();
Log.d(TAG, "Content shown: " + viewController.getClass().getName());
+ debugLog();
+ }
+
+ private void updateInternalsWhenShowingView(OverlayViewController viewController) {
+ int zOrder;
+ if (mZOrderMap.containsKey(viewController)) {
+ zOrder = mZOrderMap.get(viewController);
+ } else {
+ zOrder = mSystemUIOverlayWindowController.getBaseLayout().indexOfChild(
+ viewController.getLayout());
+ mZOrderMap.put(viewController, zOrder);
+ }
+
+ mZOrderVisibleSortedMap.put(zOrder, viewController);
+
+ refreshHighestZOrderWhenShowingView(viewController);
+ }
+
+ private void refreshHighestZOrderWhenShowingView(OverlayViewController viewController) {
+ if (mZOrderMap.getOrDefault(mHighestZOrder, UNKNOWN_Z_ORDER) < mZOrderMap.get(
+ viewController)) {
+ mHighestZOrder = viewController;
+ }
+ }
+
+ /**
+ * Hide content in Overlay Window using {@link OverlayPanelViewController}.
+ *
+ * This calls {@link OverlayViewGlobalStateController#hideView(OverlayViewController, Runnable)}
+ * where the runnable is nullified since the actual hiding of the panel is handled by the
+ * controller itself.
+ */
+ public void hideView(OverlayPanelViewController panelViewController) {
+ hideView(panelViewController, /* hide= */ null);
}
/**
- * Hide content in Overlay Window.
+ * Hide content in Overlay Window using {@link OverlayViewController}.
*/
- public void hideView(OverlayViewController viewController, Runnable hide) {
+ public void hideView(OverlayViewController viewController, @Nullable Runnable hide) {
+ debugLog();
if (!viewController.isInflated()) {
Log.d(TAG, "Content cannot be hidden since it isn't inflated: "
+ viewController.getClass().getName());
return;
}
- if (!mShownSet.contains(viewController.getClass().getName())) {
- Log.d(TAG, "Content cannot be hidden since it isn't shown: "
+ if (!mZOrderMap.containsKey(viewController)) {
+ Log.d(TAG, "Content cannot be hidden since it has never been shown: "
+ + viewController.getClass().getName());
+ return;
+ }
+ if (!mZOrderVisibleSortedMap.containsKey(mZOrderMap.get(viewController))) {
+ Log.d(TAG, "Content cannot be hidden since it isn't currently shown: "
+ viewController.getClass().getName());
return;
}
- hide.run();
- mShownSet.remove(viewController.getClass().getName());
+ if (hide != null) {
+ hide.run();
+ }
- if (mShownSet.isEmpty()) {
- mCarNavigationBarController.showBars();
+ mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
+ refreshHighestZOrderWhenHidingView(viewController);
+ refreshNavigationBarVisibility();
+
+ if (mZOrderVisibleSortedMap.isEmpty()) {
setWindowVisible(false);
}
Log.d(TAG, "Content hidden: " + viewController.getClass().getName());
+ debugLog();
+ }
+
+ private void refreshHighestZOrderWhenHidingView(OverlayViewController viewController) {
+ if (mZOrderVisibleSortedMap.isEmpty()) {
+ mHighestZOrder = null;
+ return;
+ }
+ if (!mHighestZOrder.equals(viewController)) {
+ return;
+ }
+
+ mHighestZOrder = mZOrderVisibleSortedMap.get(mZOrderVisibleSortedMap.lastKey());
}
- /** Sets the window visibility state. */
- public void setWindowVisible(boolean expanded) {
- mSystemUIOverlayWindowController.setWindowVisible(expanded);
+ private void refreshNavigationBarVisibility() {
+ if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) {
+ mCarNavigationBarController.showBars();
+ } else {
+ mCarNavigationBarController.hideBars();
+ }
}
/** Returns {@code true} is the window is visible. */
@@ -118,13 +204,14 @@ public class OverlayViewGlobalStateController {
return mSystemUIOverlayWindowController.isWindowVisible();
}
- /** Sets the focusable flag of the sysui overlawy window. */
- public void setWindowFocusable(boolean focusable) {
- mSystemUIOverlayWindowController.setWindowFocusable(focusable);
+ private void setWindowVisible(boolean visible) {
+ mSystemUIOverlayWindowController.setWindowVisible(visible);
}
- /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
- * sysui overlay window */
+ /**
+ * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
+ * sysui overlay window.
+ */
public void setWindowNeedsInput(boolean needsInput) {
mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
}
@@ -134,10 +221,34 @@ public class OverlayViewGlobalStateController {
return mSystemUIOverlayWindowController.isWindowFocusable();
}
+ /** Sets the focusable flag of the sysui overlawy window. */
+ public void setWindowFocusable(boolean focusable) {
+ mSystemUIOverlayWindowController.setWindowFocusable(focusable);
+ }
+
/** Inflates the view controlled by the given view controller. */
public void inflateView(OverlayViewController viewController) {
if (!viewController.isInflated()) {
viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
}
}
+
+ /**
+ * Return {@code true} if OverlayWindow is in a state where HUNs should be displayed above it.
+ */
+ public boolean shouldShowHUN() {
+ return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN();
+ }
+
+ private void debugLog() {
+ if (!DEBUG) {
+ return;
+ }
+
+ Log.d(TAG, "mHighestZOrder: " + mHighestZOrder);
+ Log.d(TAG, "mZOrderVisibleSortedMap.size(): " + mZOrderVisibleSortedMap.size());
+ Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap);
+ Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size());
+ Log.d(TAG, "mZOrderMap: " + mZOrderMap);
+ }
}
diff --git a/packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml b/packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml
new file mode 100644
index 000000000000..03fe0e4fcf2e
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/overlay_view_global_state_controller_test.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Fullscreen views in sysui should be listed here in increasing Z order. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ViewStub android:id="@+id/overlay_view_controller_stub_1"
+ android:inflatedId="@+id/overlay_view_controller_1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/overlay_view_controller_stub"/>
+
+ <ViewStub android:id="@+id/overlay_view_controller_stub_2"
+ android:inflatedId="@+id/overlay_view_controller_2"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/overlay_view_controller_stub"/>
+
+ <ViewStub android:id="@+id/overlay_view_controller_stub_3"
+ android:inflatedId="@+id/overlay_view_controller_3"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/overlay_view_controller_stub"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
index a2192af14758..1b4621f1c279 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
@@ -16,9 +16,10 @@
package com.android.systemui.car.keyguard;
-import static com.google.common.truth.Truth.assertThat;
-
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -29,7 +30,6 @@ import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.LockPatternUtils;
@@ -40,7 +40,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
-import com.android.systemui.car.window.SystemUIOverlayWindowController;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
@@ -51,6 +50,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -61,28 +61,20 @@ import dagger.Lazy;
public class CarKeyguardViewControllerTest extends SysuiTestCase {
private TestableCarKeyguardViewController mCarKeyguardViewController;
- private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
- private ViewGroup mBaseLayout;
@Mock
+ private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+ @Mock
private KeyguardBouncer mBouncer;
@Mock
private CarNavigationBarController mCarNavigationBarController;
@Mock
- private SystemUIOverlayWindowController mSystemUIOverlayWindowController;
- @Mock
private CarKeyguardViewController.OnKeyguardCancelClickedListener mCancelClickedListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mOverlayViewGlobalStateController = new OverlayViewGlobalStateController(
- mCarNavigationBarController, mSystemUIOverlayWindowController);
- mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
- R.layout.sysui_overlay_window, /* root= */ null);
- when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
-
mCarKeyguardViewController = new TestableCarKeyguardViewController(
mContext,
Handler.getMain(),
@@ -98,6 +90,8 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase {
mock(FalsingManager.class),
() -> mock(KeyguardBypassController.class)
);
+ mCarKeyguardViewController.inflate((ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.sysui_overlay_window, /* root= */ null));
}
@Test
@@ -113,8 +107,7 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase {
when(mBouncer.isSecure()).thenReturn(true);
mCarKeyguardViewController.show(/* options= */ null);
- assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo(
- View.VISIBLE);
+ verify(mOverlayViewGlobalStateController).showView(eq(mCarKeyguardViewController), any());
}
@Test
@@ -130,8 +123,17 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase {
when(mBouncer.isSecure()).thenReturn(false);
mCarKeyguardViewController.show(/* options= */ null);
- assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo(
- View.GONE);
+ // Here we check for both showView and hideView since the current implementation of show
+ // with bouncer being not secure has the following method execution orders:
+ // 1) show -> start -> showView
+ // 2) show -> reset -> dismissAndCollapse -> hide -> stop -> hideView
+ // Hence, we want to make sure that showView is called before hideView and not in any
+ // other combination.
+ InOrder inOrder = inOrder(mOverlayViewGlobalStateController);
+ inOrder.verify(mOverlayViewGlobalStateController).showView(eq(mCarKeyguardViewController),
+ any());
+ inOrder.verify(mOverlayViewGlobalStateController).hideView(eq(mCarKeyguardViewController),
+ any());
}
@Test
@@ -156,8 +158,11 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase {
mCarKeyguardViewController.show(/* options= */ null);
mCarKeyguardViewController.hide(/* startTime= */ 0, /* fadeoutDelay= */ 0);
- assertThat(mBaseLayout.findViewById(R.id.keyguard_container).getVisibility()).isEqualTo(
- View.GONE);
+ InOrder inOrder = inOrder(mOverlayViewGlobalStateController);
+ inOrder.verify(mOverlayViewGlobalStateController).showView(eq(mCarKeyguardViewController),
+ any());
+ inOrder.verify(mOverlayViewGlobalStateController).hideView(eq(mCarKeyguardViewController),
+ any());
}
@Test
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
index 6ac72a681bfe..ccaeb458fe54 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
@@ -28,9 +28,9 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.window.OverlayViewGlobalStateController;
import org.junit.Before;
import org.junit.Test;
@@ -42,12 +42,11 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper
@SmallTest
public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
- private CarHeadsUpNotificationSystemContainer mDefaultController;
- private CarHeadsUpNotificationSystemContainer mOverrideEnabledController;
+ private CarHeadsUpNotificationSystemContainer mCarHeadsUpNotificationSystemContainer;
@Mock
private CarDeviceProvisionedController mCarDeviceProvisionedController;
@Mock
- private NotificationPanelViewController mNotificationPanelViewController;
+ private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
@Mock
private WindowManager mWindowManager;
@@ -58,76 +57,63 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ MockitoAnnotations.initMocks(/* testClass= */this);
- when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
- when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
- when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(false);
+ when(mOverlayViewGlobalStateController.shouldShowHUN()).thenReturn(true);
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
TestableResources testableResources = mContext.getOrCreateTestableResources();
- testableResources.addOverride(
- R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, false);
-
- mDefaultController = new CarHeadsUpNotificationSystemContainer(mContext,
- testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
- () -> mNotificationPanelViewController);
-
- testableResources.addOverride(
- R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, true);
-
- mOverrideEnabledController = new CarHeadsUpNotificationSystemContainer(mContext,
+ mCarHeadsUpNotificationSystemContainer = new CarHeadsUpNotificationSystemContainer(mContext,
testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
- () -> mNotificationPanelViewController);
+ mOverlayViewGlobalStateController);
}
@Test
public void testDisplayNotification_firstNotification_isVisible() {
- mDefaultController.displayNotification(mNotificationView);
- assertThat(mDefaultController.isVisible()).isTrue();
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue();
}
@Test
public void testRemoveNotification_lastNotification_isInvisible() {
- mDefaultController.displayNotification(mNotificationView);
- mDefaultController.removeNotification(mNotificationView);
- assertThat(mDefaultController.isVisible()).isFalse();
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ mCarHeadsUpNotificationSystemContainer.removeNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isFalse();
}
@Test
public void testRemoveNotification_nonLastNotification_isVisible() {
- mDefaultController.displayNotification(mNotificationView);
- mDefaultController.displayNotification(mNotificationView2);
- mDefaultController.removeNotification(mNotificationView);
- assertThat(mDefaultController.isVisible()).isTrue();
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView2);
+ mCarHeadsUpNotificationSystemContainer.removeNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue();
}
@Test
- public void testDisplayNotification_userSetupInProgress_isInvisible() {
- when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(true);
- mDefaultController.displayNotification(mNotificationView);
- assertThat(mDefaultController.isVisible()).isFalse();
+ public void testDisplayNotification_userFullySetupTrue_isInvisible() {
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue();
}
@Test
- public void testDisplayNotification_userSetupIncomplete_isInvisible() {
- when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
- mDefaultController.displayNotification(mNotificationView);
- assertThat(mDefaultController.isVisible()).isFalse();
+ public void testDisplayNotification_userFullySetupFalse_isInvisible() {
+ when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(false);
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isFalse();
}
@Test
- public void testDisplayNotification_notificationPanelExpanded_isInvisible() {
- when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
- mDefaultController.displayNotification(mNotificationView);
- assertThat(mDefaultController.isVisible()).isFalse();
+ public void testDisplayNotification_overlayWindowStateShouldShowHUNFalse_isInvisible() {
+ when(mOverlayViewGlobalStateController.shouldShowHUN()).thenReturn(false);
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isFalse();
}
@Test
- public void testDisplayNotification_notificationPanelExpandedEnabledHUNWhenOpen_isVisible() {
- when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
- mOverrideEnabledController.displayNotification(mNotificationView);
- assertThat(mOverrideEnabledController.isVisible()).isTrue();
+ public void testDisplayNotification_overlayWindowStateShouldShowHUNTrue_isVisible() {
+ mCarHeadsUpNotificationSystemContainer.displayNotification(mNotificationView);
+ assertThat(mCarHeadsUpNotificationSystemContainer.isVisible()).isTrue();
}
}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
index 8d705a8cca1f..45a05ac69bd7 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
@@ -339,7 +339,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
mOverlayPanelViewController.setPanelVisible(true);
- verify(mOverlayViewGlobalStateController).setWindowVisible(true);
+ verify(mOverlayViewGlobalStateController).showView(mOverlayPanelViewController);
}
@Test
@@ -349,7 +349,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
mOverlayPanelViewController.setPanelVisible(true);
- verify(mOverlayViewGlobalStateController, never()).setWindowVisible(true);
+ verify(mOverlayViewGlobalStateController, never()).showView(mOverlayPanelViewController);
}
@Test
@@ -377,7 +377,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
mOverlayPanelViewController.setPanelVisible(false);
- verify(mOverlayViewGlobalStateController).setWindowVisible(false);
+ verify(mOverlayViewGlobalStateController).hideView(mOverlayPanelViewController);
}
@Test
@@ -387,7 +387,7 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
mOverlayPanelViewController.setPanelVisible(false);
- verify(mOverlayViewGlobalStateController, never()).setWindowVisible(false);
+ verify(mOverlayViewGlobalStateController, never()).hideView(mOverlayPanelViewController);
}
@Test
@@ -428,10 +428,6 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
private static class TestOverlayPanelViewController extends OverlayPanelViewController {
- private boolean mShouldAnimateCollapsePanel;
- private boolean mShouldAnimateExpandPanel;
- private boolean mShouldAllowClosingScroll;
-
boolean mOnAnimateCollapsePanelCalled;
boolean mAnimateCollapsePanelCalled;
boolean mOnAnimateExpandPanelCalled;
@@ -440,6 +436,9 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
boolean mOnExpandAnimationEndCalled;
boolean mOnOpenScrollStartEnd;
List<Integer> mOnScrollHeights;
+ private boolean mShouldAnimateCollapsePanel;
+ private boolean mShouldAnimateExpandPanel;
+ private boolean mShouldAllowClosingScroll;
TestOverlayPanelViewController(
Context context,
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
index 25dd4f502fb7..9e6e616e3ccf 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
@@ -24,25 +24,33 @@ import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.view.ViewStub;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
+import com.android.systemui.tests.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Arrays;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
- private static final String MOCK_OVERLAY_VIEW_CONTROLLER_NAME = "OverlayViewController";
+ private static final int OVERLAY_VIEW_CONTROLLER_1_Z_ORDER = 0;
+ private static final int OVERLAY_VIEW_CONTROLLER_2_Z_ORDER = 1;
+ private static final int OVERLAY_PANEL_VIEW_CONTROLLER_Z_ORDER = 2;
private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
private ViewGroup mBaseLayout;
@@ -54,7 +62,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
@Mock
private OverlayViewMediator mOverlayViewMediator;
@Mock
- private OverlayViewController mOverlayViewController;
+ private OverlayViewController mOverlayViewController1;
+ @Mock
+ private OverlayViewController mOverlayViewController2;
+ @Mock
+ private OverlayPanelViewController mOverlayPanelViewController;
@Mock
private Runnable mRunnable;
@@ -62,14 +74,15 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(/* testClass= */ this);
+ mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.overlay_view_global_state_controller_test, /* root= */ null);
+
+ when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
+
mOverlayViewGlobalStateController = new OverlayViewGlobalStateController(
mCarNavigationBarController, mSystemUIOverlayWindowController);
verify(mSystemUIOverlayWindowController).attach();
-
- mBaseLayout = new FrameLayout(mContext);
-
- when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
}
@Test
@@ -87,182 +100,445 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
}
@Test
- public void showView_nothingAlreadyShown_navigationBarsHidden() {
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ public void showView_nothingAlreadyShown_shouldShowNavBarFalse_navigationBarsHidden() {
+ setupOverlayViewController1();
+ when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
verify(mCarNavigationBarController).hideBars();
}
@Test
- public void showView_nothingAlreadyShown_windowIsExpanded() {
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ public void showView_nothingAlreadyShown_shouldShowNavBarTrue_navigationBarsShown() {
+ setupOverlayViewController1();
+ when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mCarNavigationBarController).showBars();
+ }
+
+ @Test
+ public void showView_nothingAlreadyShown_windowIsSetVisible() {
+ setupOverlayViewController1();
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
verify(mSystemUIOverlayWindowController).setWindowVisible(true);
}
@Test
- public void showView_somethingAlreadyShown_navigationBarsHidden() {
- mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+ public void showView_nothingAlreadyShown_newHighestZOrder() {
+ setupOverlayViewController1();
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo(
+ mOverlayViewController1);
+ }
+
+ @Test
+ public void showView_nothingAlreadyShown_newHighestZOrder_isVisible() {
+ setupOverlayViewController1();
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey(
+ OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isTrue();
+ }
+
+ @Test
+ public void showView_newHighestZOrder() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
- verify(mCarNavigationBarController, never()).hideBars();
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo(
+ mOverlayViewController2);
}
@Test
- public void showView_somethingAlreadyShown_windowIsExpanded() {
- mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+ public void showView_newHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ verify(mCarNavigationBarController).hideBars();
+ }
+
+ @Test
+ public void showView_newHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+ verify(mCarNavigationBarController).showBars();
+ }
+
+ @Test
+ public void showView_newHighestZOrder_correctViewsShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.keySet().toArray())
+ .isEqualTo(Arrays.asList(OVERLAY_VIEW_CONTROLLER_1_Z_ORDER,
+ OVERLAY_VIEW_CONTROLLER_2_Z_ORDER).toArray());
+ }
+
+ @Test
+ public void showView_oldHighestZOrder() {
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo(
+ mOverlayViewController2);
+ }
+
+ @Test
+ public void showView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarsHidden() {
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+ when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mCarNavigationBarController).hideBars();
+ }
+
+ @Test
+ public void showView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarsShown() {
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+ when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ verify(mCarNavigationBarController).showBars();
+ }
+
+ @Test
+ public void showView_oldHighestZOrder_correctViewsShown() {
+ setupOverlayViewController1();
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.keySet().toArray())
+ .isEqualTo(Arrays.asList(OVERLAY_VIEW_CONTROLLER_1_Z_ORDER,
+ OVERLAY_VIEW_CONTROLLER_2_Z_ORDER).toArray());
+ }
+
+ @Test
+ public void showView_somethingAlreadyShown_windowVisibleNotCalled() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
verify(mSystemUIOverlayWindowController, never()).setWindowVisible(true);
}
@Test
public void showView_viewControllerNotInflated_inflateViewController() {
- when(mOverlayViewController.isInflated()).thenReturn(false);
+ setupOverlayViewController2();
+ when(mOverlayViewController2.isInflated()).thenReturn(false);
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
- verify(mOverlayViewController).inflate(mBaseLayout);
+ verify(mOverlayViewController2).inflate(mBaseLayout);
}
@Test
public void showView_viewControllerInflated_inflateViewControllerNotCalled() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
+ setupOverlayViewController2();
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable);
- verify(mOverlayViewController, never()).inflate(mBaseLayout);
+ verify(mOverlayViewController2, never()).inflate(mBaseLayout);
}
@Test
- public void showView_showRunnableCalled() {
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ public void showView_panelViewController_inflateViewControllerNotCalled() {
+ setupOverlayPanelViewController();
- verify(mRunnable).run();
+ mOverlayViewGlobalStateController.showView(mOverlayPanelViewController, mRunnable);
+
+ verify(mOverlayPanelViewController, never()).inflate(mBaseLayout);
+ verify(mOverlayPanelViewController, never()).isInflated();
}
@Test
- public void showView_overlayViewControllerAddedToShownSet() {
- mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
+ public void showView_showRunnableCalled() {
+ setupOverlayViewController1();
+
+ mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable);
- assertThat(mOverlayViewGlobalStateController.mShownSet.contains(
- mOverlayViewController.getClass().getName())).isTrue();
+ verify(mRunnable).run();
}
@Test
public void hideView_viewControllerNotInflated_hideRunnableNotCalled() {
- when(mOverlayViewController.isInflated()).thenReturn(false);
+ when(mOverlayViewController2.isInflated()).thenReturn(false);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
verify(mRunnable, never()).run();
}
@Test
public void hideView_nothingShown_hideRunnableNotCalled() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.clear();
+ when(mOverlayViewController2.isInflated()).thenReturn(true);
+ mOverlayViewGlobalStateController.mZOrderMap.clear();
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
verify(mRunnable, never()).run();
}
@Test
public void hideView_viewControllerNotShown_hideRunnableNotCalled() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ when(mOverlayViewController2.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
verify(mRunnable, never()).run();
}
@Test
public void hideView_viewControllerShown_hideRunnableCalled() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(
- mOverlayViewController.getClass().getName());
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
verify(mRunnable).run();
}
@Test
+ public void hideView_viewControllerOnlyShown_noHighestZOrder() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isNull();
+ }
+
+ @Test
public void hideView_viewControllerOnlyShown_nothingShown() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(
- mOverlayViewController.getClass().getName());
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void hideView_viewControllerOnlyShown_viewControllerNotShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsKey(
+ OVERLAY_VIEW_CONTROLLER_1_Z_ORDER)).isFalse();
+ }
+
+ @Test
+ public void hideView_newHighestZOrder_twoViewsShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo(
+ mOverlayViewController1);
+ }
+
+ @Test
+ public void hideView_newHighestZOrder_threeViewsShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ setupOverlayPanelViewController();
+ setOverlayViewControllerAsShowing(mOverlayPanelViewController);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayPanelViewController, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo(
+ mOverlayViewController2);
+ }
+
+ @Test
+ public void hideView_newHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
+
+ verify(mCarNavigationBarController).hideBars();
+ }
+
+ @Test
+ public void hideView_newHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ verify(mCarNavigationBarController).showBars();
+ }
+
+ @Test
+ public void hideView_oldHighestZOrder() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
- assertThat(mOverlayViewGlobalStateController.mShownSet.isEmpty()).isTrue();
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+ assertThat(mOverlayViewGlobalStateController.mHighestZOrder).isEqualTo(
+ mOverlayViewController2);
}
@Test
- public void hideView_viewControllerNotOnlyShown_navigationBarNotShown() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(
- mOverlayViewController.getClass().getName());
- mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+ public void hideView_oldHighestZOrder_shouldShowNavBarFalse_navigationBarHidden() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
- verify(mCarNavigationBarController, never()).showBars();
+ verify(mCarNavigationBarController).hideBars();
+ }
+
+ @Test
+ public void hideView_oldHighestZOrder_shouldShowNavBarTrue_navigationBarShown() {
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
+ when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(true);
+
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
+
+ verify(mCarNavigationBarController).showBars();
}
@Test
public void hideView_viewControllerNotOnlyShown_windowNotCollapsed() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(
- mOverlayViewController.getClass().getName());
- mOverlayViewGlobalStateController.mShownSet.add(MOCK_OVERLAY_VIEW_CONTROLLER_NAME);
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
+ setupOverlayViewController2();
+ setOverlayViewControllerAsShowing(mOverlayViewController2);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable);
verify(mSystemUIOverlayWindowController, never()).setWindowVisible(false);
}
@Test
public void hideView_viewControllerOnlyShown_navigationBarShown() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(
- mOverlayViewController.getClass().getName());
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
verify(mCarNavigationBarController).showBars();
}
@Test
public void hideView_viewControllerOnlyShown_windowCollapsed() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.mShownSet.add(
- mOverlayViewController.getClass().getName());
+ setupOverlayViewController1();
+ setOverlayViewControllerAsShowing(mOverlayViewController1);
- mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
+ mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable);
verify(mSystemUIOverlayWindowController).setWindowVisible(false);
}
@Test
public void inflateView_notInflated_inflates() {
- when(mOverlayViewController.isInflated()).thenReturn(false);
+ when(mOverlayViewController2.isInflated()).thenReturn(false);
- mOverlayViewGlobalStateController.inflateView(mOverlayViewController);
+ mOverlayViewGlobalStateController.inflateView(mOverlayViewController2);
- verify(mOverlayViewController).inflate(mBaseLayout);
+ verify(mOverlayViewController2).inflate(mBaseLayout);
}
@Test
public void inflateView_alreadyInflated_doesNotInflate() {
- when(mOverlayViewController.isInflated()).thenReturn(true);
+ when(mOverlayViewController2.isInflated()).thenReturn(true);
- mOverlayViewGlobalStateController.inflateView(mOverlayViewController);
+ mOverlayViewGlobalStateController.inflateView(mOverlayViewController2);
+
+ verify(mOverlayViewController2, never()).inflate(mBaseLayout);
+ }
+
+ private void setupOverlayViewController1() {
+ setupOverlayViewController(mOverlayViewController1, R.id.overlay_view_controller_stub_1,
+ R.id.overlay_view_controller_1);
+ }
- verify(mOverlayViewController, never()).inflate(mBaseLayout);
+ private void setupOverlayViewController2() {
+ setupOverlayViewController(mOverlayViewController2, R.id.overlay_view_controller_stub_2,
+ R.id.overlay_view_controller_2);
+ }
+
+ private void setupOverlayPanelViewController() {
+ setupOverlayViewController(mOverlayPanelViewController, R.id.overlay_view_controller_stub_3,
+ R.id.overlay_view_controller_3);
+ }
+
+ private void setupOverlayViewController(OverlayViewController overlayViewController,
+ int stubId, int inflatedId) {
+ ViewStub viewStub = mBaseLayout.findViewById(stubId);
+ View layout;
+ if (viewStub == null) {
+ layout = mBaseLayout.findViewById(inflatedId);
+ } else {
+ layout = viewStub.inflate();
+ }
+ when(overlayViewController.getLayout()).thenReturn(layout);
+ when(overlayViewController.isInflated()).thenReturn(true);
+ }
+
+ private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) {
+ mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null);
+ Mockito.reset(mCarNavigationBarController, mSystemUIOverlayWindowController);
+ when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index c4ff71940d20..ae3194df28fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -140,4 +140,15 @@ public class AppUtils {
.isSystemModule(packageName);
}
+ /**
+ * Returns a boolean indicating whether a given package is a mainline module.
+ */
+ public static boolean isMainlineModule(Context context, String packageName) {
+ final PackageManager pm = context.getPackageManager();
+ try {
+ return pm.getModuleInfo(packageName, 0 /* flags */) != null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 31ea5b40d756..887a49b95279 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -27,10 +27,13 @@ import android.util.Log;
import androidx.annotation.IntDef;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -430,7 +433,8 @@ public class LocalMediaManager implements BluetoothCallback {
cachedDeviceManager.findDevice(device);
if (cachedDevice != null) {
if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
- && !cachedDevice.isConnected()) {
+ && !cachedDevice.isConnected()
+ && isA2dpOrHearingAidDevice(cachedDevice)) {
deviceCount++;
cachedBluetoothDeviceList.add(cachedDevice);
if (deviceCount >= MAX_DISCONNECTED_DEVICE_NUM) {
@@ -454,6 +458,15 @@ public class LocalMediaManager implements BluetoothCallback {
return new ArrayList<>(mDisconnectedMediaDevices);
}
+ private boolean isA2dpOrHearingAidDevice(CachedBluetoothDevice device) {
+ for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
+ if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void onDeviceRemoved(MediaDevice device) {
if (mMediaDevices.contains(device)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 34de1528ed9a..139a12c44e0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -31,18 +31,14 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.text.TextUtils;
-import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.R;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -215,30 +211,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
*
* @return application label.
*/
- public String getClientAppLabel() {
- final String packageName = mRouteInfo.getClientPackageName();
- if (TextUtils.isEmpty(packageName)) {
- Log.d(TAG, "Client package name is empty");
- return mContext.getResources().getString(R.string.unknown);
- }
- try {
- final PackageManager packageManager = mContext.getPackageManager();
- final String appLabel = packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(packageName, 0)).toString();
- if (!TextUtils.isEmpty(appLabel)) {
- return appLabel;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "unable to find " + packageName);
- }
- return mContext.getResources().getString(R.string.unknown);
- }
-
- /**
- * Get application label from MediaDevice.
- *
- * @return application label.
- */
public int getDeviceType() {
return mType;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 42f2542e5c30..8ea5ff1bf662 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -41,7 +41,10 @@ public class PhoneMediaDevice extends MediaDevice {
private static final String TAG = "PhoneMediaDevice";
- public static final String ID = "phone_media_device_id_1";
+ public static final String PHONE_ID = "phone_media_device_id";
+ // For 3.5 mm wired headset
+ public static final String WIRED_HEADSET_ID = "wired_headset_media_device_id";
+ public static final String USB_HEADSET_ID = "usb_headset_media_device_id";
private String mSummary = "";
@@ -109,7 +112,25 @@ public class PhoneMediaDevice extends MediaDevice {
@Override
public String getId() {
- return ID;
+ String id;
+ switch (mRouteInfo.getType()) {
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ id = WIRED_HEADSET_ID;
+ break;
+ case TYPE_USB_DEVICE:
+ case TYPE_USB_HEADSET:
+ case TYPE_USB_ACCESSORY:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ id = USB_HEADSET_ID;
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ default:
+ id = PHONE_ID;
+ break;
+ }
+ return id;
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 77316e91bae2..365a16c50b99 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -41,6 +41,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
@@ -560,6 +561,10 @@ public class LocalMediaManagerTest {
mLocalMediaManager.mMediaDevices.add(device3);
mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+ final List<LocalBluetoothProfile> profiles = new ArrayList<>();
+ final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
+ profiles.add(a2dpProfile);
+
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -571,6 +576,7 @@ public class LocalMediaManagerTest {
when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
+ when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
@@ -634,6 +640,10 @@ public class LocalMediaManagerTest {
mLocalMediaManager.mMediaDevices.add(device3);
mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+ final List<LocalBluetoothProfile> profiles = new ArrayList<>();
+ final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
+ profiles.add(a2dpProfile);
+
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class);
@@ -662,6 +672,7 @@ public class LocalMediaManagerTest {
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
+ when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
when(bluetoothDevice.getBluetoothClass()).thenReturn(bluetoothClass);
when(bluetoothClass.getDeviceClass()).thenReturn(AUDIO_VIDEO_HEADPHONES);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 6664870a6257..47d4beb752c5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -29,13 +29,9 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageStats;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
-import com.android.settingslib.R;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidProfile;
@@ -49,8 +45,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -70,8 +64,6 @@ public class MediaDeviceTest {
private static final String ROUTER_ID_2 = "RouterId_2";
private static final String ROUTER_ID_3 = "RouterId_3";
private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
- private static final String TEST_PACKAGE_NAME2 = "com.test.playmusic2";
- private static final String TEST_APPLICATION_LABEL = "playmusic";
private final BluetoothClass mHeadreeClass =
new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
private final BluetoothClass mCarkitClass =
@@ -125,10 +117,6 @@ public class MediaDeviceTest {
private InfoMediaDevice mInfoMediaDevice3;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private PhoneMediaDevice mPhoneMediaDevice;
- private ShadowPackageManager mShadowPackageManager;
- private ApplicationInfo mAppInfo;
- private PackageInfo mPackageInfo;
- private PackageStats mPackageStats;
@Before
public void setUp() {
@@ -459,41 +447,6 @@ public class MediaDeviceTest {
assertThat(mInfoMediaDevice1.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME);
}
- private void initPackage() {
- mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
- mAppInfo = new ApplicationInfo();
- mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED;
- mAppInfo.packageName = TEST_PACKAGE_NAME;
- mAppInfo.name = TEST_APPLICATION_LABEL;
- mPackageInfo = new PackageInfo();
- mPackageInfo.packageName = TEST_PACKAGE_NAME;
- mPackageInfo.applicationInfo = mAppInfo;
- mPackageStats = new PackageStats(TEST_PACKAGE_NAME);
- }
-
- @Test
- public void getClientAppLabel_matchedPackageName_returnLabel() {
- initPackage();
- when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(
- mContext.getResources().getString(R.string.unknown));
-
- mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
-
- assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(TEST_APPLICATION_LABEL);
- }
-
- @Test
- public void getClientAppLabel_noMatchedPackageName_returnDefault() {
- initPackage();
- mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
- when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2);
-
- assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(
- mContext.getResources().getString(R.string.unknown));
- }
-
@Test
public void setState_verifyGetState() {
mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 6f265dd603e5..47f6fe3bce02 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -21,6 +21,10 @@ import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static com.android.settingslib.media.PhoneMediaDevice.PHONE_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.USB_HEADSET_ID;
+import static com.android.settingslib.media.PhoneMediaDevice.WIRED_HEADSET_ID;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
@@ -108,4 +112,22 @@ public class PhoneMediaDeviceTest {
assertThat(mPhoneMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
}
+
+ @Test
+ public void getId_returnCorrectId() {
+ when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
+
+ assertThat(mPhoneMediaDevice.getId())
+ .isEqualTo(WIRED_HEADSET_ID);
+
+ when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
+
+ assertThat(mPhoneMediaDevice.getId())
+ .isEqualTo(USB_HEADSET_ID);
+
+ when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+
+ assertThat(mPhoneMediaDevice.getId())
+ .isEqualTo(PHONE_ID);
+ }
}
diff --git a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
index 163015b7b0f0..21013c6c7b16 100644
--- a/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
+++ b/packages/SystemUI/res/drawable/screenshot_actions_background_protection.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:angle="90"
- android:startColor="#1f000000"
+ android:startColor="@color/global_screenshot_background_protection_start"
android:endColor="#00000000"/>
</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml
index 88a05ec5824a..1ed1f07fb277 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_view.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml
@@ -36,6 +36,8 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLines="1"
+ android:lines="2"
+ android:ellipsize="end"
android:layout_gravity="center"
android:paddingTop="@dimen/bubble_overflow_text_padding"
android:gravity="center"/>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index de19303b4948..1dbb38d5dc7a 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -22,7 +22,7 @@
android:layout_height="match_parent">
<ImageView
android:id="@+id/global_screenshot_actions_background"
- android:layout_height="@dimen/global_screenshot_bg_protection_height"
+ android:layout_height="@dimen/screenshot_bg_protection_height"
android:layout_width="match_parent"
android:alpha="0.0"
android:src="@drawable/screenshot_actions_background_protection"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index df576d83323f..fd9936f6b8ea 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -59,29 +59,16 @@
android:layout_gravity="center"
android:layout_weight="0"
android:layout_marginRight="@dimen/screenrecord_dialog_padding"/>
- <LinearLayout
+ <Spinner
+ android:id="@+id/screen_recording_options"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_weight="1">
- <TextView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:text="@string/screenrecord_audio_label"
- android:textColor="?android:attr/textColorPrimary"
- android:textAppearance="?android:attr/textAppearanceMedium"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/audio_type"
- android:text="@string/screenrecord_mic_label"
- android:textAppearance="?android:attr/textAppearanceSmall"/>
- </LinearLayout>
+ android:layout_height="48dp"
+ android:prompt="@string/screenrecord_audio_label"/>
<Switch
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_weight="0"
+ android:layout_weight="1"
+ android:layout_gravity="end"
android:id="@+id/screenrecord_audio_switch"/>
</LinearLayout>
@@ -102,7 +89,8 @@
android:id="@+id/screenrecord_taps_switch"
android:text="@string/screenrecord_taps_label"
android:textColor="?android:attr/textColorPrimary"
- android:textAppearance="?android:attr/textAppearanceMedium"/>
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
new file mode 100644
index 000000000000..af6f9bb827f6
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="vertical"
+ android:padding="10dp"
+ android:layout_weight="1">
+ <TextView
+ android:id="@+id/screen_recording_dialog_source_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"/>
+ <TextView
+ android:id="@+id/screen_recording_dialog_source_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml
new file mode 100644
index 000000000000..fabe9e2d4453
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="vertical"
+ android:layout_weight="1">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="@string/screenrecord_audio_label"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"/>
+ <TextView
+ android:id="@+id/screen_recording_dialog_source_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 2d5101104237..196357c4794e 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -79,6 +79,7 @@
<color name="global_screenshot_button_icon">@color/GM2_blue_300</color>
<color name="global_screenshot_dismiss_background">@color/GM2_grey_800</color>
<color name="global_screenshot_dismiss_foreground">#FFFFFF</color>
+ <color name="global_screenshot_background_protection_start">#80000000</color> <!-- 50% black -->
<!-- Biometric dialog colors -->
diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml
index 481483991de9..23e323112845 100644
--- a/packages/SystemUI/res/values-night/dimens.xml
+++ b/packages/SystemUI/res/values-night/dimens.xml
@@ -18,4 +18,8 @@
<resources>
<!-- The height of the divider between the individual notifications. -->
<dimen name="notification_divider_height">1dp</dimen>
+
+ <!-- Height of the background gradient behind the screenshot UI (taller in dark mode) -->
+ <dimen name="screenshot_bg_protection_height">375dp</dimen>
+
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 82eda311da6a..b6776005d83e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -200,6 +200,7 @@
<color name="global_screenshot_button_icon">@color/GM2_blue_500</color>
<color name="global_screenshot_dismiss_background">#FFFFFF</color>
<color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
+ <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
<!-- GM2 colors -->
<color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2cbb49801aa8..99e347eb1a69 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -306,6 +306,7 @@
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_protection_height">400dp</dimen>
<dimen name="global_screenshot_x_scale">80dp</dimen>
+ <dimen name="screenshot_bg_protection_height">242dp</dimen>
<dimen name="screenshot_preview_elevation">6dp</dimen>
<dimen name="screenshot_offset_y">48dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
@@ -1168,7 +1169,7 @@
<!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
<dimen name="bubble_expanded_default_height">180dp</dimen>
<!-- Default height of bubble overflow -->
- <dimen name="bubble_overflow_height">460dp</dimen>
+ <dimen name="bubble_overflow_height">480dp</dimen>
<!-- Bubble overflow padding when there are no bubbles -->
<dimen name="bubble_overflow_empty_state_padding">16dp</dimen>
<!-- Padding of container for overflow bubbles -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 04640f418e97..8156e8dc9bf1 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -144,5 +144,10 @@
<!-- NotificationPanelView -->
<item type="id" name="notification_panel" />
+
+ <!-- Screen Recording -->
+ <item type="id" name="screen_recording_options" />
+ <item type="id" name="screen_recording_dialog_source_text" />
+ <item type="id" name="screen_recording_dialog_source_description" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ee97e7374148..ec29622c9ba2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,6 +240,8 @@
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_name">Screen Recorder</string>
+ <!-- Processing screen recoding video in the background [CHAR LIMIT=30]-->
+ <string name="screenrecord_background_processing_label">Processing screen recording</string>
<!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]-->
<string name="screenrecord_channel_description">Ongoing notification for a screen record session</string>
<!-- Title for the screen prompting the user to begin recording their screen [CHAR LIMIT=NONE]-->
@@ -2611,6 +2613,10 @@
<!-- Text used for content description of settings button in the header of expanded bubble
view. [CHAR_LIMIT=NONE] -->
<string name="bubbles_settings_button_description">Settings for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubbles</string>
+ <!-- Content description for button that shows bubble overflow on click [CHAR LIMIT=NONE] -->
+ <string name="bubble_overflow_button_content_description">Overflow</string>
+ <!-- Action to add overflow bubble back to stack. [CHAR LIMIT=NONE] -->
+ <string name="bubble_accessibility_action_add_back">Add back to stack</string>
<!-- The text for the manage bubbles link. [CHAR LIMIT=NONE] -->
<string name="manage_bubbles_text">Manage</string>
<!-- Content description when a bubble is focused. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 7dea7f83f0c6..7c25d2811793 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -432,7 +432,7 @@ public abstract class AuthBiometricView extends LinearLayout {
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}
- public void updateState(@BiometricState int newState) {
+ void updateState(@BiometricState int newState) {
Log.v(TAG, "newState: " + newState);
switch (newState) {
@@ -453,8 +453,10 @@ public abstract class AuthBiometricView extends LinearLayout {
}
announceForAccessibility(getResources()
.getString(R.string.biometric_dialog_authenticated));
- mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_AUTHENTICATED),
- getDelayAfterAuthenticatedDurationMs());
+ mHandler.postDelayed(() -> {
+ Log.d(TAG, "Sending ACTION_AUTHENTICATED");
+ mCallback.onAction(Callback.ACTION_AUTHENTICATED);
+ }, getDelayAfterAuthenticatedDurationMs());
break;
case STATE_PENDING_CONFIRMATION:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b736b4df8abf..86087668604e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -113,6 +113,7 @@ public class AuthContainerView extends LinearLayout
int mModalityMask;
boolean mSkipIntro;
long mOperationId;
+ int mSysUiSessionId;
}
public static class Builder {
@@ -158,6 +159,11 @@ public class AuthContainerView extends LinearLayout
return this;
}
+ public Builder setSysUiSessionId(int sysUiSessionId) {
+ mConfig.mSysUiSessionId = sysUiSessionId;
+ return this;
+ }
+
public AuthContainerView build(int modalityMask) {
mConfig.mModalityMask = modalityMask;
return new AuthContainerView(mConfig, new Injector());
@@ -203,6 +209,9 @@ public class AuthContainerView extends LinearLayout
final class BiometricCallback implements AuthBiometricView.Callback {
@Override
public void onAction(int action) {
+ Log.d(TAG, "onAction: " + action
+ + ", sysUiSessionId: " + mConfig.mSysUiSessionId
+ + ", state: " + mContainerState);
switch (action) {
case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
@@ -461,13 +470,13 @@ public class AuthContainerView extends LinearLayout
if (animate) {
animateAway(false /* sendReason */, 0 /* reason */);
} else {
- removeWindowIfAttached();
+ removeWindowIfAttached(false /* sendReason */);
}
}
@Override
public void dismissFromSystemServer() {
- removeWindowIfAttached();
+ removeWindowIfAttached(true /* sendReason */);
}
@Override
@@ -540,7 +549,7 @@ public class AuthContainerView extends LinearLayout
final Runnable endActionRunnable = () -> {
setVisibility(View.INVISIBLE);
- removeWindowIfAttached();
+ removeWindowIfAttached(true /* sendReason */);
};
postOnAnimation(() -> {
@@ -575,19 +584,24 @@ public class AuthContainerView extends LinearLayout
}
private void sendPendingCallbackIfNotNull() {
- Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
+ Log.d(TAG, "pendingCallback: " + mPendingCallbackReason
+ + " sysUISessionId: " + mConfig.mSysUiSessionId);
if (mPendingCallbackReason != null) {
mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);
mPendingCallbackReason = null;
}
}
- private void removeWindowIfAttached() {
- sendPendingCallbackIfNotNull();
+ private void removeWindowIfAttached(boolean sendReason) {
+ if (sendReason) {
+ sendPendingCallbackIfNotNull();
+ }
if (mContainerState == STATE_GONE) {
+ Log.w(TAG, "Container already STATE_GONE, mSysUiSessionId: " + mConfig.mSysUiSessionId);
return;
}
+ Log.d(TAG, "Removing container, mSysUiSessionId: " + mConfig.mSysUiSessionId);
mContainerState = STATE_GONE;
mWindowManager.removeView(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0c6794c2ab85..9f0ea3ee46ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -276,14 +276,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
- long operationId) {
+ long operationId, int sysUiSessionId) {
final int authenticators = Utils.getAuthenticators(bundle);
if (DEBUG) {
Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators
+ ", biometricModality: " + biometricModality
+ ", requireConfirmation: " + requireConfirmation
- + ", operationId: " + operationId);
+ + ", operationId: " + operationId
+ + ", sysUiSessionId: " + sysUiSessionId);
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
@@ -293,6 +294,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
args.argi2 = userId;
args.arg4 = opPackageName;
args.arg5 = operationId;
+ args.argi3 = sysUiSessionId;
boolean skipAnimation = false;
if (mCurrentDialog != null) {
@@ -382,6 +384,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
final int userId = args.argi2;
final String opPackageName = (String) args.arg4;
final long operationId = (long) args.arg5;
+ final int sysUiSessionId = args.argi3;
// Create a new dialog but do not replace the current one yet.
final AuthDialog newDialog = buildDialog(
@@ -391,7 +394,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
type,
opPackageName,
skipAnimation,
- operationId);
+ operationId,
+ sysUiSessionId);
if (newDialog == null) {
Log.e(TAG, "Unsupported type: " + type);
@@ -403,7 +407,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
+ " savedState: " + savedState
+ " mCurrentDialog: " + mCurrentDialog
+ " newDialog: " + newDialog
- + " type: " + type);
+ + " type: " + type
+ + " sysUiSessionId: " + sysUiSessionId);
}
if (mCurrentDialog != null) {
@@ -458,7 +463,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
- int userId, int type, String opPackageName, boolean skipIntro, long operationId) {
+ int userId, int type, String opPackageName, boolean skipIntro, long operationId,
+ int sysUiSessionId) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setBiometricPromptBundle(biometricPromptBundle)
@@ -467,6 +473,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
.setOperationId(operationId)
+ .setSysUiSessionId(sysUiSessionId)
.build(type);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index 13669a68defa..e96bef36ba18 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -73,12 +73,13 @@ public class BubbleOverflow implements BubbleViewProvider {
updateIcon(mContext, parentViewGroup);
}
- // TODO(b/149146374) Propagate theme change to bubbles in overflow.
void updateIcon(Context context, ViewGroup parentViewGroup) {
mInflater = LayoutInflater.from(context);
mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button,
parentViewGroup /* root */,
false /* attachToRoot */);
+ mOverflowBtn.setContentDescription(mContext.getResources().getString(
+ R.string.bubble_overflow_button_content_description));
TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.colorBackgroundFloating});
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index de54c353fc85..c2ca9fad6d43 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -21,6 +21,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.app.Activity;
+import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -32,6 +33,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -103,7 +105,7 @@ public class BubbleOverflowActivity extends Activity {
- res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
final int viewHeight = recyclerViewHeight / rows;
- mAdapter = new BubbleOverflowAdapter(mOverflowBubbles,
+ mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
@@ -221,13 +223,15 @@ public class BubbleOverflowActivity extends Activity {
}
class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
+ private Context mContext;
private Consumer<Bubble> mPromoteBubbleFromOverflow;
private List<Bubble> mBubbles;
private int mWidth;
private int mHeight;
- public BubbleOverflowAdapter(List<Bubble> list, Consumer<Bubble> promoteBubble, int width,
- int height) {
+ public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
+ int width, int height) {
+ mContext = context;
mBubbles = list;
mPromoteBubbleFromOverflow = promoteBubble;
mWidth = width;
@@ -260,6 +264,32 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
mPromoteBubbleFromOverflow.accept(b);
});
+ final CharSequence titleCharSeq =
+ b.getEntry().getSbn().getNotification().extras.getCharSequence(
+ Notification.EXTRA_TITLE);
+ String titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
+ if (titleCharSeq != null) {
+ titleStr = titleCharSeq.toString();
+ }
+ vh.iconView.setContentDescription(mContext.getResources().getString(
+ R.string.bubble_content_description_single, titleStr, b.getAppName()));
+
+ vh.iconView.setAccessibilityDelegate(
+ new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ // Talkback prompts "Double tap to add back to stack"
+ // instead of the default "Double tap to activate"
+ info.addAction(
+ new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK,
+ mContext.getResources().getString(
+ R.string.bubble_accessibility_action_add_back)));
+ }
+ });
+
Bubble.FlyoutMessage message = b.getFlyoutMessage();
if (message != null && message.senderName != null) {
vh.textView.setText(message.senderName);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 341458763625..2cb097f6075e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -454,7 +454,6 @@ public class BubbleStackView extends FrameLayout
// that means overflow was previously expanded. Set the selected bubble
// internally without going through BubbleData (which would ignore it since it's
// already selected).
- mBubbleData.setShowingOverflow(true);
setSelectedBubble(clickedBubble);
}
} else {
@@ -1344,7 +1343,10 @@ public class BubbleStackView extends FrameLayout
}
if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) {
mBubbleData.setShowingOverflow(false);
+ } else {
+ mBubbleData.setShowingOverflow(true);
}
+
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
updatePointerPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java
new file mode 100644
index 000000000000..e90781b48f23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger.qualifiers;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface LongRunning {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index f322489b8dc2..f72de11a01ed 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -376,7 +376,8 @@ public class PipAnimationController {
// NOTE: intentionally does not apply the transaction here.
// this end transaction should get executed synchronously with the final
// WindowContainerTransaction in task organizer
- getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds());
+ getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds())
+ .crop(tx, leash, getDestinationBounds());
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 8d6ce4718aef..7e2efc04ea8e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -552,6 +552,9 @@ public class PipTaskOrganizer extends TaskOrganizer {
? null : destinationBounds;
// As for the final windowing mode, simply reset it to undefined.
wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ if (mSplitDivider != null && direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN) {
+ wct.reparent(mToken, mSplitDivider.getSecondaryRoot(), true /* onTop */);
+ }
} else {
taskBounds = destinationBounds;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index ae0a1c4d9822..b253635e9bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -137,7 +137,7 @@ public class RecordingController
* Check if the recording is ongoing
* @return
*/
- public boolean isRecording() {
+ public synchronized boolean isRecording() {
return mIsRecording;
}
@@ -157,7 +157,7 @@ public class RecordingController
* Update the current status
* @param isRecording
*/
- public void updateState(boolean isRecording) {
+ public synchronized void updateState(boolean isRecording) {
mIsRecording = isRecording;
for (RecordingStateChangeCallback cb : mListeners) {
if (isRecording) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 390ac0969b21..cf098d52fa91 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -22,41 +22,27 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
-import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionManager;
-import android.media.projection.MediaProjection;
-import android.media.projection.MediaProjectionManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.MediaStore;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
-import android.view.Surface;
-import android.view.WindowManager;
import android.widget.Toast;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.LongRunning;
-import java.io.File;
import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -66,13 +52,15 @@ import javax.inject.Inject;
public class RecordingService extends Service implements MediaRecorder.OnInfoListener {
public static final int REQUEST_CODE = 2;
- private static final int NOTIFICATION_ID = 1;
+ private static final int NOTIFICATION_RECORDING_ID = 4274;
+ private static final int NOTIFICATION_PROCESSING_ID = 4275;
+ private static final int NOTIFICATION_VIEW_ID = 4273;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
private static final String EXTRA_RESULT_CODE = "extra_resultCode";
private static final String EXTRA_DATA = "extra_data";
private static final String EXTRA_PATH = "extra_path";
- private static final String EXTRA_USE_AUDIO = "extra_useAudio";
+ private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String ACTION_START = "com.android.systemui.screenrecord.START";
@@ -80,29 +68,19 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
private static final String ACTION_DELETE = "com.android.systemui.screenrecord.DELETE";
- private static final int TOTAL_NUM_TRACKS = 1;
- private static final int VIDEO_BIT_RATE = 10000000;
- private static final int VIDEO_FRAME_RATE = 30;
- private static final int AUDIO_BIT_RATE = 16;
- private static final int AUDIO_SAMPLE_RATE = 44100;
- private static final int MAX_DURATION_MS = 60 * 60 * 1000;
- private static final long MAX_FILESIZE_BYTES = 5000000000L;
-
private final RecordingController mController;
- private MediaProjection mMediaProjection;
- private Surface mInputSurface;
- private VirtualDisplay mVirtualDisplay;
- private MediaRecorder mMediaRecorder;
private Notification.Builder mRecordingNotificationBuilder;
- private boolean mUseAudio;
+ private ScreenRecordingAudioSource mAudioSource;
private boolean mShowTaps;
private boolean mOriginalShowTaps;
- private File mTempFile;
+ private ScreenMediaRecorder mRecorder;
+ private final Executor mLongExecutor;
@Inject
- public RecordingService(RecordingController controller) {
+ public RecordingService(RecordingController controller, @LongRunning Executor executor) {
mController = controller;
+ mLongExecutor = executor;
}
/**
@@ -113,16 +91,16 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
* android.content.Intent)}
* @param data The data from {@link android.app.Activity#onActivityResult(int, int,
* android.content.Intent)}
- * @param useAudio True to enable microphone input while recording
+ * @param audioSource The ordinal value of the audio source
+ * {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource}
* @param showTaps True to make touches visible while recording
*/
- public static Intent getStartIntent(Context context, int resultCode, Intent data,
- boolean useAudio, boolean showTaps) {
+ public static Intent getStartIntent(Context context, int resultCode,
+ int audioSource, boolean showTaps) {
return new Intent(context, RecordingService.class)
.setAction(ACTION_START)
.putExtra(EXTRA_RESULT_CODE, resultCode)
- .putExtra(EXTRA_DATA, data)
- .putExtra(EXTRA_USE_AUDIO, useAudio)
+ .putExtra(EXTRA_AUDIO_SOURCE, audioSource)
.putExtra(EXTRA_SHOW_TAPS, showTaps);
}
@@ -139,36 +117,31 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
switch (action) {
case ACTION_START:
- mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false);
+ mAudioSource = ScreenRecordingAudioSource
+ .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
+ Log.d(TAG, "recording with audio source" + mAudioSource);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
- try {
- IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
- IMediaProjectionManager mediaService =
- IMediaProjectionManager.Stub.asInterface(b);
- IMediaProjection proj = mediaService.createProjection(getUserId(),
- getPackageName(),
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
- IBinder projection = proj.asBinder();
- if (projection == null) {
- Log.e(TAG, "Projection was null");
- Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG)
- .show();
- return Service.START_NOT_STICKY;
- }
- mMediaProjection = new MediaProjection(getApplicationContext(),
- IMediaProjection.Stub.asInterface(projection));
- startRecording();
- } catch (RemoteException e) {
- e.printStackTrace();
- Toast.makeText(this, R.string.screenrecord_start_error, Toast.LENGTH_LONG)
- .show();
- return Service.START_NOT_STICKY;
- }
+
+ mOriginalShowTaps = Settings.System.getInt(
+ getApplicationContext().getContentResolver(),
+ Settings.System.SHOW_TOUCHES, 0) != 0;
+
+ setTapsVisible(mShowTaps);
+
+ mRecorder = new ScreenMediaRecorder(
+ getApplicationContext(),
+ getUserId(),
+ mAudioSource,
+ this
+ );
+ startRecording();
break;
case ACTION_STOP:
stopRecording();
+ notificationManager.cancel(NOTIFICATION_RECORDING_ID);
saveRecording(notificationManager);
+ stopSelf();
break;
case ACTION_SHARE:
@@ -183,10 +156,10 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// Remove notification
- notificationManager.cancel(NOTIFICATION_ID);
+ notificationManager.cancel(NOTIFICATION_RECORDING_ID);
startActivity(Intent.createChooser(shareIntent, shareLabel)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
break;
case ACTION_DELETE:
// Close quick shade
@@ -202,7 +175,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
Toast.LENGTH_LONG).show();
// Remove notification
- notificationManager.cancel(NOTIFICATION_ID);
+ notificationManager.cancel(NOTIFICATION_RECORDING_ID);
Log.d(TAG, "Deleted recording " + uri);
break;
}
@@ -224,70 +197,15 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
*/
private void startRecording() {
try {
- File cacheDir = getCacheDir();
- cacheDir.mkdirs();
- mTempFile = File.createTempFile("temp", ".mp4", cacheDir);
- Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath());
-
- mOriginalShowTaps = 1 == Settings.System.getInt(
- getApplicationContext().getContentResolver(),
- Settings.System.SHOW_TOUCHES, 0);
- setTapsVisible(mShowTaps);
-
- // Set up media recorder
- mMediaRecorder = new MediaRecorder();
- if (mUseAudio) {
- mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
- }
- mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
- mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
-
- // Set up video
- DisplayMetrics metrics = new DisplayMetrics();
- WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- wm.getDefaultDisplay().getRealMetrics(metrics);
- int screenWidth = metrics.widthPixels;
- int screenHeight = metrics.heightPixels;
- mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
- mMediaRecorder.setVideoSize(screenWidth, screenHeight);
- mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
- mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
- mMediaRecorder.setMaxDuration(MAX_DURATION_MS);
- mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES);
-
- // Set up audio
- if (mUseAudio) {
- mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
- mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS);
- mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
- mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
- }
-
- mMediaRecorder.setOutputFile(mTempFile);
- mMediaRecorder.prepare();
-
- // Create surface
- mInputSurface = mMediaRecorder.getSurface();
- mVirtualDisplay = mMediaProjection.createVirtualDisplay(
- "Recording Display",
- screenWidth,
- screenHeight,
- metrics.densityDpi,
- DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
- mInputSurface,
- null,
- null);
-
- mMediaRecorder.setOnInfoListener(this);
- mMediaRecorder.start();
+ mRecorder.start();
mController.updateState(true);
- } catch (IOException e) {
- Log.e(TAG, "Error starting screen recording: " + e.getMessage());
+ createRecordingNotification();
+ } catch (IOException | RemoteException e) {
+ Toast.makeText(this,
+ R.string.screenrecord_start_error, Toast.LENGTH_LONG)
+ .show();
e.printStackTrace();
- throw new RuntimeException(e);
}
-
- createRecordingNotification();
}
private void createRecordingNotification() {
@@ -306,7 +224,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
res.getString(R.string.screenrecord_name));
- String notificationTitle = mUseAudio
+ String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
? res.getString(R.string.screenrecord_ongoing_screen_and_audio)
: res.getString(R.string.screenrecord_ongoing_screen_only);
@@ -323,9 +241,10 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
this, REQUEST_CODE, getStopIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT))
.addExtras(extras);
- notificationManager.notify(NOTIFICATION_ID, mRecordingNotificationBuilder.build());
+ notificationManager.notify(NOTIFICATION_RECORDING_ID,
+ mRecordingNotificationBuilder.build());
Notification notification = mRecordingNotificationBuilder.build();
- startForeground(NOTIFICATION_ID, notification);
+ startForeground(NOTIFICATION_RECORDING_ID, notification);
}
private Notification createSaveNotification(Uri uri) {
@@ -392,47 +311,38 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
private void stopRecording() {
setTapsVisible(mOriginalShowTaps);
- mMediaRecorder.stop();
- mMediaRecorder.release();
- mMediaRecorder = null;
- mMediaProjection.stop();
- mMediaProjection = null;
- mInputSurface.release();
- mVirtualDisplay.release();
- stopSelf();
+ mRecorder.end();
mController.updateState(false);
}
private void saveRecording(NotificationManager notificationManager) {
- String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'")
- .format(new Date());
-
- ContentValues values = new ContentValues();
- values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
- values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
- values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis());
- values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
-
- ContentResolver resolver = getContentResolver();
- Uri collectionUri = MediaStore.Video.Media.getContentUri(
- MediaStore.VOLUME_EXTERNAL_PRIMARY);
- Uri itemUri = resolver.insert(collectionUri, values);
-
- try {
- // Add to the mediastore
- OutputStream os = resolver.openOutputStream(itemUri, "w");
- Files.copy(mTempFile.toPath(), os);
- os.close();
-
- Notification notification = createSaveNotification(itemUri);
- notificationManager.notify(NOTIFICATION_ID, notification);
-
- mTempFile.delete();
- } catch (IOException e) {
- Log.e(TAG, "Error saving screen recording: " + e.getMessage());
- Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
- .show();
- }
+ Resources res = getApplicationContext().getResources();
+ String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
+ ? res.getString(R.string.screenrecord_ongoing_screen_only)
+ : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+ Notification.Builder builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
+ .setContentTitle(notificationTitle)
+ .setContentText(
+ getResources().getString(R.string.screenrecord_background_processing_label))
+ .setSmallIcon(R.drawable.ic_screenrecord);
+ notificationManager.notify(NOTIFICATION_PROCESSING_ID, builder.build());
+
+ mLongExecutor.execute(() -> {
+ try {
+ Log.d(TAG, "saving recording");
+ Notification notification = createSaveNotification(mRecorder.save());
+ if (!mController.isRecording()) {
+ Log.d(TAG, "showing saved notification");
+ notificationManager.notify(NOTIFICATION_VIEW_ID, notification);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error saving screen recording: " + e.getMessage());
+ Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
+ .show();
+ } finally {
+ notificationManager.cancel(NOTIFICATION_PROCESSING_ID);
+ }
+ });
}
private void setTapsVisible(boolean turnOn) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
new file mode 100644
index 000000000000..752f4fddf24b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioPlaybackCaptureConfiguration;
+import android.media.AudioRecord;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.MediaRecorder;
+import android.media.projection.MediaProjection;
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Recording internal audio
+ */
+public class ScreenInternalAudioRecorder {
+ private static String TAG = "ScreenAudioRecorder";
+ private static final int TIMEOUT = 500;
+ private final Context mContext;
+ private AudioRecord mAudioRecord;
+ private AudioRecord mAudioRecordMic;
+ private Config mConfig = new Config();
+ private Thread mThread;
+ private MediaProjection mMediaProjection;
+ private MediaCodec mCodec;
+ private long mPresentationTime;
+ private long mTotalBytes;
+ private MediaMuxer mMuxer;
+ private String mOutFile;
+ private boolean mMic;
+
+ private int mTrackId = -1;
+
+ public ScreenInternalAudioRecorder(String outFile, Context context,
+ MediaProjection mp, boolean includeMicInput) throws IOException {
+ mMic = includeMicInput;
+ mOutFile = outFile;
+ mMuxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+ mContext = context;
+ mMediaProjection = mp;
+ Log.d(TAG, "creating audio file " + outFile);
+ setupSimple();
+ }
+ /**
+ * Audio recoding configuration
+ */
+ public static class Config {
+ public int channelOutMask = AudioFormat.CHANNEL_OUT_MONO;
+ public int channelInMask = AudioFormat.CHANNEL_IN_MONO;
+ public int encoding = AudioFormat.ENCODING_PCM_16BIT;
+ public int sampleRate = 44100;
+ public int bitRate = 196000;
+ public int bufferSizeBytes = 1 << 17;
+ public boolean privileged = true;
+ public boolean legacy_app_looback = false;
+
+ @Override
+ public String toString() {
+ return "channelMask=" + channelOutMask
+ + "\n encoding=" + encoding
+ + "\n sampleRate=" + sampleRate
+ + "\n bufferSize=" + bufferSizeBytes
+ + "\n privileged=" + privileged
+ + "\n legacy app looback=" + legacy_app_looback;
+ }
+
+ }
+
+ private void setupSimple() throws IOException {
+ int size = AudioRecord.getMinBufferSize(
+ mConfig.sampleRate, mConfig.channelInMask,
+ mConfig.encoding) * 2;
+
+ Log.d(TAG, "audio buffer size: " + size);
+
+ AudioFormat format = new AudioFormat.Builder()
+ .setEncoding(mConfig.encoding)
+ .setSampleRate(mConfig.sampleRate)
+ .setChannelMask(mConfig.channelOutMask)
+ .build();
+
+ AudioPlaybackCaptureConfiguration playbackConfig =
+ new AudioPlaybackCaptureConfiguration.Builder(mMediaProjection)
+ .addMatchingUsage(AudioAttributes.USAGE_MEDIA)
+ .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN)
+ .addMatchingUsage(AudioAttributes.USAGE_GAME)
+ .build();
+
+ mAudioRecord = new AudioRecord.Builder()
+ .setAudioFormat(format)
+ .setAudioPlaybackCaptureConfig(playbackConfig)
+ .build();
+
+ if (mMic) {
+ mAudioRecordMic = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION,
+ mConfig.sampleRate, AudioFormat.CHANNEL_IN_MONO, mConfig.encoding, size);
+ }
+
+ mCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+ MediaFormat medFormat = MediaFormat.createAudioFormat(
+ MediaFormat.MIMETYPE_AUDIO_AAC, mConfig.sampleRate, 1);
+ medFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
+ MediaCodecInfo.CodecProfileLevel.AACObjectLC);
+ medFormat.setInteger(MediaFormat.KEY_BIT_RATE, mConfig.bitRate);
+ medFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mConfig.encoding);
+ mCodec.configure(medFormat,
+ null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+
+ mThread = new Thread(() -> {
+ short[] bufferInternal = null;
+ short[] bufferMic = null;
+ byte[] buffer = null;
+
+ if (mMic) {
+ bufferInternal = new short[size / 2];
+ bufferMic = new short[size / 2];
+ } else {
+ buffer = new byte[size];
+ }
+
+ while (true) {
+ int readBytes = 0;
+ int readShortsInternal = 0;
+ int readShortsMic = 0;
+ if (mMic) {
+ readShortsInternal = mAudioRecord.read(bufferInternal, 0,
+ bufferInternal.length);
+ readShortsMic = mAudioRecordMic.read(bufferMic, 0, bufferMic.length);
+ readBytes = Math.min(readShortsInternal, readShortsMic) * 2;
+ buffer = addAndConvertBuffers(bufferInternal, readShortsInternal, bufferMic,
+ readShortsMic);
+ } else {
+ readBytes = mAudioRecord.read(buffer, 0, buffer.length);
+ }
+
+ //exit the loop when at end of stream
+ if (readBytes < 0) {
+ Log.e(TAG, "read error " + readBytes +
+ ", shorts internal: " + readShortsInternal +
+ ", shorts mic: " + readShortsMic);
+ break;
+ }
+ encode(buffer, readBytes);
+ }
+ endStream();
+ });
+ }
+
+ private byte[] addAndConvertBuffers(short[] a1, int a1Limit, short[] a2, int a2Limit) {
+ int size = Math.max(a1Limit, a2Limit);
+ if (size < 0) return new byte[0];
+ byte[] buff = new byte[size * 2];
+ for (int i = 0; i < size; i++) {
+ int sum;
+ if (i > a1Limit) {
+ sum = a2[i];
+ } else if (i > a2Limit) {
+ sum = a1[i];
+ } else {
+ sum = (int) a1[i] + (int) a2[i];
+ }
+
+ if (sum > Short.MAX_VALUE) sum = Short.MAX_VALUE;
+ if (sum < Short.MIN_VALUE) sum = Short.MIN_VALUE;
+ int byteIndex = i * 2;
+ buff[byteIndex] = (byte) (sum & 0xff);
+ buff[byteIndex + 1] = (byte) ((sum >> 8) & 0xff);
+ }
+ return buff;
+ }
+
+ private void encode(byte[] buffer, int readBytes) {
+ int offset = 0;
+ while (readBytes > 0) {
+ int totalBytesRead = 0;
+ int bufferIndex = mCodec.dequeueInputBuffer(TIMEOUT);
+ if (bufferIndex < 0) {
+ writeOutput();
+ return;
+ }
+ ByteBuffer buff = mCodec.getInputBuffer(bufferIndex);
+ buff.clear();
+ int bufferSize = buff.capacity();
+ int bytesToRead = readBytes > bufferSize ? bufferSize : readBytes;
+ totalBytesRead += bytesToRead;
+ readBytes -= bytesToRead;
+ buff.put(buffer, offset, bytesToRead);
+ offset += bytesToRead;
+ mCodec.queueInputBuffer(bufferIndex, 0, bytesToRead, mPresentationTime, 0);
+ mTotalBytes += totalBytesRead;
+ mPresentationTime = 1000000L * (mTotalBytes / 2) / mConfig.sampleRate;
+
+ writeOutput();
+ }
+ }
+
+ private void endStream() {
+ int bufferIndex = mCodec.dequeueInputBuffer(TIMEOUT);
+ mCodec.queueInputBuffer(bufferIndex, 0, 0, mPresentationTime,
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ writeOutput();
+ }
+
+ private void writeOutput() {
+ while (true) {
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+ int bufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT);
+ if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ mTrackId = mMuxer.addTrack(mCodec.getOutputFormat());
+ mMuxer.start();
+ continue;
+ }
+ if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ break;
+ }
+ if (mTrackId < 0) return;
+ ByteBuffer buff = mCodec.getOutputBuffer(bufferIndex);
+
+ if (!((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0
+ && bufferInfo.size != 0)) {
+ mMuxer.writeSampleData(mTrackId, buff, bufferInfo);
+ }
+ mCodec.releaseOutputBuffer(bufferIndex, false);
+ }
+ }
+
+ /**
+ * start recording
+ */
+ public void start() {
+ if (mThread != null) {
+ Log.e(TAG, "a recording is being done in parallel or stop is not called");
+ }
+ mAudioRecord.startRecording();
+ if (mMic) mAudioRecordMic.startRecording();
+ Log.d(TAG, "channel count " + mAudioRecord.getChannelCount());
+ mCodec.start();
+ if (mAudioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
+ Log.e(TAG, "Error starting audio recording");
+ return;
+ }
+ mThread.start();
+ }
+
+ /**
+ * end recording
+ */
+ public void end() {
+ mAudioRecord.stop();
+ if (mMic) {
+ mAudioRecordMic.stop();
+ }
+ mAudioRecord.release();
+ if (mMic) {
+ mAudioRecordMic.release();
+ }
+ try {
+ mThread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ mCodec.stop();
+ mCodec.release();
+ mMuxer.stop();
+ mMuxer.release();
+ mThread = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
new file mode 100644
index 000000000000..c967648c544e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaMuxer;
+import android.media.MediaRecorder;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Recording screen and mic/internal audio
+ */
+public class ScreenMediaRecorder {
+ private static final int TOTAL_NUM_TRACKS = 1;
+ private static final int VIDEO_BIT_RATE = 10000000;
+ private static final int VIDEO_FRAME_RATE = 30;
+ private static final int AUDIO_BIT_RATE = 16;
+ private static final int AUDIO_SAMPLE_RATE = 44100;
+ private static final int MAX_DURATION_MS = 60 * 60 * 1000;
+ private static final long MAX_FILESIZE_BYTES = 5000000000L;
+ private static final String TAG = "ScreenMediaRecorder";
+
+
+ private File mTempVideoFile;
+ private File mTempAudioFile;
+ private MediaProjection mMediaProjection;
+ private Surface mInputSurface;
+ private VirtualDisplay mVirtualDisplay;
+ private MediaRecorder mMediaRecorder;
+ private int mUser;
+ private ScreenRecordingMuxer mMuxer;
+ private ScreenInternalAudioRecorder mAudio;
+ private ScreenRecordingAudioSource mAudioSource;
+
+ private Context mContext;
+ MediaRecorder.OnInfoListener mListener;
+
+ public ScreenMediaRecorder(Context context,
+ int user, ScreenRecordingAudioSource audioSource,
+ MediaRecorder.OnInfoListener listener) {
+ mContext = context;
+ mUser = user;
+ mListener = listener;
+ mAudioSource = audioSource;
+ }
+
+ private void prepare() throws IOException, RemoteException {
+ //Setup media projection
+ IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+ IMediaProjectionManager mediaService =
+ IMediaProjectionManager.Stub.asInterface(b);
+ IMediaProjection proj = null;
+ proj = mediaService.createProjection(mUser, mContext.getPackageName(),
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
+ IBinder projection = proj.asBinder();
+ mMediaProjection = new MediaProjection(mContext,
+ IMediaProjection.Stub.asInterface(projection));
+
+ File cacheDir = mContext.getCacheDir();
+ cacheDir.mkdirs();
+ mTempVideoFile = File.createTempFile("temp", ".mp4", cacheDir);
+
+ // Set up media recorder
+ mMediaRecorder = new MediaRecorder();
+
+ // Set up audio source
+ if (mAudioSource == MIC) {
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ }
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+
+ mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+
+
+ // Set up video
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getRealMetrics(metrics);
+ int screenWidth = metrics.widthPixels;
+ int screenHeight = metrics.heightPixels;
+ mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+ mMediaRecorder.setVideoSize(screenWidth, screenHeight);
+ mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
+ mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
+ mMediaRecorder.setMaxDuration(MAX_DURATION_MS);
+ mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES);
+
+ // Set up audio
+ if (mAudioSource == MIC) {
+ mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
+ mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS);
+ mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
+ mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
+ }
+
+ mMediaRecorder.setOutputFile(mTempVideoFile);
+ mMediaRecorder.prepare();
+ // Create surface
+ mInputSurface = mMediaRecorder.getSurface();
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay(
+ "Recording Display",
+ screenWidth,
+ screenHeight,
+ metrics.densityDpi,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ mInputSurface,
+ null,
+ null);
+
+ mMediaRecorder.setOnInfoListener(mListener);
+ if (mAudioSource == INTERNAL ||
+ mAudioSource == MIC_AND_INTERNAL) {
+ mTempAudioFile = File.createTempFile("temp", ".aac",
+ mContext.getCacheDir());
+ mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(), mContext,
+ mMediaProjection, mAudioSource == MIC_AND_INTERNAL);
+ }
+
+ }
+
+ /**
+ * Start screen recording
+ */
+ void start() throws IOException, RemoteException {
+ Log.d(TAG, "start recording");
+ prepare();
+ mMediaRecorder.start();
+ recordInternalAudio();
+ }
+
+ /**
+ * End screen recording
+ */
+ void end() {
+ mMediaRecorder.stop();
+ mMediaProjection.stop();
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ mMediaProjection = null;
+ mInputSurface.release();
+ mVirtualDisplay.release();
+ stopInternalAudioRecording();
+
+ Log.d(TAG, "end recording");
+ }
+
+ private void stopInternalAudioRecording() {
+ if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
+ mAudio.end();
+ mAudio = null;
+ }
+ }
+
+ private void recordInternalAudio() {
+ if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
+ mAudio.start();
+ }
+ }
+
+ /**
+ * Store recorded video
+ */
+ Uri save() throws IOException {
+ String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'")
+ .format(new Date());
+
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
+ values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
+ values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis());
+ values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
+
+ ContentResolver resolver = mContext.getContentResolver();
+ Uri collectionUri = MediaStore.Video.Media.getContentUri(
+ MediaStore.VOLUME_EXTERNAL_PRIMARY);
+ Uri itemUri = resolver.insert(collectionUri, values);
+
+ Log.d(TAG, itemUri.toString());
+ if (mAudioSource == MIC_AND_INTERNAL || mAudioSource == INTERNAL) {
+ try {
+ Log.d(TAG, "muxing recording");
+ File file = File.createTempFile("temp", ".mp4",
+ mContext.getCacheDir());
+ mMuxer = new ScreenRecordingMuxer(MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+ file.getAbsolutePath(),
+ mTempVideoFile.getAbsolutePath(),
+ mTempAudioFile.getAbsolutePath());
+ mMuxer.mux();
+ mTempVideoFile.delete();
+ mTempVideoFile = file;
+ } catch (IOException e) {
+ Log.e(TAG, "muxing recording " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ // Add to the mediastore
+ OutputStream os = resolver.openOutputStream(itemUri, "w");
+ Files.copy(mTempVideoFile.toPath(), os);
+ os.close();
+ mTempVideoFile.delete();
+ if (mTempAudioFile != null) mTempAudioFile.delete();
+ return itemUri;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 26973d092209..c247328078a7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -16,17 +16,30 @@
package com.android.systemui.screenrecord;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE;
+
import android.app.Activity;
import android.app.PendingIntent;
import android.os.Bundle;
+import android.util.Log;
import android.view.Gravity;
+import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.Spinner;
import android.widget.Switch;
import com.android.systemui.R;
+import java.util.ArrayList;
+import java.util.List;
+
import javax.inject.Inject;
/**
@@ -35,10 +48,15 @@ import javax.inject.Inject;
public class ScreenRecordDialog extends Activity {
private static final long DELAY_MS = 3000;
private static final long INTERVAL_MS = 1000;
+ private static final String TAG = "ScreenRecordDialog";
private final RecordingController mController;
- private Switch mAudioSwitch;
private Switch mTapsSwitch;
+ private Switch mAudioSwitch;
+ private Spinner mOptions;
+ private List<ScreenRecordingAudioSource> mModes;
+ private int mSelected;
+
@Inject
public ScreenRecordDialog(RecordingController controller) {
@@ -68,17 +86,32 @@ public class ScreenRecordDialog extends Activity {
finish();
});
+ mModes = new ArrayList<>();
+ mModes.add(INTERNAL);
+ mModes.add(MIC);
+ mModes.add(MIC_AND_INTERNAL);
+
mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
+ mOptions = findViewById(R.id.screen_recording_options);
+ ArrayAdapter a = new ScreenRecordingAdapter(getApplicationContext(),
+ android.R.layout.simple_spinner_dropdown_item,
+ mModes);
+ a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mOptions.setAdapter(a);
+
}
private void requestScreenCapture() {
- boolean useAudio = mAudioSwitch.isChecked();
boolean showTaps = mTapsSwitch.isChecked();
+ ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
+ ? (ScreenRecordingAudioSource) mOptions.getSelectedItem()
+ : NONE;
PendingIntent startIntent = PendingIntent.getForegroundService(this,
RecordingService.REQUEST_CODE,
RecordingService.getStartIntent(
- ScreenRecordDialog.this, RESULT_OK, null, useAudio, showTaps),
+ ScreenRecordDialog.this, RESULT_OK,
+ audioMode.ordinal(), showTaps),
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent stopIntent = PendingIntent.getService(this,
RecordingService.REQUEST_CODE,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java
new file mode 100644
index 000000000000..2e0e746594b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
+import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import java.util.List;
+
+/**
+ * Screen recording view adapter
+ */
+public class ScreenRecordingAdapter extends ArrayAdapter<ScreenRecordingAudioSource> {
+ private LinearLayout mSelectedMic;
+ private LinearLayout mSelectedInternal;
+ private LinearLayout mSelectedMicAndInternal;
+ private LinearLayout mMicOption;
+ private LinearLayout mMicAndInternalOption;
+ private LinearLayout mInternalOption;
+
+ public ScreenRecordingAdapter(Context context, int resource,
+ List<ScreenRecordingAudioSource> objects) {
+ super(context, resource, objects);
+ initViews();
+ }
+
+ private void initViews() {
+ mSelectedInternal = getSelected(R.string.screenrecord_device_audio_label);
+ mSelectedMic = getSelected(R.string.screenrecord_mic_label);
+ mSelectedMicAndInternal = getSelected(R.string.screenrecord_device_audio_and_mic_label);
+
+ mMicOption = getOption(R.string.screenrecord_mic_label, Resources.ID_NULL);
+ mMicOption.removeViewAt(1);
+
+ mMicAndInternalOption = getOption(
+ R.string.screenrecord_device_audio_and_mic_label, Resources.ID_NULL);
+ mMicAndInternalOption.removeViewAt(1);
+
+ mInternalOption = getOption(R.string.screenrecord_device_audio_label,
+ R.string.screenrecord_device_audio_description);
+ }
+
+ private LinearLayout getOption(int label, int description) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LinearLayout layout = (LinearLayout) inflater
+ .inflate(R.layout.screen_record_dialog_audio_source, null, false);
+ ((TextView) layout.findViewById(R.id.screen_recording_dialog_source_text))
+ .setText(label);
+ if (description != Resources.ID_NULL)
+ ((TextView) layout.findViewById(R.id.screen_recording_dialog_source_description))
+ .setText(description);
+ return layout;
+ }
+
+ private LinearLayout getSelected(int label) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ LinearLayout layout = (LinearLayout) inflater
+ .inflate(R.layout.screen_record_dialog_audio_source_selected, null, false);
+ ((TextView) layout.findViewById(R.id.screen_recording_dialog_source_text))
+ .setText(label);
+ return layout;
+ }
+
+ private void setDescription(LinearLayout layout, int description) {
+ if (description != Resources.ID_NULL) {
+ ((TextView) layout.getChildAt(1)).setText(description);
+ }
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ switch (getItem(position)) {
+ case INTERNAL:
+ return mInternalOption;
+ case MIC_AND_INTERNAL:
+ return mMicAndInternalOption;
+ case MIC:
+ return mMicOption;
+ default:
+ return super.getDropDownView(position, convertView, parent);
+ }
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ switch (getItem(position)) {
+ case INTERNAL:
+ return mSelectedInternal;
+ case MIC_AND_INTERNAL:
+ return mSelectedMicAndInternal;
+ case MIC:
+ return mSelectedMic;
+ default:
+ return super.getView(position, convertView, parent);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAudioSource.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAudioSource.java
new file mode 100644
index 000000000000..ee11865a03a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAudioSource.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+/**
+ * Audio sources
+ */
+public enum ScreenRecordingAudioSource {
+ NONE,
+ INTERNAL,
+ MIC,
+ MIC_AND_INTERNAL;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingMuxer.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingMuxer.java
new file mode 100644
index 000000000000..7ffcfd46a1a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingMuxer.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord;
+
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaMuxer;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * Mixing audio and video tracks
+ */
+public class ScreenRecordingMuxer {
+ // size of a memory page for cache coherency
+ private static final int BUFFER_SIZE = 1024 * 4096;
+ private String[] mFiles;
+ private String mOutFile;
+ private int mFormat;
+ private ArrayMap<Pair<MediaExtractor, Integer>, Integer> mExtractorIndexToMuxerIndex
+ = new ArrayMap<>();
+ private ArrayList<MediaExtractor> mExtractors = new ArrayList<>();
+
+ private static String TAG = "ScreenRecordingMuxer";
+ public ScreenRecordingMuxer(@MediaMuxer.Format int format, String outfileName,
+ String... inputFileNames) {
+ mFiles = inputFileNames;
+ mOutFile = outfileName;
+ mFormat = format;
+ Log.d(TAG, "out: " + mOutFile + " , in: " + mFiles[0]);
+ }
+
+ /**
+ * RUN IN THE BACKGROUND THREAD!
+ */
+ public void mux() throws IOException {
+ MediaMuxer muxer = null;
+ muxer = new MediaMuxer(mOutFile, mFormat);
+ // Add extractors
+ for (String file: mFiles) {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource(file);
+ } catch (IOException e) {
+ Log.e(TAG, "error creating extractor: " + file);
+ e.printStackTrace();
+ continue;
+ }
+ Log.d(TAG, file + " track count: " + extractor.getTrackCount());
+ mExtractors.add(extractor);
+ for (int i = 0; i < extractor.getTrackCount(); i++) {
+ int muxId = muxer.addTrack(extractor.getTrackFormat(i));
+ Log.d(TAG, "created extractor format" + extractor.getTrackFormat(i).toString());
+ mExtractorIndexToMuxerIndex.put(Pair.create(extractor, i), muxId);
+ }
+ }
+
+ muxer.start();
+ for (Pair<MediaExtractor, Integer> pair: mExtractorIndexToMuxerIndex.keySet()) {
+ MediaExtractor extractor = pair.first;
+ extractor.selectTrack(pair.second);
+ int muxId = mExtractorIndexToMuxerIndex.get(pair);
+ Log.d(TAG, "track format: " + extractor.getTrackFormat(pair.second));
+ extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ int offset;
+ while (true) {
+ offset = buffer.arrayOffset();
+ info.size = extractor.readSampleData(buffer, offset);
+ if (info.size < 0) break;
+ info.presentationTimeUs = extractor.getSampleTime();
+ info.flags = extractor.getSampleFlags();
+ muxer.writeSampleData(muxId, buffer, info);
+ extractor.advance();
+ }
+ }
+
+ for (MediaExtractor extractor: mExtractors) {
+ extractor.release();
+ }
+ muxer.stop();
+ muxer.release();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index e67b3d715c84..02a7aca38abe 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -813,4 +813,12 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
updateVisibility(true /* visible */);
}
}
+
+ /** @return the container token for the secondary split root task. */
+ public WindowContainerToken getSecondaryRoot() {
+ if (mSplits == null || mSplits.mSecondary == null) {
+ return null;
+ }
+ return mSplits.mSecondary.token;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 24195156d8cf..96d6ecbcc07f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -263,7 +263,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showAuthenticationDialog(Bundle bundle,
IBiometricServiceReceiverInternal receiver, int biometricModality,
boolean requireConfirmation, int userId, String opPackageName,
- long operationId) { }
+ long operationId, int sysUiSessionId) { }
default void onBiometricAuthenticated() { }
default void onBiometricHelp(String message) { }
default void onBiometricError(int modality, int error, int vendorCode) { }
@@ -782,7 +782,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
@Override
public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
- long operationId) {
+ long operationId, int sysUiSessionId) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = bundle;
@@ -792,6 +792,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
args.argi2 = userId;
args.arg4 = opPackageName;
args.arg5 = operationId;
+ args.argi3 = sysUiSessionId;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
}
@@ -1169,7 +1170,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
(boolean) someArgs.arg3 /* requireConfirmation */,
someArgs.argi2 /* userId */,
(String) someArgs.arg4 /* opPackageName */,
- (long) someArgs.arg5 /* operationId */);
+ (long) someArgs.arg5 /* operationId */,
+ someArgs.argi3 /* sysUiSessionId */);
}
someArgs.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 8fcc67a0708e..e7f26183fa8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -184,8 +184,11 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn());
- boolean groupChangesAllowed = mVisualStabilityManager.areGroupChangesAllowed()
- || !ent.hasFinishedInitialization();
+ boolean groupChangesAllowed =
+ mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
+ || !ent.hasFinishedInitialization() // notif recently added
+ || !mListContainer.containsView(ent.getRow()); // notif recently unfiltered
+
NotificationEntry parent = mGroupManager.getGroupSummary(ent.getSbn());
if (!groupChangesAllowed) {
// We don't to change groups while the user is looking at them
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 55a20fae4ffd..040dbe320711 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -288,7 +288,7 @@ public class InstantAppNotifier extends SystemUI
mContext,
0,
new Intent(Intent.ACTION_VIEW).setData(Uri.parse(helpUrl)),
- 0,
+ PendingIntent.FLAG_IMMUTABLE,
null,
user)
: null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index fc6a02840891..567ddb680848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -321,7 +321,8 @@ public class NotificationShadeWindowController implements Callback, Dumpable,
|| state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
|| state.mHeadsUpShowing
|| state.mScrimsVisibility != ScrimController.TRANSPARENT)
- || state.mBackgroundBlurRadius > 0;
+ || state.mBackgroundBlurRadius > 0
+ || state.mLaunchingActivity;
}
private void applyFitsSystemWindows(State state) {
@@ -485,6 +486,11 @@ public class NotificationShadeWindowController implements Callback, Dumpable,
apply(mCurrentState);
}
+ void setLaunchingActivity(boolean launching) {
+ mCurrentState.mLaunchingActivity = launching;
+ apply(mCurrentState);
+ }
+
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;
apply(mCurrentState);
@@ -645,6 +651,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable,
boolean mForceCollapsed;
boolean mForceDozeBrightness;
boolean mForceUserActivity;
+ boolean mLaunchingActivity;
boolean mBackdropShowing;
boolean mWallpaperSupportsAmbientMode;
boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 596a607bb8ad..0d2589847bcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,6 +93,7 @@ public class NotificationShadeWindowViewController {
private PhoneStatusBarView mStatusBarView;
private PhoneStatusBarTransitions mBarTransitions;
private StatusBar mService;
+ private NotificationShadeWindowController mNotificationShadeWindowController;
private DragDownHelper mDragDownHelper;
private boolean mDoubleTapEnabled;
private boolean mSingleTapEnabled;
@@ -430,10 +431,14 @@ public class NotificationShadeWindowViewController {
public void setExpandAnimationPending(boolean pending) {
mExpandAnimationPending = pending;
+ mNotificationShadeWindowController
+ .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
}
public void setExpandAnimationRunning(boolean running) {
mExpandAnimationRunning = running;
+ mNotificationShadeWindowController
+ .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
}
public void cancelExpandHelper() {
@@ -456,8 +461,9 @@ public class NotificationShadeWindowViewController {
}
}
- public void setService(StatusBar statusBar) {
+ public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
mService = statusBar;
+ mNotificationShadeWindowController = controller;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index c5901398a54b..33997b9a5735 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -440,24 +440,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
if (!(relevantState && mExpansionAffectsAlpha)) {
return;
}
- applyExpansionToAlpha();
- if (mUpdatePending) {
- return;
- }
- setOrAdaptCurrentAnimation(mScrimBehind);
- setOrAdaptCurrentAnimation(mScrimInFront);
- setOrAdaptCurrentAnimation(mScrimForBubble);
- dispatchScrimState(mScrimBehind.getViewAlpha());
-
- // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
- // and docking.
- if (mWallpaperVisibilityTimedOut) {
- mWallpaperVisibilityTimedOut = false;
- DejankUtils.postAfterTraversal(() -> {
- mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
- AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
- });
- }
+ applyAndDispatchExpansion();
}
}
@@ -513,6 +496,27 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
}
+ private void applyAndDispatchExpansion() {
+ applyExpansionToAlpha();
+ if (mUpdatePending) {
+ return;
+ }
+ setOrAdaptCurrentAnimation(mScrimBehind);
+ setOrAdaptCurrentAnimation(mScrimInFront);
+ setOrAdaptCurrentAnimation(mScrimForBubble);
+ dispatchScrimState(mScrimBehind.getViewAlpha());
+
+ // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
+ // and docking.
+ if (mWallpaperVisibilityTimedOut) {
+ mWallpaperVisibilityTimedOut = false;
+ DejankUtils.postAfterTraversal(() -> {
+ mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
+ AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ });
+ }
+ }
+
/**
* Sets the given drawable as the background of the scrim that shows up behind the
* notifications.
@@ -1006,6 +1010,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
mExpansionAffectsAlpha = expansionAffectsAlpha;
+ if (expansionAffectsAlpha) {
+ applyAndDispatchExpansion();
+ }
}
public void setKeyguardOccluded(boolean keyguardOccluded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index bbf83bc2057a..dd54a3d800fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@ public class StatusBar extends SystemUI implements DemoMode,
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowViewController.setService(this);
+ mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 8acfbf2b6996..7729965b56c4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -23,6 +23,7 @@ import android.os.Looper;
import android.os.Process;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -50,6 +51,17 @@ public abstract class ConcurrencyModule {
return thread.getLooper();
}
+ /** Long running tasks Looper */
+ @Provides
+ @Singleton
+ @LongRunning
+ public static Looper provideLongRunningLooper() {
+ HandlerThread thread = new HandlerThread("SysUiLng",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ }
+
/** Main Looper */
@Provides
@Main
@@ -89,6 +101,16 @@ public abstract class ConcurrencyModule {
}
/**
+ * Provide a Long running Executor by default.
+ */
+ @Provides
+ @Singleton
+ @LongRunning
+ public static Executor provideLongRunningExecutor(@LongRunning Looper looper) {
+ return new ExecutorImpl(looper);
+ }
+
+ /**
* Provide a Background-Thread Executor.
*/
@Provides
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index fc1ddf74a448..821b8504e5f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -470,7 +470,8 @@ public class AuthControllerTest extends SysuiTestCase {
true /* requireConfirmation */,
0 /* userId */,
"testPackage",
- 0 /* operationId */);
+ 0 /* operationId */,
+ 0 /* sysUiSessionId */);
}
private Bundle createTestDialogBundle(int authenticators) {
@@ -508,7 +509,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Override
protected AuthDialog buildDialog(Bundle biometricPromptBundle,
boolean requireConfirmation, int userId, int type, String opPackageName,
- boolean skipIntro, long operationId) {
+ boolean skipIntro, long operationId, int sysUiSessionId) {
mLastBiometricPromptBundle = biometricPromptBundle;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index cffcabb55e14..63e6e8cba628 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -410,11 +410,12 @@ public class CommandQueueTest extends SysuiTestCase {
Bundle bundle = new Bundle();
String packageName = "test";
final long operationId = 1;
+ final int sysUiSessionId = 2;
mCommandQueue.showAuthenticationDialog(bundle, null /* receiver */, 1, true, 3,
- packageName, operationId);
+ packageName, operationId, sysUiSessionId);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3),
- eq(packageName), eq(operationId));
+ eq(packageName), eq(operationId), eq(sysUiSessionId));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index cc2d1c25de38..e04d25b17c71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,6 +83,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock private NotificationShadeDepthController mNotificationShadeDepthController;
@Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
+ @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Before
public void setUp() {
@@ -121,7 +122,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mNotificationPanelViewController,
mStatusBarViewFactory);
mController.setupExpandedStatusBar();
- mController.setService(mStatusBar);
+ mController.setService(mStatusBar, mNotificationShadeWindowController);
mController.setDragDownHelper(mDragDownHelper);
}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index bfb65241ec6d..cbc5e14139ac 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -27,7 +27,7 @@ java_defaults {
"androidx.annotation_annotation",
"netd_aidl_interface-V3-java",
"netlink-client",
- "networkstack-aidl-interfaces-unstable-java",
+ "networkstack-aidl-interfaces-java",
"android.hardware.tetheroffload.config-V1.0-java",
"android.hardware.tetheroffload.control-V1.0-java",
"net-utils-framework-common",
@@ -109,6 +109,7 @@ android_app {
manifest: "AndroidManifest_InProcess.xml",
// InProcessTethering is a replacement for Tethering
overrides: ["Tethering"],
+ apex_available: ["com.android.tethering"],
}
// Updatable tethering packaged as an application
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index 24df5f696077..20ccd2ad6453 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -36,3 +36,12 @@ android_app_certificate {
name: "com.android.tethering.certificate",
certificate: "com.android.tethering",
}
+
+override_apex {
+ name: "com.android.tethering.inprocess",
+ base: "com.android.tethering",
+ package_name: "com.android.tethering.inprocess",
+ apps: [
+ "InProcessTethering",
+ ],
+}
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 952325cc4380..b2a43c47d1c6 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -62,7 +62,6 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
-import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -268,12 +267,9 @@ public class Tethering {
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
mTetherMasterSM.start();
- final NetworkStatsManager statsManager =
- (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
mHandler = mTetherMasterSM.getHandler();
- mOffloadController = new OffloadController(mHandler,
- mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
- statsManager, mLog, new OffloadController.Dependencies() {
+ mOffloadController = mDeps.getOffloadController(mHandler, mLog,
+ new OffloadController.Dependencies() {
@Override
public TetheringConfiguration getTetherConfig() {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9b54b5ff2403..802f2acb3310 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering;
+import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.INetd;
@@ -47,6 +48,19 @@ public abstract class TetheringDependencies {
}
/**
+ * Get a reference to the offload controller to be used by tethering.
+ */
+ @NonNull
+ public OffloadController getOffloadController(@NonNull Handler h,
+ @NonNull SharedLog log, @NonNull OffloadController.Dependencies deps) {
+ final NetworkStatsManager statsManager =
+ (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE);
+ return new OffloadController(h, getOffloadHardwareInterface(h, log),
+ getContext().getContentResolver(), statsManager, log, deps);
+ }
+
+
+ /**
* Get a reference to the UpstreamNetworkMonitor to be used by tethering.
*/
public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 0363f5f9989f..fff7a70f54d0 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -150,6 +150,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.ArrayList;
@@ -212,6 +214,9 @@ public class TetheringTest {
private Tethering mTethering;
private PhoneStateListener mPhoneStateListener;
private InterfaceConfigurationParcel mInterfaceConfiguration;
+ private TetheringConfiguration mConfig;
+ private EntitlementManager mEntitleMgr;
+ private OffloadController mOffloadCtrl;
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -297,8 +302,9 @@ public class TetheringTest {
}
}
- private class MockTetheringConfiguration extends TetheringConfiguration {
- MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
+ // MyTetheringConfiguration is used to override static method for testing.
+ private class MyTetheringConfiguration extends TetheringConfiguration {
+ MyTetheringConfiguration(Context ctx, SharedLog log, int id) {
super(ctx, log, id);
}
@@ -328,6 +334,15 @@ public class TetheringTest {
}
@Override
+ public OffloadController getOffloadController(Handler h, SharedLog log,
+ OffloadController.Dependencies deps) {
+ mOffloadCtrl = spy(super.getOffloadController(h, log, deps));
+ // Return real object here instead of mock because
+ // testReportFailCallbackIfOffloadNotSupported depend on real OffloadController object.
+ return mOffloadCtrl;
+ }
+
+ @Override
public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
StateMachine target, SharedLog log, int what) {
mUpstreamNetworkMonitorMasterSM = target;
@@ -352,6 +367,13 @@ public class TetheringTest {
}
@Override
+ public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
+ SharedLog log, int what) {
+ mEntitleMgr = spy(super.getEntitlementManager(ctx, target, log, what));
+ return mEntitleMgr;
+ }
+
+ @Override
public boolean isTetheringSupported() {
return true;
}
@@ -359,7 +381,8 @@ public class TetheringTest {
@Override
public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
int subId) {
- return new MockTetheringConfiguration(ctx, log, subId);
+ mConfig = spy(new MyTetheringConfiguration(ctx, log, subId));
+ return mConfig;
}
@Override
@@ -1726,6 +1749,17 @@ public class TetheringTest {
verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
}
+ @Test
+ public void testDumpTetheringLog() throws Exception {
+ final FileDescriptor mockFd = mock(FileDescriptor.class);
+ final PrintWriter mockPw = mock(PrintWriter.class);
+ runUsbTethering(null);
+ mTethering.dump(mockFd, mockPw, new String[0]);
+ verify(mConfig).dump(any());
+ verify(mEntitleMgr).dump(any());
+ verify(mOffloadCtrl).dump(any());
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index edb4445151d5..0f98992d1ea0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -94,8 +94,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (userState == null) return;
final long identity = Binder.clearCallingIdentity();
try {
- int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
- | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
+ int flags = Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
+ | Context.BIND_INCLUDE_CAPABILITIES;
if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
index ce11c76e5c6a..22451e1d992e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -37,6 +37,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.InlineSuggestionsRequestInfo;
+import com.android.server.autofill.ui.InlineSuggestionFactory;
import com.android.server.inputmethod.InputMethodManagerInternal;
import java.lang.ref.WeakReference;
@@ -242,7 +243,8 @@ final class AutofillInlineSuggestionsRequestSession {
}
if (sDebug) Log.d(TAG, "Send inline response: " + response.getInlineSuggestions().size());
try {
- mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
+ mResponseCallback.onInlineSuggestionsResponse(mAutofillId,
+ InlineSuggestionFactory.copy(response));
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
index 255adcd92da3..617c111c6c38 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
@@ -47,7 +47,7 @@ public final class RemoteInlineSuggestionRenderService extends
private static final String TAG = "RemoteInlineSuggestionRenderService";
- private final int mIdleUnbindTimeoutMs = 5000;
+ private final long mIdleUnbindTimeoutMs = PERMANENT_BOUND_TIMEOUT_MS;
RemoteInlineSuggestionRenderService(Context context, ComponentName componentName,
String serviceInterface, int userId, InlineSuggestionRenderCallbacks callback,
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java b/services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java
new file mode 100644
index 000000000000..819f2b813a5e
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineContentProviderImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
+import com.android.server.FgThread;
+
+/**
+ * We create one instance of this class for each {@link android.view.inputmethod.InlineSuggestion}
+ * instance. Each inline suggestion instance will only be sent to the remote IME process once. In
+ * case of filtering and resending the suggestion when keyboard state changes between hide and
+ * show, a new instance of this class will be created using {@link #copy()}, with the same backing
+ * {@link RemoteInlineSuggestionUi}. When the
+ * {@link #provideContent(int, int, IInlineContentCallback)} is called the first time (it's only
+ * allowed to be called at most once), the passed in width/height is used to determine whether
+ * the existing {@link RemoteInlineSuggestionUi} provided in the constructor can be reused, or a
+ * new one should be created to suit the new size requirement for the view. In normal cases,
+ * we should not expect the size requirement to change, although in theory the public API allows
+ * the IME to do that.
+ *
+ * <p>This design is to enable us to be able to reuse the backing remote view while still keeping
+ * the callbacks relatively well aligned. For example, if we allow multiple remote IME binder
+ * callbacks to call into one instance of this class, then binder A may call in with width/height
+ * X for which we create a view (i.e. {@link RemoteInlineSuggestionUi}) for it,
+ *
+ * See also {@link RemoteInlineSuggestionUi} for relevant information.
+ */
+public final class InlineContentProviderImpl extends IInlineContentProvider.Stub {
+
+ // TODO(b/153615023): consider not holding strong reference to heavy objects in this stub, to
+ // avoid memory leak in case the client app is holding the remote reference for a longer
+ // time than expected. Essentially we need strong reference in the system process to
+ // the member variables, but weak reference to them in the IInlineContentProvider.Stub.
+
+ private static final String TAG = InlineContentProviderImpl.class.getSimpleName();
+
+ private final Handler mHandler = FgThread.getHandler();;
+
+ @NonNull
+ private final RemoteInlineSuggestionViewConnector mRemoteInlineSuggestionViewConnector;
+ @Nullable
+ private RemoteInlineSuggestionUi mRemoteInlineSuggestionUi;
+
+ private boolean mProvideContentCalled = false;
+
+ InlineContentProviderImpl(
+ @NonNull RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector,
+ @Nullable RemoteInlineSuggestionUi remoteInlineSuggestionUi) {
+ mRemoteInlineSuggestionViewConnector = remoteInlineSuggestionViewConnector;
+ mRemoteInlineSuggestionUi = remoteInlineSuggestionUi;
+ }
+
+ /**
+ * Returns a new instance of this class, with the same {@code mInlineSuggestionRenderer} and
+ * {@code mRemoteInlineSuggestionUi}. The latter may or may not be reusable depending on the
+ * size information provided when the client calls {@link #provideContent(int, int,
+ * IInlineContentCallback)}.
+ */
+ @NonNull
+ public InlineContentProviderImpl copy() {
+ return new InlineContentProviderImpl(mRemoteInlineSuggestionViewConnector,
+ mRemoteInlineSuggestionUi);
+ }
+
+ /**
+ * Provides a SurfacePackage associated with the inline suggestion view to the IME. If such
+ * view doesn't exit, then create a new one. This method should be called once per lifecycle
+ * of this object. Any further calls to the method will be ignored.
+ */
+ @Override
+ public void provideContent(int width, int height, IInlineContentCallback callback) {
+ mHandler.post(() -> handleProvideContent(width, height, callback));
+ }
+
+ @Override
+ public void requestSurfacePackage() {
+ mHandler.post(this::handleGetSurfacePackage);
+ }
+
+ @Override
+ public void onSurfacePackageReleased() {
+ mHandler.post(this::handleOnSurfacePackageReleased);
+ }
+
+ private void handleProvideContent(int width, int height, IInlineContentCallback callback) {
+ if (sVerbose) Slog.v(TAG, "handleProvideContent");
+ if (mProvideContentCalled) {
+ // This method should only be called once.
+ return;
+ }
+ mProvideContentCalled = true;
+ if (mRemoteInlineSuggestionUi == null || !mRemoteInlineSuggestionUi.match(width, height)) {
+ mRemoteInlineSuggestionUi = new RemoteInlineSuggestionUi(
+ mRemoteInlineSuggestionViewConnector,
+ width, height, mHandler);
+ }
+ mRemoteInlineSuggestionUi.setInlineContentCallback(callback);
+ mRemoteInlineSuggestionUi.requestSurfacePackage();
+ }
+
+ private void handleGetSurfacePackage() {
+ if (sVerbose) Slog.v(TAG, "handleGetSurfacePackage");
+ if (!mProvideContentCalled || mRemoteInlineSuggestionUi == null) {
+ // provideContent should be called first, and remote UI should not be null.
+ return;
+ }
+ mRemoteInlineSuggestionUi.requestSurfacePackage();
+ }
+
+ private void handleOnSurfacePackageReleased() {
+ if (sVerbose) Slog.v(TAG, "handleOnSurfacePackageReleased");
+ if (!mProvideContentCalled || mRemoteInlineSuggestionUi == null) {
+ // provideContent should be called first, and remote UI should not be null.
+ return;
+ }
+ mRemoteInlineSuggestionUi.surfacePackageReleased();
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 79c9efa48d73..e74463a8584b 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -24,14 +24,11 @@ import android.annotation.Nullable;
import android.content.Intent;
import android.content.IntentSender;
import android.os.IBinder;
-import android.os.RemoteException;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
-import android.service.autofill.IInlineSuggestionUiCallback;
import android.service.autofill.InlinePresentation;
import android.text.TextUtils;
import android.util.Slog;
-import android.view.SurfaceControlViewHost;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -41,12 +38,8 @@ import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InlineSuggestionsResponse;
import android.widget.inline.InlinePresentationSpec;
-import com.android.internal.view.inline.IInlineContentCallback;
import com.android.internal.view.inline.IInlineContentProvider;
-import com.android.server.LocalServices;
-import com.android.server.UiThread;
import com.android.server.autofill.RemoteInlineSuggestionRenderService;
-import com.android.server.inputmethod.InputMethodManagerInternal;
import java.util.ArrayList;
import java.util.List;
@@ -73,6 +66,27 @@ public final class InlineSuggestionFactory {
}
/**
+ * Returns a copy of the response, that internally copies the {@link IInlineContentProvider}
+ * so that it's not reused by the remote IME process across different inline suggestions.
+ * See {@link InlineContentProviderImpl} for why this is needed.
+ */
+ @NonNull
+ public static InlineSuggestionsResponse copy(@NonNull InlineSuggestionsResponse response) {
+ final ArrayList<InlineSuggestion> copiedInlineSuggestions = new ArrayList<>();
+ for (InlineSuggestion inlineSuggestion : response.getInlineSuggestions()) {
+ final IInlineContentProvider contentProvider = inlineSuggestion.getContentProvider();
+ if (contentProvider instanceof InlineContentProviderImpl) {
+ copiedInlineSuggestions.add(new
+ InlineSuggestion(inlineSuggestion.getInfo(),
+ ((InlineContentProviderImpl) contentProvider).copy()));
+ } else {
+ copiedInlineSuggestions.add(inlineSuggestion);
+ }
+ }
+ return new InlineSuggestionsResponse(copiedInlineSuggestions);
+ }
+
+ /**
* Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by the
* autofill service, potentially filtering the datasets.
*/
@@ -276,78 +290,20 @@ public final class InlineSuggestionFactory {
inlinePresentation.isPinned());
}
- private static IInlineContentProvider.Stub createInlineContentProvider(
+ private static IInlineContentProvider createInlineContentProvider(
@NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
@NonNull Runnable onErrorCallback,
@NonNull Consumer<IntentSender> intentSenderConsumer,
@Nullable RemoteInlineSuggestionRenderService remoteRenderService,
@Nullable IBinder hostInputToken,
int displayId) {
- return new IInlineContentProvider.Stub() {
- @Override
- public void provideContent(int width, int height, IInlineContentCallback callback) {
- UiThread.getHandler().post(() -> {
- final IInlineSuggestionUiCallback uiCallback = createInlineSuggestionUiCallback(
- callback, onClickAction, onErrorCallback, intentSenderConsumer);
-
- if (remoteRenderService == null) {
- Slog.e(TAG, "RemoteInlineSuggestionRenderService is null");
- return;
- }
-
- remoteRenderService.renderSuggestion(uiCallback, inlinePresentation,
- width, height, hostInputToken, displayId);
- });
- }
- };
- }
-
- private static IInlineSuggestionUiCallback.Stub createInlineSuggestionUiCallback(
- @NonNull IInlineContentCallback callback, @NonNull Runnable onAutofillCallback,
- @NonNull Runnable onErrorCallback,
- @NonNull Consumer<IntentSender> intentSenderConsumer) {
- return new IInlineSuggestionUiCallback.Stub() {
- @Override
- public void onClick() throws RemoteException {
- onAutofillCallback.run();
- callback.onClick();
- }
-
- @Override
- public void onLongClick() throws RemoteException {
- callback.onLongClick();
- }
-
- @Override
- public void onContent(SurfaceControlViewHost.SurfacePackage surface, int width,
- int height)
- throws RemoteException {
- callback.onContent(surface, width, height);
- surface.release();
- }
-
- @Override
- public void onError() throws RemoteException {
- onErrorCallback.run();
- }
-
- @Override
- public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId)
- throws RemoteException {
- final InputMethodManagerInternal inputMethodManagerInternal =
- LocalServices.getService(InputMethodManagerInternal.class);
- if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
- displayId)) {
- Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
- onErrorCallback.run();
- }
- }
-
- @Override
- public void onStartIntentSender(IntentSender intentSender) {
- intentSenderConsumer.accept(intentSender);
- }
- };
+ RemoteInlineSuggestionViewConnector
+ remoteInlineSuggestionViewConnector = new RemoteInlineSuggestionViewConnector(
+ remoteRenderService, inlinePresentation, hostInputToken, displayId, onClickAction,
+ onErrorCallback, intentSenderConsumer);
+ InlineContentProviderImpl inlineContentProvider = new InlineContentProviderImpl(
+ remoteInlineSuggestionViewConnector, null);
+ return inlineContentProvider;
}
private InlineSuggestionFactory() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
new file mode 100644
index 000000000000..00a5283c9b1f
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentSender;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.autofill.IInlineSuggestionUi;
+import android.service.autofill.IInlineSuggestionUiCallback;
+import android.service.autofill.ISurfacePackageResultCallback;
+import android.util.Slog;
+import android.view.SurfaceControlViewHost;
+
+import com.android.internal.view.inline.IInlineContentCallback;
+
+/**
+ * The instance of this class lives in the system server, orchestrating the communication between
+ * the remote process owning embedded view (i.e. ExtServices) and the remote process hosting the
+ * embedded view (i.e. IME). It's also responsible for releasing the embedded view from the owning
+ * process when it's not longer needed in the hosting process.
+ *
+ * <p>An instance of this class may be reused to associate with multiple instances of
+ * {@link InlineContentProviderImpl}s, each of which wraps a callback from the IME. But at any
+ * given time, there is only one active IME callback which this class will callback into.
+ *
+ * <p>This class is thread safe, because all the outside calls are piped into the same single
+ * thread handler to be processed.
+ *
+ * TODO(b/154683107): implement the reference counting in case there are multiple active
+ * SurfacePackages at the same time. This will not happen for now since all the InlineSuggestions
+ * sharing the same UI will be sent to the same IME window, so the previous view will be detached
+ * before the new view are attached to the window.
+ */
+final class RemoteInlineSuggestionUi {
+
+ private static final String TAG = RemoteInlineSuggestionUi.class.getSimpleName();
+
+ // The delay time to release the remote inline suggestion view (in the renderer
+ // process) after receiving a signal about the surface package being released due to being
+ // detached from the window in the host app (in the IME process). The release will be
+ // canceled if the host app reattaches the view to a window within this delay time.
+ // TODO(b/154683107): try out using the Chroreographer to schedule the release right at the
+ // next frame. Basically if the view is not re-attached to the window immediately in the next
+ // frame after it was detached, then it will be released.
+ private static final long RELEASE_REMOTE_VIEW_HOST_DELAY_MS = 200;
+
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final RemoteInlineSuggestionViewConnector mRemoteInlineSuggestionViewConnector;
+ private final int mWidth;
+ private final int mHeight;
+ @NonNull
+ private final InlineSuggestionUiCallbackImpl mInlineSuggestionUiCallback;
+
+ @Nullable
+ private IInlineContentCallback mInlineContentCallback; // from IME
+
+ /**
+ * Remote inline suggestion view, backed by an instance of {@link SurfaceControlViewHost} in
+ * the render service process. We takes care of releasing it when there is no remote
+ * reference to it (from IME), and we will create a new instance of the view when it's needed
+ * by IME again.
+ */
+ @Nullable
+ private IInlineSuggestionUi mInlineSuggestionUi;
+ private boolean mWaitingForUiCreation = false;
+ private int mActualWidth;
+ private int mActualHeight;
+
+ @Nullable
+ private Runnable mDelayedReleaseViewRunnable;
+
+ RemoteInlineSuggestionUi(
+ @NonNull RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector,
+ int width, int height, Handler handler) {
+ mHandler = handler;
+ mRemoteInlineSuggestionViewConnector = remoteInlineSuggestionViewConnector;
+ mWidth = width;
+ mHeight = height;
+ mInlineSuggestionUiCallback = new InlineSuggestionUiCallbackImpl();
+ }
+
+ /**
+ * Updates the callback from the IME process. It'll swap out the previous IME callback, and
+ * all the subsequent callback events (onClick, onLongClick, touch event transfer, etc) will
+ * be directed to the new callback.
+ */
+ void setInlineContentCallback(@NonNull IInlineContentCallback inlineContentCallback) {
+ mHandler.post(() -> {
+ mInlineContentCallback = inlineContentCallback;
+ });
+ }
+
+ /**
+ * Handles the request from the IME process to get a new surface package. May create a new
+ * view in the renderer process if the existing view is already released.
+ */
+ void requestSurfacePackage() {
+ mHandler.post(this::handleRequestSurfacePackage);
+ }
+
+ /**
+ * Handles the signal from the IME process that the previously sent surface package has been
+ * released.
+ */
+ void surfacePackageReleased() {
+ mHandler.post(this::handleSurfacePackageReleased);
+ }
+
+ /**
+ * Returns true if the provided size matches the remote view's size.
+ */
+ boolean match(int width, int height) {
+ return mWidth == width && mHeight == height;
+ }
+
+ private void handleSurfacePackageReleased() {
+ cancelPendingReleaseViewRequest();
+
+ // Schedule a delayed release view request
+ mDelayedReleaseViewRunnable = () -> {
+ if (mInlineSuggestionUi != null) {
+ try {
+ mInlineSuggestionUi.releaseSurfaceControlViewHost();
+ mInlineSuggestionUi = null;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling releaseSurfaceControlViewHost");
+ }
+ }
+ mDelayedReleaseViewRunnable = null;
+ };
+ mHandler.postDelayed(mDelayedReleaseViewRunnable, RELEASE_REMOTE_VIEW_HOST_DELAY_MS);
+ }
+
+ private void handleRequestSurfacePackage() {
+ cancelPendingReleaseViewRequest();
+
+ if (mInlineSuggestionUi == null) {
+ if (mWaitingForUiCreation) {
+ // This could happen in the following case: the remote embedded view was released
+ // when previously detached from window. An event after that to re-attached to
+ // the window will cause us calling the renderSuggestion again. Now, before the
+ // render call returns a new surface package, if the view is detached and
+ // re-attached to the window, causing this method to be called again, we will get
+ // to this state. This request will be ignored and the surface package will still
+ // be sent back once the view is rendered.
+ if (sDebug) Slog.d(TAG, "Inline suggestion ui is not ready");
+ } else {
+ mRemoteInlineSuggestionViewConnector.renderSuggestion(mWidth, mHeight,
+ mInlineSuggestionUiCallback);
+ mWaitingForUiCreation = true;
+ }
+ } else {
+ try {
+ mInlineSuggestionUi.getSurfacePackage(new ISurfacePackageResultCallback.Stub() {
+ @Override
+ public void onResult(SurfaceControlViewHost.SurfacePackage result)
+ throws RemoteException {
+ if (sDebug) Slog.d(TAG, "Sending new SurfacePackage to IME");
+ mInlineContentCallback.onContent(result, mActualWidth, mActualHeight);
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling getSurfacePackage.");
+ }
+ }
+ }
+
+ private void cancelPendingReleaseViewRequest() {
+ if (mDelayedReleaseViewRunnable != null) {
+ mHandler.removeCallbacks(mDelayedReleaseViewRunnable);
+ mDelayedReleaseViewRunnable = null;
+ }
+ }
+
+ /**
+ * This is called when a new inline suggestion UI is inflated from the ext services.
+ */
+ private void handleInlineSuggestionUiReady(IInlineSuggestionUi content,
+ SurfaceControlViewHost.SurfacePackage surfacePackage, int width, int height) {
+ mInlineSuggestionUi = content;
+ mWaitingForUiCreation = false;
+ mActualWidth = width;
+ mActualHeight = height;
+ if (mInlineContentCallback != null) {
+ try {
+ mInlineContentCallback.onContent(surfacePackage, mActualWidth, mActualHeight);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling onContent");
+ }
+ }
+ if (surfacePackage != null) {
+ surfacePackage.release();
+ }
+ }
+
+ private void handleOnClick() {
+ // Autofill the value
+ mRemoteInlineSuggestionViewConnector.onClick();
+
+ // Notify the remote process (IME) that hosts the embedded UI that it's clicked
+ if (mInlineContentCallback != null) {
+ try {
+ mInlineContentCallback.onClick();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling onClick");
+ }
+ }
+ }
+
+ private void handleOnLongClick() {
+ // Notify the remote process (IME) that hosts the embedded UI that it's long clicked
+ if (mInlineContentCallback != null) {
+ try {
+ mInlineContentCallback.onLongClick();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling onLongClick");
+ }
+ }
+ }
+
+ private void handleOnError() {
+ mRemoteInlineSuggestionViewConnector.onError();
+ }
+
+ private void handleOnTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) {
+ mRemoteInlineSuggestionViewConnector.onTransferTouchFocusToImeWindow(sourceInputToken,
+ displayId);
+ }
+
+ private void handleOnStartIntentSender(IntentSender intentSender) {
+ mRemoteInlineSuggestionViewConnector.onStartIntentSender(intentSender);
+ }
+
+ /**
+ * Responsible for communicating with the inline suggestion view owning process.
+ */
+ private class InlineSuggestionUiCallbackImpl extends IInlineSuggestionUiCallback.Stub {
+
+ @Override
+ public void onClick() {
+ mHandler.post(RemoteInlineSuggestionUi.this::handleOnClick);
+ }
+
+ @Override
+ public void onLongClick() {
+ mHandler.post(RemoteInlineSuggestionUi.this::handleOnLongClick);
+ }
+
+ @Override
+ public void onContent(IInlineSuggestionUi content,
+ SurfaceControlViewHost.SurfacePackage surface, int width, int height) {
+ mHandler.post(() -> handleInlineSuggestionUiReady(content, surface, width, height));
+ }
+
+ @Override
+ public void onError() {
+ mHandler.post(RemoteInlineSuggestionUi.this::handleOnError);
+ }
+
+ @Override
+ public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) {
+ mHandler.post(() -> handleOnTransferTouchFocusToImeWindow(sourceInputToken, displayId));
+ }
+
+ @Override
+ public void onStartIntentSender(IntentSender intentSender) {
+ mHandler.post(() -> handleOnStartIntentSender(intentSender));
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
new file mode 100644
index 000000000000..9d23c171800d
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.IntentSender;
+import android.os.IBinder;
+import android.service.autofill.IInlineSuggestionUiCallback;
+import android.service.autofill.InlinePresentation;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.autofill.RemoteInlineSuggestionRenderService;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.function.Consumer;
+
+/**
+ * Wraps the parameters needed to create a new inline suggestion view in the remote renderer
+ * service, and handles the callback from the events on the created remote view.
+ */
+final class RemoteInlineSuggestionViewConnector {
+ private static final String TAG = RemoteInlineSuggestionViewConnector.class.getSimpleName();
+
+ @Nullable
+ private final RemoteInlineSuggestionRenderService mRemoteRenderService;
+ @NonNull
+ private final InlinePresentation mInlinePresentation;
+ @Nullable
+ private final IBinder mHostInputToken;
+ private final int mDisplayId;
+
+ @NonNull
+ private final Runnable mOnAutofillCallback;
+ @NonNull
+ private final Runnable mOnErrorCallback;
+ @NonNull
+ private final Consumer<IntentSender> mStartIntentSenderFromClientApp;
+
+ RemoteInlineSuggestionViewConnector(
+ @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
+ @NonNull InlinePresentation inlinePresentation,
+ @Nullable IBinder hostInputToken,
+ int displayId,
+ @NonNull Runnable onAutofillCallback,
+ @NonNull Runnable onErrorCallback,
+ @NonNull Consumer<IntentSender> startIntentSenderFromClientApp) {
+ mRemoteRenderService = remoteRenderService;
+ mInlinePresentation = inlinePresentation;
+ mHostInputToken = hostInputToken;
+ mDisplayId = displayId;
+
+ mOnAutofillCallback = onAutofillCallback;
+ mOnErrorCallback = onErrorCallback;
+ mStartIntentSenderFromClientApp = startIntentSenderFromClientApp;
+ }
+
+ /**
+ * Calls the remote renderer service to create a new inline suggestion view.
+ *
+ * @return true if the call is made to the remote renderer service, false otherwise.
+ */
+ public boolean renderSuggestion(int width, int height,
+ @NonNull IInlineSuggestionUiCallback callback) {
+ if (mRemoteRenderService != null) {
+ if (sDebug) Slog.d(TAG, "Request to recreate the UI");
+ mRemoteRenderService.renderSuggestion(callback, mInlinePresentation, width, height,
+ mHostInputToken, mDisplayId);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handles the callback for the event of remote view being clicked.
+ */
+ public void onClick() {
+ mOnAutofillCallback.run();
+ }
+
+ /**
+ * Handles the callback for the remote error when creating or interacting with the view.
+ */
+ public void onError() {
+ mOnErrorCallback.run();
+ }
+
+ /**
+ * Handles the callback for transferring the touch event on the remote view to the IME
+ * process.
+ */
+ public void onTransferTouchFocusToImeWindow(IBinder sourceInputToken, int displayId) {
+ final InputMethodManagerInternal inputMethodManagerInternal =
+ LocalServices.getService(InputMethodManagerInternal.class);
+ if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken,
+ displayId)) {
+ Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME");
+ mOnErrorCallback.run();
+ }
+ }
+
+ /**
+ * Handles starting an intent sender from the client app's process.
+ */
+ public void onStartIntentSender(IntentSender intentSender) {
+ mStartIntentSenderFromClientApp.accept(intentSender);
+ }
+}
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index bfcde97d6c91..829fca66ec0d 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -253,10 +253,7 @@ public class RescueParty {
logCriticalInfo(Log.DEBUG,
"Finished rescue level " + levelToString(level));
} catch (Throwable t) {
- final String msg = ExceptionUtils.getCompleteMessage(t);
- EventLogTags.writeRescueFailure(level, msg);
- logCriticalInfo(Log.ERROR,
- "Failed rescue level " + levelToString(level) + ": " + msg);
+ logRescueException(level, t);
}
}
@@ -274,11 +271,31 @@ public class RescueParty {
resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, failedPackage);
break;
case LEVEL_FACTORY_RESET:
- RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
+ // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
+ // when device shutting down.
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
+ } catch (Throwable t) {
+ logRescueException(level, t);
+ }
+ }
+ };
+ Thread thread = new Thread(runnable);
+ thread.start();
break;
}
}
+ private static void logRescueException(int level, Throwable t) {
+ final String msg = ExceptionUtils.getCompleteMessage(t);
+ EventLogTags.writeRescueFailure(level, msg);
+ logCriticalInfo(Log.ERROR,
+ "Failed rescue level " + levelToString(level) + ": " + msg);
+ }
+
private static int mapRescueLevelToUserImpact(int rescueLevel) {
switch(rescueLevel) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 70b639846e1e..26cb208a3d47 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -450,6 +450,14 @@ final class UiModeManagerService extends SystemService {
return oldNightMode != mNightMode;
}
+ private static long toMilliSeconds(LocalTime t) {
+ return t.toNanoOfDay() / 1000;
+ }
+
+ private static LocalTime fromMilliseconds(long t) {
+ return LocalTime.ofNanoOfDay(t * 1000);
+ }
+
private void registerScreenOffEventLocked() {
if (mPowerSave) return;
mWaitForScreenOff = true;
@@ -1385,8 +1393,11 @@ final class UiModeManagerService extends SystemService {
pw.println("UiModeManager service (uimode) commands:");
pw.println(" help");
pw.println(" Print this help text.");
- pw.println(" night [yes|no|auto]");
+ pw.println(" night [yes|no|auto|custom]");
pw.println(" Set or read night mode.");
+ pw.println(" time [start|end] <ISO time>");
+ pw.println(" Set custom start/end schedule time"
+ + " (night mode must be set to custom to apply).");
}
@Override
@@ -1399,6 +1410,8 @@ final class UiModeManagerService extends SystemService {
switch (cmd) {
case "night":
return handleNightMode();
+ case "time":
+ return handleCustomTime();
default:
return handleDefaultCommands(cmd);
}
@@ -1409,6 +1422,34 @@ final class UiModeManagerService extends SystemService {
return -1;
}
+ private int handleCustomTime() throws RemoteException {
+ final String modeStr = getNextArg();
+ if (modeStr == null) {
+ printCustomTime();
+ return 0;
+ }
+ switch (modeStr) {
+ case "start":
+ final String start = getNextArg();
+ mInterface.setCustomNightModeStart(toMilliSeconds(LocalTime.parse(start)));
+ return 0;
+ case "end":
+ final String end = getNextArg();
+ mInterface.setCustomNightModeEnd(toMilliSeconds(LocalTime.parse(end)));
+ return 0;
+ default:
+ getErrPrintWriter().println("command must be in [start|end]");
+ return -1;
+ }
+ }
+
+ private void printCustomTime() throws RemoteException {
+ getOutPrintWriter().println("start " + fromMilliseconds(
+ mInterface.getCustomNightModeStart()).toString());
+ getOutPrintWriter().println("end " + fromMilliseconds(
+ mInterface.getCustomNightModeEnd()).toString());
+ }
+
private int handleNightMode() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
final String modeStr = getNextArg();
@@ -1424,7 +1465,8 @@ final class UiModeManagerService extends SystemService {
return 0;
} else {
err.println("Error: mode must be '" + NIGHT_MODE_STR_YES + "', '"
- + NIGHT_MODE_STR_NO + "', or '" + NIGHT_MODE_STR_AUTO + "'");
+ + NIGHT_MODE_STR_NO + "', or '" + NIGHT_MODE_STR_AUTO
+ + "', or '" + NIGHT_MODE_STR_CUSTOM + "'");
return -1;
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 70fbca5b4798..7e28e94a17bb 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -170,6 +170,8 @@ public class BiometricService extends SystemService {
final String mOpPackageName;
// Info to be shown on BiometricDialog when all cookies are returned.
final Bundle mBundle;
+ // Random id associated to this AuthSession
+ final int mSysUiSessionId;
final int mCallingUid;
final int mCallingPid;
final int mCallingUserId;
@@ -203,11 +205,13 @@ public class BiometricService extends SystemService {
mClientReceiver = receiver;
mOpPackageName = opPackageName;
mBundle = bundle;
+ mSysUiSessionId = mRandom.nextInt();
mCallingUid = callingUid;
mCallingPid = callingPid;
mCallingUserId = callingUserId;
mModality = modality;
mRequireConfirmation = requireConfirmation;
+ Slog.d(TAG, "New AuthSession, mSysUiSessionId: " + mSysUiSessionId);
}
boolean isCrypto() {
@@ -1457,7 +1461,8 @@ public class BiometricService extends SystemService {
false /* requireConfirmation */,
mCurrentAuthSession.mUserId,
mCurrentAuthSession.mOpPackageName,
- mCurrentAuthSession.mSessionId);
+ mCurrentAuthSession.mSessionId,
+ mCurrentAuthSession.mSysUiSessionId);
} else {
mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode);
mPendingAuthSession = null;
@@ -1680,7 +1685,8 @@ public class BiometricService extends SystemService {
mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle,
mInternalReceiver, modality, requireConfirmation, userId,
mCurrentAuthSession.mOpPackageName,
- mCurrentAuthSession.mSessionId);
+ mCurrentAuthSession.mSessionId,
+ mCurrentAuthSession.mSysUiSessionId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
@@ -1766,7 +1772,8 @@ public class BiometricService extends SystemService {
false /* requireConfirmation */,
mCurrentAuthSession.mUserId,
mCurrentAuthSession.mOpPackageName,
- sessionId);
+ sessionId,
+ mCurrentAuthSession.mSysUiSessionId);
} else {
mPendingAuthSession.mState = STATE_AUTH_CALLED;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b2bf1fce0d07..ccbe96f30e04 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -2667,8 +2667,7 @@ public class LocationManagerService extends ILocationManager.Stub {
mRequestStatistics.statistics);
for (Map.Entry<PackageProviderKey, PackageStatistics> entry
: sorted.entrySet()) {
- PackageProviderKey key = entry.getKey();
- ipw.println(key.mPackageName + ": " + key.mProviderName + ": " + entry.getValue());
+ ipw.println(entry.getKey() + ": " + entry.getValue());
}
ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
index dcdf48ba08d2..e629b428d864 100644
--- a/services/core/java/com/android/server/location/LocationRequestStatistics.java
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -16,6 +16,7 @@
package com.android.server.location;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.util.Log;
@@ -25,6 +26,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Objects;
@@ -121,14 +123,30 @@ public class LocationRequestStatistics {
this.mProviderName = providerName;
}
+ @NonNull
+ @Override
+ public String toString() {
+ return mProviderName + ": " + mPackageName
+ + (mFeatureId == null ? "" : ": " + mFeatureId);
+ }
+
+ /**
+ * Sort by provider, then package, then feature
+ */
@Override
public int compareTo(PackageProviderKey other) {
final int providerCompare = mProviderName.compareTo(other.mProviderName);
if (providerCompare != 0) {
return providerCompare;
- } else {
- return mProviderName.compareTo(other.mProviderName);
}
+
+ final int packageCompare = mPackageName.compareTo(other.mPackageName);
+ if (packageCompare != 0) {
+ return packageCompare;
+ }
+
+ return Objects.compare(mFeatureId, other.mFeatureId, Comparator
+ .nullsFirst(String::compareTo));
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index a5de90c93aab..a435f1e16b80 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -315,9 +315,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
return;
}
- sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
- .setProviderId(getUniqueId())
- .build();
+ sessionInfo = updateSessionInfo(sessionInfo);
boolean duplicateSessionAlreadyExists = false;
synchronized (mLock) {
@@ -348,9 +346,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
return;
}
- sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
- .setProviderId(getUniqueId())
- .build();
+ sessionInfo = updateSessionInfo(sessionInfo);
boolean found = false;
synchronized (mLock) {
@@ -380,9 +376,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
return;
}
- sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
- .setProviderId(getUniqueId())
- .build();
+ sessionInfo = updateSessionInfo(sessionInfo);
boolean found = false;
synchronized (mLock) {
@@ -403,6 +397,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
mCallback.onSessionReleased(this, sessionInfo);
}
+ private RoutingSessionInfo updateSessionInfo(RoutingSessionInfo sessionInfo) {
+ return new RoutingSessionInfo.Builder(sessionInfo)
+ .setOwnerPackageName(mComponentName.getPackageName())
+ .setProviderId(getUniqueId())
+ .build();
+ }
+
private void onRequestFailed(Connection connection, long requestId, int reason) {
if (mActiveConnection != connection) {
return;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index ec941c8aea59..3283fd9b2c51 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -799,7 +799,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
writePolicyAL();
}
- enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, true);
setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
updateRulesForGlobalChangeAL(false);
updateNotificationsNL();
@@ -871,6 +870,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
new NetworkRequest.Builder().build(), mNetworkCallback);
mAppStandby.addListener(new NetPolicyAppIdleStateChangeListener());
+ synchronized (mUidRulesFirstLock) {
+ updateRulesForAppIdleParoleUL();
+ }
// Listen for subscriber changes
mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener(
@@ -3893,6 +3895,39 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
/**
+ * Toggle the firewall standby chain and inform listeners if the uid rules have effectively
+ * changed.
+ */
+ @GuardedBy("mUidRulesFirstLock")
+ private void updateRulesForAppIdleParoleUL() {
+ final boolean paroled = mAppStandby.isInParole();
+ final boolean enableChain = !paroled;
+ enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, enableChain);
+
+ int ruleCount = mUidFirewallStandbyRules.size();
+ for (int i = 0; i < ruleCount; i++) {
+ final int uid = mUidFirewallStandbyRules.keyAt(i);
+ int oldRules = mUidRules.get(uid);
+ if (enableChain) {
+ // Chain wasn't enabled before and the other power-related
+ // chains are whitelists, so we can clear the
+ // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
+ // the effective rules result in blocking network access.
+ oldRules &= MASK_METERED_NETWORKS;
+ } else {
+ // Skip if it had no restrictions to begin with
+ if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
+ }
+ final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules, paroled);
+ if (newUidRules == RULE_NONE) {
+ mUidRules.delete(uid);
+ } else {
+ mUidRules.put(uid, newUidRules);
+ }
+ }
+ }
+
+ /**
* Update rules that might be changed by {@link #mRestrictBackground},
* {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
@@ -4347,7 +4382,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private void updateRulesForPowerRestrictionsUL(int uid) {
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules);
+ final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false);
if (newUidRules == RULE_NONE) {
mUidRules.delete(uid);
@@ -4361,28 +4396,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
*
* @param uid the uid of the app to update rules for
* @param oldUidRules the current rules for the uid, in order to determine if there's a change
+ * @param paroled whether to ignore idle state of apps and only look at other restrictions
*
* @return the new computed rules for the uid
*/
- private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules) {
+ private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
- "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules);
+ "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+ + (paroled ? "P" : "-"));
}
try {
- return updateRulesForPowerRestrictionsULInner(uid, oldUidRules);
+ return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
- private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules) {
+ private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
}
- final boolean isIdle = isUidIdle(uid);
+ final boolean isIdle = !paroled && isUidIdle(uid);
final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
@@ -4452,6 +4489,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
} catch (NameNotFoundException nnfe) {
}
}
+
+ @Override
+ public void onParoleStateChanged(boolean isParoleOn) {
+ synchronized (mUidRulesFirstLock) {
+ mLogger.paroleStateChanged(isParoleOn);
+ updateRulesForAppIdleParoleUL();
+ }
+ }
}
private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 8e3de1598275..a9fa2b1bd491 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1382,13 +1382,21 @@ public final class NotificationRecord {
*/
public boolean isConversation() {
Notification notification = getNotification();
- if (mChannel.isDemoted()
- || !Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
+ if (!Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) {
+ // very common; don't bother logging
+ return false;
+ }
+ if (mChannel.isDemoted()) {
return false;
}
if (mIsNotConversationOverride) {
return false;
}
+ if (mTargetSdkVersion >= Build.VERSION_CODES.R
+ && Notification.MessagingStyle.class.equals(notification.getNotificationStyle())
+ && mShortcutInfo == null) {
+ return false;
+ }
return true;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 3367cd556b2b..5b5f334803e5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -676,7 +676,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
- stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID,
+ stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
@@ -830,7 +830,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
synchronized (mSessions) {
final PackageInstallerSession session = mSessions.get(sessionId);
- return session != null
+ return (session != null && !(session.isStaged() && session.isDestroyed()))
? session.generateInfoForCaller(true /*withIcon*/, Binder.getCallingUid())
: null;
}
@@ -851,7 +851,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- if (session.userId == userId && !session.hasParentSessionId()) {
+ if (session.userId == userId && !session.hasParentSessionId()
+ && !(session.isStaged() && session.isDestroyed())) {
result.add(session.generateInfoForCaller(false, callingUid));
}
}
@@ -1269,7 +1270,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
public void onStagedSessionChanged(PackageInstallerSession session) {
session.markUpdated();
writeSessionsAsync();
- if (mOkToSendBroadcasts) {
+ if (mOkToSendBroadcasts && !session.isDestroyed()) {
// we don't scrub the data here as this is sent only to the installer several
// privileged system packages
mPm.sendSessionUpdatedBroadcast(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d3f377e135cb..e0d057a8d7f3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -190,6 +190,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
private static final String ATTR_PREPARED = "prepared";
private static final String ATTR_COMMITTED = "committed";
+ private static final String ATTR_DESTROYED = "destroyed";
private static final String ATTR_SEALED = "sealed";
private static final String ATTR_MULTI_PACKAGE = "multiPackage";
private static final String ATTR_PARENT_SESSION_ID = "parentSessionId";
@@ -533,7 +534,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
File stageDir, String stageCid, InstallationFile[] files, boolean prepared,
- boolean committed, boolean sealed,
+ boolean committed, boolean destroyed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
String stagedSessionErrorMessage) {
@@ -579,6 +580,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mPrepared = prepared;
mCommitted = committed;
+ mDestroyed = destroyed;
mStagedSessionReady = isReady;
mStagedSessionFailed = isFailed;
mStagedSessionApplied = isApplied;
@@ -713,6 +715,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ /** {@hide} */
+ boolean isDestroyed() {
+ synchronized (mLock) {
+ return mDestroyed;
+ }
+ }
+
/** Returns true if a staged session has reached a final state and can be forgotten about */
public boolean isStagedAndInTerminalState() {
synchronized (mLock) {
@@ -1068,6 +1077,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
/**
+ * Check if the caller is the owner of this session. Otherwise throw a
+ * {@link SecurityException}.
+ */
+ @GuardedBy("mLock")
+ private void assertCallerIsOwnerOrRootOrSystemLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid
+ && callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("Session does not belong to uid " + callingUid);
+ }
+ }
+
+ /**
* If anybody is reading or writing data of the session, throw an {@link SecurityException}.
*/
@GuardedBy("mLock")
@@ -2552,7 +2574,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
+ mParentSessionId + " and may not be abandoned directly.");
}
synchronized (mLock) {
- assertCallerIsOwnerOrRootLocked();
+ if (params.isStaged && mDestroyed) {
+ // If a user abandons staged session in an unsafe state, then system will try to
+ // abandon the destroyed staged session when it is safe on behalf of the user.
+ assertCallerIsOwnerOrRootOrSystemLocked();
+ } else {
+ assertCallerIsOwnerOrRootLocked();
+ }
if (isStagedAndInTerminalState()) {
// We keep the session in the database if it's in a finalized state. It will be
@@ -2562,11 +2590,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
if (mCommitted && params.isStaged) {
- synchronized (mLock) {
- mDestroyed = true;
+ mDestroyed = true;
+ if (!mStagingManager.abortCommittedSessionLocked(this)) {
+ // Do not clean up the staged session from system. It is not safe yet.
+ mCallback.onStagedSessionChanged(this);
+ return;
}
- mStagingManager.abortCommittedSession(this);
-
cleanStageDir();
}
@@ -2926,6 +2955,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/** {@hide} */
void setStagedSessionReady() {
synchronized (mLock) {
+ if (mDestroyed) return; // Do not allow destroyed staged session to change state
mStagedSessionReady = true;
mStagedSessionApplied = false;
mStagedSessionFailed = false;
@@ -2939,6 +2969,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
String errorMessage) {
synchronized (mLock) {
+ if (mDestroyed) return; // Do not allow destroyed staged session to change state
mStagedSessionReady = false;
mStagedSessionApplied = false;
mStagedSessionFailed = true;
@@ -2953,6 +2984,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/** {@hide} */
void setStagedSessionApplied() {
synchronized (mLock) {
+ if (mDestroyed) return; // Do not allow destroyed staged session to change state
mStagedSessionReady = false;
mStagedSessionApplied = true;
mStagedSessionFailed = false;
@@ -3197,7 +3229,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException {
synchronized (mLock) {
- if (mDestroyed) {
+ if (mDestroyed && !params.isStaged) {
return;
}
@@ -3223,6 +3255,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
writeBooleanAttribute(out, ATTR_PREPARED, isPrepared());
writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted());
+ writeBooleanAttribute(out, ATTR_DESTROYED, isDestroyed());
writeBooleanAttribute(out, ATTR_SEALED, isSealed());
writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage);
@@ -3352,6 +3385,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
final boolean committed = readBooleanAttribute(in, ATTR_COMMITTED);
+ final boolean destroyed = readBooleanAttribute(in, ATTR_DESTROYED);
final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
final int parentSessionId = readIntAttribute(in, ATTR_PARENT_SESSION_ID,
SessionInfo.INVALID_ID);
@@ -3473,7 +3507,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
installSource, params, createdMillis, stageDir, stageCid, fileArray,
- prepared, committed, sealed, childSessionIdsArray, parentSessionId,
+ prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f31dbbf077bb..bde9d5735960 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5966,25 +5966,7 @@ public class PackageManagerService extends IPackageManager.Stub
|| shouldFilterApplicationLocked(ps2, callingUid, callingUserId)) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- SigningDetails p1SigningDetails = p1.getSigningDetails();
- SigningDetails p2SigningDetails = p2.getSigningDetails();
- int result = compareSignatures(p1SigningDetails.signatures,
- p2SigningDetails.signatures);
- // To support backwards compatibility with clients of this API expecting pre-key
- // rotation results if either of the packages has a signing lineage the oldest signer
- // in the lineage is used for signature verification.
- if (result != PackageManager.SIGNATURE_MATCH && (
- p1SigningDetails.hasPastSigningCertificates()
- || p2SigningDetails.hasPastSigningCertificates())) {
- Signature[] p1Signatures = p1SigningDetails.hasPastSigningCertificates()
- ? new Signature[]{p1SigningDetails.pastSigningCertificates[0]}
- : p1SigningDetails.signatures;
- Signature[] p2Signatures = p2SigningDetails.hasPastSigningCertificates()
- ? new Signature[]{p2SigningDetails.pastSigningCertificates[0]}
- : p2SigningDetails.signatures;
- result = compareSignatures(p1Signatures, p2Signatures);
- }
- return result;
+ return checkSignaturesInternal(p1.getSigningDetails(), p2.getSigningDetails());
}
}
@@ -5998,21 +5980,21 @@ public class PackageManagerService extends IPackageManager.Stub
final int appId2 = UserHandle.getAppId(uid2);
// reader
synchronized (mLock) {
- Signature[] s1;
- Signature[] s2;
+ SigningDetails p1SigningDetails;
+ SigningDetails p2SigningDetails;
Object obj = mSettings.getSettingLPr(appId1);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
if (isCallerInstantApp) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s1 = ((SharedUserSetting)obj).signatures.mSigningDetails.signatures;
+ p1SigningDetails = ((SharedUserSetting) obj).signatures.mSigningDetails;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (shouldFilterApplicationLocked(ps, callingUid, callingUserId)) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s1 = ps.signatures.mSigningDetails.signatures;
+ p1SigningDetails = ps.signatures.mSigningDetails;
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
@@ -6025,21 +6007,51 @@ public class PackageManagerService extends IPackageManager.Stub
if (isCallerInstantApp) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s2 = ((SharedUserSetting)obj).signatures.mSigningDetails.signatures;
+ p2SigningDetails = ((SharedUserSetting) obj).signatures.mSigningDetails;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (shouldFilterApplicationLocked(ps, callingUid, callingUserId)) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- s2 = ps.signatures.mSigningDetails.signatures;
+ p2SigningDetails = ps.signatures.mSigningDetails;
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
} else {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- return compareSignatures(s1, s2);
+ return checkSignaturesInternal(p1SigningDetails, p2SigningDetails);
+ }
+ }
+
+ private int checkSignaturesInternal(SigningDetails p1SigningDetails,
+ SigningDetails p2SigningDetails) {
+ if (p1SigningDetails == null) {
+ return p2SigningDetails == null
+ ? PackageManager.SIGNATURE_NEITHER_SIGNED
+ : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
}
+ if (p2SigningDetails == null) {
+ return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+ }
+ int result = compareSignatures(p1SigningDetails.signatures, p2SigningDetails.signatures);
+ if (result == PackageManager.SIGNATURE_MATCH) {
+ return result;
+ }
+ // To support backwards compatibility with clients of this API expecting pre-key
+ // rotation results if either of the packages has a signing lineage the oldest signer
+ // in the lineage is used for signature verification.
+ if (p1SigningDetails.hasPastSigningCertificates()
+ || p2SigningDetails.hasPastSigningCertificates()) {
+ Signature[] p1Signatures = p1SigningDetails.hasPastSigningCertificates()
+ ? new Signature[]{p1SigningDetails.pastSigningCertificates[0]}
+ : p1SigningDetails.signatures;
+ Signature[] p2Signatures = p2SigningDetails.hasPastSigningCertificates()
+ ? new Signature[]{p2SigningDetails.pastSigningCertificates[0]}
+ : p2SigningDetails.signatures;
+ result = compareSignatures(p1Signatures, p2Signatures);
+ }
+ return result;
}
@Override
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 9a297d601a6b..a83fa32ec9a9 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -61,6 +61,7 @@ import android.text.TextUtils;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;
@@ -137,6 +138,9 @@ public class StagingManager {
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
+ if (stagedSession.isDestroyed()) {
+ continue;
+ }
result.add(stagedSession.generateInfoForCaller(false /*icon*/, callingUid));
}
}
@@ -202,7 +206,7 @@ public class StagingManager {
final IntArray childSessionIds = new IntArray();
if (session.isMultiPackage()) {
for (int id : session.getChildSessionIds()) {
- if (isApexSession(mStagedSessions.get(id))) {
+ if (isApexSession(getStagedSession(id))) {
childSessionIds.add(id);
}
}
@@ -797,6 +801,8 @@ public class StagingManager {
+ session.sessionId + " [" + errorMessage + "]");
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
+ mPreRebootVerificationHandler.onPreRebootVerificationComplete(
+ session.sessionId);
return;
}
mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
@@ -880,7 +886,8 @@ public class StagingManager {
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
- if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()) {
+ if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()
+ || stagedSession.isDestroyed()) {
continue;
}
if (stagedSession.isMultiPackage()) {
@@ -943,27 +950,68 @@ public class StagingManager {
}
}
- void abortCommittedSession(@NonNull PackageInstallerSession session) {
+ /**
+ * <p>Abort committed staged session
+ *
+ * <p>This method must be called while holding {@link PackageInstallerSession.mLock}.
+ *
+ * <p>The method returns {@code false} to indicate it is not safe to clean up the session from
+ * system yet. When it is safe, the method returns {@code true}.
+ *
+ * <p> When it is safe to clean up, {@link StagingManager} will call
+ * {@link PackageInstallerSession#abandon()} on the session again.
+ *
+ * @return {@code true} if it is safe to cleanup the session resources, otherwise {@code false}.
+ */
+ boolean abortCommittedSessionLocked(@NonNull PackageInstallerSession session) {
+ int sessionId = session.sessionId;
if (session.isStagedSessionApplied()) {
- Slog.w(TAG, "Cannot abort applied session : " + session.sessionId);
- return;
+ Slog.w(TAG, "Cannot abort applied session : " + sessionId);
+ return false;
+ }
+ if (!session.isDestroyed()) {
+ throw new IllegalStateException("Committed session must be destroyed before aborting it"
+ + " from StagingManager");
+ }
+ if (getStagedSession(sessionId) == null) {
+ Slog.w(TAG, "Session " + sessionId + " has been abandoned already");
+ return false;
}
- abortSession(session);
- boolean hasApex = sessionContainsApex(session);
- if (hasApex) {
- ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
- if (apexSession == null || isApexSessionFinalized(apexSession)) {
- Slog.w(TAG,
- "Cannot abort session " + session.sessionId
- + " because it is not active or APEXD is not reachable");
- return;
- }
- try {
- mApexManager.abortStagedSession(session.sessionId);
- } catch (Exception ignore) {
+ // If pre-reboot verification is running, then return false. StagingManager will call
+ // abandon again when pre-reboot verification ends.
+ if (mPreRebootVerificationHandler.isVerificationRunning(sessionId)) {
+ Slog.w(TAG, "Session " + sessionId + " aborted before pre-reboot "
+ + "verification completed.");
+ return false;
+ }
+
+ // A session could be marked ready once its pre-reboot verification ends
+ if (session.isStagedSessionReady()) {
+ if (sessionContainsApex(session)) {
+ try {
+ ApexSessionInfo apexSession =
+ mApexManager.getStagedSessionInfo(session.sessionId);
+ if (apexSession == null || isApexSessionFinalized(apexSession)) {
+ Slog.w(TAG,
+ "Cannot abort session " + session.sessionId
+ + " because it is not active.");
+ } else {
+ mApexManager.abortStagedSession(session.sessionId);
+ }
+ } catch (Exception e) {
+ // Failed to contact apexd service. The apex might still be staged. We can still
+ // safely cleanup the staged session since pre-reboot verification is complete.
+ // Also, cleaning up the stageDir prevents the apex from being activated.
+ Slog.w(TAG, "Could not contact apexd to abort staged session " + sessionId);
+ }
}
}
+
+ // Session was successfully aborted from apexd (if required) and pre-reboot verification
+ // is also complete. It is now safe to clean up the session from system.
+ abortSession(session);
+ return true;
}
private boolean isApexSessionFinalized(ApexSessionInfo session) {
@@ -1042,6 +1090,11 @@ public class StagingManager {
// Final states, nothing to do.
return;
}
+ if (session.isDestroyed()) {
+ // Device rebooted before abandoned session was cleaned up.
+ session.abandon();
+ return;
+ }
if (!session.isStagedSessionReady()) {
// The framework got restarted before the pre-reboot verification could complete,
// restart the verification.
@@ -1124,10 +1177,20 @@ public class StagingManager {
}
}
+ private PackageInstallerSession getStagedSession(int sessionId) {
+ PackageInstallerSession session;
+ synchronized (mStagedSessions) {
+ session = mStagedSessions.get(sessionId);
+ }
+ return session;
+ }
+
private final class PreRebootVerificationHandler extends Handler {
// Hold session ids before handler gets ready to do the verification.
private IntArray mPendingSessionIds;
private boolean mIsReady;
+ @GuardedBy("mVerificationRunning")
+ private final SparseBooleanArray mVerificationRunning = new SparseBooleanArray();
PreRebootVerificationHandler(Looper looper) {
super(looper);
@@ -1155,13 +1218,15 @@ public class StagingManager {
@Override
public void handleMessage(Message msg) {
final int sessionId = msg.arg1;
- final PackageInstallerSession session;
- synchronized (mStagedSessions) {
- session = mStagedSessions.get(sessionId);
- }
- // Maybe session was aborted before pre-reboot verification was complete
+ final PackageInstallerSession session = getStagedSession(sessionId);
if (session == null) {
- Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
+ Slog.wtf(TAG, "Session disappeared in the middle of pre-reboot verification: "
+ + sessionId);
+ return;
+ }
+ if (session.isDestroyed()) {
+ // No point in running verification on a destroyed session
+ onPreRebootVerificationComplete(sessionId);
return;
}
switch (msg.what) {
@@ -1200,9 +1265,40 @@ public class StagingManager {
mPendingSessionIds.add(sessionId);
return;
}
+
+ PackageInstallerSession session = getStagedSession(sessionId);
+ synchronized (mVerificationRunning) {
+ // Do not start verification on a session that has been abandoned
+ if (session == null || session.isDestroyed()) {
+ return;
+ }
+ Slog.d(TAG, "Starting preRebootVerification for session " + sessionId);
+ mVerificationRunning.put(sessionId, true);
+ }
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
}
+ // Things to do when pre-reboot verification completes for a particular sessionId
+ private void onPreRebootVerificationComplete(int sessionId) {
+ // Remove it from mVerificationRunning so that verification is considered complete
+ synchronized (mVerificationRunning) {
+ Slog.d(TAG, "Stopping preRebootVerification for session " + sessionId);
+ mVerificationRunning.delete(sessionId);
+ }
+ // Check if the session was destroyed while pre-reboot verification was running. If so,
+ // abandon it again.
+ PackageInstallerSession session = getStagedSession(sessionId);
+ if (session != null && session.isDestroyed()) {
+ session.abandon();
+ }
+ }
+
+ private boolean isVerificationRunning(int sessionId) {
+ synchronized (mVerificationRunning) {
+ return mVerificationRunning.get(sessionId);
+ }
+ }
+
private void notifyPreRebootVerification_Start_Complete(int sessionId) {
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
}
@@ -1221,8 +1317,6 @@ public class StagingManager {
* See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
*/
private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
- Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
-
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// If rollback is enabled for this session, we call through to the RollbackManager
// with the list of sessions it must enable rollback for. Note that
@@ -1269,6 +1363,7 @@ public class StagingManager {
}
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
+ onPreRebootVerificationComplete(session.sessionId);
return;
}
@@ -1301,6 +1396,7 @@ public class StagingManager {
// TODO(b/118865310): abort the session on apexd.
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
+ onPreRebootVerificationComplete(session.sessionId);
}
}
@@ -1323,9 +1419,18 @@ public class StagingManager {
Slog.e(TAG, "Failed to get hold of StorageManager", e);
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN,
"Failed to get hold of StorageManager");
+ onPreRebootVerificationComplete(session.sessionId);
return;
}
+ // Stop pre-reboot verification before marking session ready. From this point on, if we
+ // abandon the session then it will be cleaned up immediately. If session is abandoned
+ // after this point, then even if for some reason system tries to install the session
+ // or activate its apex, there won't be any files to work with as they will be cleaned
+ // up by the system as part of abandonment. If session is abandoned before this point,
+ // then the session is already destroyed and cannot be marked ready anymore.
+ onPreRebootVerificationComplete(session.sessionId);
+
// Proactively mark session as ready before calling apexd. Although this call order
// looks counter-intuitive, this is the easiest way to ensure that session won't end up
// in the inconsistent state:
@@ -1337,15 +1442,16 @@ public class StagingManager {
// only apex part of the train will be applied, leaving device in an inconsistent state.
Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
session.setStagedSessionReady();
- final boolean hasApex = sessionContainsApex(session);
- if (!hasApex) {
- // Session doesn't contain apex, nothing to do.
- return;
- }
- try {
- mApexManager.markStagedSessionReady(session.sessionId);
- } catch (PackageManagerException e) {
- session.setStagedSessionFailed(e.error, e.getMessage());
+ if (session.isStagedSessionReady()) {
+ final boolean hasApex = sessionContainsApex(session);
+ if (hasApex) {
+ try {
+ mApexManager.markStagedSessionReady(session.sessionId);
+ } catch (PackageManagerException e) {
+ session.setStagedSessionFailed(e.error, e.getMessage());
+ return;
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index eb79b6ec652a..d3cd1a90b0b6 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -37,6 +37,9 @@
},
{
"include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
+ },
+ {
+ "include-filter": "android.content.pm.cts.PackageManagerTest"
}
]
},
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e6af86e52035..16d96d9a5574 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4516,8 +4516,8 @@ public class UserManagerService extends IUserManager.Stub {
switch(cmd) {
case "list":
return runList(pw, shell);
- case "list-missing-system-packages":
- return runListMissingSystemPackages(pw, shell);
+ case "report-system-user-package-whitelist-problems":
+ return runReportPackageWhitelistProblems(pw, shell);
default:
return shell.handleDefaultCommands(cmd);
}
@@ -4584,17 +4584,22 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private int runListMissingSystemPackages(PrintWriter pw, Shell shell) {
+ private int runReportPackageWhitelistProblems(PrintWriter pw, Shell shell) {
boolean verbose = false;
- boolean force = false;
+ boolean criticalOnly = false;
+ int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
String opt;
while ((opt = shell.getNextOption()) != null) {
switch (opt) {
case "-v":
+ case "--verbose":
verbose = true;
break;
- case "--force":
- force = true;
+ case "--critical-only":
+ criticalOnly = true;
+ break;
+ case "--mode":
+ mode = Integer.parseInt(shell.getNextArgRequired());
break;
default:
pw.println("Invalid option: " + opt);
@@ -4602,8 +4607,12 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ Slog.d(LOG_TAG, "runReportPackageWhitelistProblems(): verbose=" + verbose
+ + ", criticalOnly=" + criticalOnly
+ + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
+
try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
- mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose);
+ mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose, criticalOnly);
}
return 0;
}
@@ -5176,13 +5185,18 @@ public class UserManagerService extends IUserManager.Stub {
final PrintWriter pw = getOutPrintWriter();
pw.println("User manager (user) commands:");
pw.println(" help");
- pw.println(" Print this help text.");
+ pw.println(" Prints this help text.");
pw.println("");
pw.println(" list [-v] [-all]");
pw.println(" Prints all users on the system.");
- pw.println(" list-missing-system-packages [-v] [--force]");
- pw.println(" Prints all system packages that were not explicitly configured to be "
- + "installed.");
+ pw.println(" report-system-user-package-whitelist-problems [-v | --verbose] "
+ + "[--critical-only] [--mode MODE]");
+ pw.println(" Reports all issues on user-type package whitelist XML files. Options:");
+ pw.println(" -v | --verbose : shows extra info, like number of issues");
+ pw.println(" --critical-only: show only critical issues, excluding warnings");
+ pw.println(" --mode MODE: shows what errors would be if device used mode MODE (where"
+ + " MODE is the whitelist mode integer as defined by "
+ + "config_userTypePackageWhitelistMode)");
}
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 0b6024a84f78..1fec8aa0a3ff 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -208,7 +208,6 @@ public class UserRestrictionsUtils {
Sets.newArraySet(
UserManager.DISALLOW_CONFIG_DATE_TIME,
UserManager.DISALLOW_CAMERA,
- UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_BLUETOOTH,
UserManager.DISALLOW_BLUETOOTH_SHARING,
UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index cd1087f5fcd7..9ec03e5e8f5c 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -27,7 +27,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.Pair;
+import android.util.DebugUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -41,6 +41,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -111,14 +112,20 @@ class UserSystemPackageInstaller {
* frameworks/base/core/res/res/values/config.xml
*/
static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10;
+
+ // NOTE: flags below are public so they can used by DebugUtils.flagsToString. And this class
+ // itself is package-protected, so it doesn't matter...
+ public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00;
+ public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01;
+ public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02;
+ public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04;
+ public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08;
+ public static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10;
static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+ // Used by Shell command only
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_NONE = -1000;
+
@IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE,
USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
@@ -266,58 +273,56 @@ class UserSystemPackageInstaller {
if (!isLogMode(mode) && !isEnforceMode(mode)) {
return;
}
- final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
- final int size = warnings.size();
- if (size == 0) {
- Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings");
- return;
+ Slog.v(TAG, "Checking that all system packages are whitelisted.");
+
+ // Check whether all whitelisted packages are indeed on the system.
+ final List<String> warnings = getPackagesWhitelistWarnings();
+ final int numberWarnings = warnings.size();
+ if (numberWarnings == 0) {
+ Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode)
+ + ") has no warnings");
+ } else {
+ Slog.w(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode)
+ + ") has " + numberWarnings + " warnings:");
+ for (int i = 0; i < numberWarnings; i++) {
+ Slog.w(TAG, warnings.get(i));
+ }
}
+ // Check whether all system packages are indeed whitelisted.
if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
- // Only shows whether all whitelisted packages are indeed on the system.
- for (int i = 0; i < size; i++) {
- final Pair<Boolean, String> pair = warnings.get(i);
- final boolean isSevere = pair.first;
- if (!isSevere) {
- final String msg = pair.second;
- Slog.w(TAG, msg);
- }
- }
return;
}
- Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings");
+ final List<String> errors = getPackagesWhitelistErrors(mode);
+ final int numberErrors = errors.size();
+
+ if (numberErrors == 0) {
+ Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode)
+ + ") has no errors");
+ return;
+ }
+ Slog.e(TAG, "checkWhitelistedSystemPackages(mode=" + modeToString(mode) + ") has "
+ + numberErrors + " errors:");
+
boolean doWtf = !isImplicitWhitelistMode(mode);
- for (int i = 0; i < size; i++) {
- final Pair<Boolean, String> pair = warnings.get(i);
- final boolean isSevere = pair.first;
- final String msg = pair.second;
- if (isSevere) {
- if (doWtf) {
- Slog.wtf(TAG, msg);
- } else {
- Slog.e(TAG, msg);
- }
+ for (int i = 0; i < numberWarnings; i++) {
+ final String msg = errors.get(i);
+ if (doWtf) {
+ Slog.wtf(TAG, msg);
} else {
- Slog.w(TAG, msg);
+ Slog.e(TAG, msg);
}
}
}
- // TODO: method below was created to refactor the one-time logging logic so it can be used on
- // dump / cmd as well. It could to be further refactored (for example, creating a new
- // structure for the warnings so it doesn't need a Pair).
/**
- * Gets warnings for system user whitelisting.
- *
- * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF,
- * {@code false} for WARN) and {@code Pair.second} the message.
+ * Gets packages that are listed in the whitelist XML but are not present on the system image.
*/
@NonNull
- private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings(
- @PackageWhitelistMode int mode) {
+ private List<String> getPackagesWhitelistWarnings() {
final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
- final List<Pair<Boolean, String>> warnings = new ArrayList<>();
+ final List<String> warnings = new ArrayList<>();
final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
// Check whether all whitelisted packages are indeed on the system.
@@ -326,25 +331,39 @@ class UserSystemPackageInstaller {
for (String pkgName : allWhitelistedPackages) {
final AndroidPackage pkg = pmInt.getPackage(pkgName);
if (pkg == null) {
- warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName)));
+ warnings.add(String.format(notPresentFmt, pkgName));
} else if (!pkg.isSystem()) {
- warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName)));
+ warnings.add(String.format(notSystemFmt, pkgName));
}
}
+ return warnings;
+ }
+
+ /**
+ * Gets packages that are not listed in the whitelist XMLs when they should be.
+ */
+ @NonNull
+ private List<String> getPackagesWhitelistErrors(@PackageWhitelistMode int mode) {
+ if ((!isEnforceMode(mode) || isImplicitWhitelistMode(mode)) && !isLogMode(mode)) {
+ return Collections.emptyList();
+ }
+
+ final List<String> errors = new ArrayList<>();
+ final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
+ final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
// Check whether all system packages are indeed whitelisted.
final String logMessageFmt = "System package %s is not whitelisted using "
+ "'install-in-user-type' in SystemConfig for any user types!";
- final boolean isSevere = isEnforceMode(mode);
pmInt.forEachPackage(pkg -> {
if (!pkg.isSystem()) return;
final String pkgName = pkg.getManifestPackageName();
if (!allWhitelistedPackages.contains(pkgName)) {
- warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName)));
+ errors.add(String.format(logMessageFmt, pkgName));
}
});
- return warnings;
+ return errors;
}
/** Whether to only install system packages in new users for which they are whitelisted. */
@@ -420,10 +439,28 @@ class UserSystemPackageInstaller {
if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
return runtimeMode;
}
+ return getDeviceDefaultWhitelistMode();
+ }
+
+ /** Gets the PackageWhitelistMode as defined by {@code config_userTypePackageWhitelistMode}. */
+ private @PackageWhitelistMode int getDeviceDefaultWhitelistMode() {
return Resources.getSystem()
.getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode);
}
+ static @NonNull String modeToString(@PackageWhitelistMode int mode) {
+ // Must handle some types separately because they're not bitwise flags
+ switch (mode) {
+ case USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT:
+ return "DEVICE_DEFAULT";
+ case USER_TYPE_PACKAGE_WHITELIST_MODE_NONE:
+ return "NONE";
+ default:
+ return DebugUtils.flagsToString(UserSystemPackageInstaller.class,
+ "USER_TYPE_PACKAGE_WHITELIST_MODE_", mode);
+ }
+ }
+
/**
* Gets the system packages names that should be installed on the given user.
* See {@link #getInstallablePackagesForUserType(String)}.
@@ -703,34 +740,44 @@ class UserSystemPackageInstaller {
pw.decreaseIndent(); pw.decreaseIndent();
pw.increaseIndent();
- dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true);
+ dumpPackageWhitelistProblems(pw, mode, /* verbose= */ true, /* criticalOnly= */ false);
pw.decreaseIndent();
}
- void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) {
- final int mode = getWhitelistMode();
- final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode));
- if (!show) return;
+ void dumpPackageWhitelistProblems(IndentingPrintWriter pw, @PackageWhitelistMode int mode,
+ boolean verbose, boolean criticalOnly) {
+ // Handle special cases first
+ if (mode == USER_TYPE_PACKAGE_WHITELIST_MODE_NONE) {
+ mode = getWhitelistMode();
+ } else if (mode == USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
+ mode = getDeviceDefaultWhitelistMode();
+ }
+ Slog.v(TAG, "dumpPackageWhitelistProblems(): using mode " + modeToString(mode));
+
+ final List<String> errors = getPackagesWhitelistErrors(mode);
+ showIssues(pw, verbose, errors, "errors");
- final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
- final int size = warnings.size();
+ if (criticalOnly) return;
+ final List<String> warnings = getPackagesWhitelistWarnings();
+ showIssues(pw, verbose, warnings, "warnings");
+ }
+
+ private static void showIssues(IndentingPrintWriter pw, boolean verbose, List<String> issues,
+ String issueType) {
+ final int size = issues.size();
if (size == 0) {
if (verbose) {
- pw.println("All system packages are accounted for");
+ pw.print("No "); pw.println(issueType);
}
return;
}
-
if (verbose) {
- pw.print(size); pw.println(" warnings for system user:");
+ pw.print(size); pw.print(' '); pw.println(issueType);
pw.increaseIndent();
}
for (int i = 0; i < size; i++) {
- final Pair<Boolean, String> pair = warnings.get(i);
- final String lvl = pair.first ? "WTF" : "WARN";
- final String msg = pair.second;
- pw.print(lvl); pw.print(": "); pw.println(msg);
+ pw.println(issues.get(i));
}
if (verbose) {
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f17890334b6d..a967f3d7c4f9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -149,6 +149,8 @@ import com.android.server.policy.SoftRestrictedPermissionPolicy;
import libcore.util.EmptyArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -417,6 +419,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
LocalServices.addService(PermissionManagerInternal.class, localService);
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.getSystemService(PermissionControllerManager.class).dump(fd, pw, args);
+ }
+
/**
* Creates and returns an initialized, internal service for use by other components.
* <p>
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2f84a99774f4..3bc151af3589 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1222,10 +1222,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case LONG_PRESS_POWER_NOTHING:
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
- mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
- "Power - Long Press - Global Actions");
- showGlobalActionsInternal();
+ if (!mPowerKeyHandled) {
+ mPowerKeyHandled = true;
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ "Power - Long Press - Global Actions");
+ showGlobalActionsInternal();
+ }
break;
case LONG_PRESS_POWER_SHUT_OFF:
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b0c702f55821..3b4c4235d8a4 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -30,6 +30,7 @@ import static android.util.MathUtils.abs;
import static android.util.MathUtils.constrain;
import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_TRUNCATE_TIMESTAMP;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -755,6 +756,13 @@ public class StatsPullAtomService extends SystemService {
stats.getValues(j, entry);
StatsEvent.Builder e = StatsEvent.newBuilder();
e.setAtomId(atomTag);
+ switch (atomTag) {
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
+ e.addBooleanAnnotation(ANNOTATION_ID_TRUNCATE_TIMESTAMP, true);
+ break;
+ default:
+ }
e.writeInt(entry.uid);
e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
if (withFgbg) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 289bf66e1add..4ba58bd259fc 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -665,12 +665,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
- long operationId) {
+ long operationId, int sysUiSessionId) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showAuthenticationDialog(bundle, receiver, biometricModality,
- requireConfirmation, userId, opPackageName, operationId);
+ requireConfirmation, userId, opPackageName, operationId, sysUiSessionId);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f05217c0b47a..ad806c2bcae7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1160,8 +1160,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
computeConfigurationAfterMultiWindowModeChange();
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- true /* ignoreVisibility */);
+ // If the activity is in stopping or stopped state, for instance, it's in the
+ // split screen task and not the top one, the last configuration it should keep
+ // is the one before multi-window mode change.
+ final ActivityState state = getState();
+ if (state != STOPPED && state != STOPPING) {
+ ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
+ true /* ignoreVisibility */);
+ }
}
}
}
@@ -1281,12 +1287,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
if (stack != null && stack.topRunningActivity() == this) {
- // carry over the PictureInPictureParams to the parent stack without calling
- // TaskOrganizerController#dispatchTaskInfoChanged.
- // this is to ensure the stack holding up-to-dated pinned stack information
- // when activity is re-parented to enter pip mode, see also
- // RootWindowContainer#moveActivityToPinnedStack
- stack.mPictureInPictureParams.copyOnlySet(pictureInPictureArgs);
// make ensure the TaskOrganizer still works after re-parenting
if (firstWindowDrawn) {
stack.setHasBeenVisible(true);
@@ -7769,6 +7769,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void setPictureInPictureParams(PictureInPictureParams p) {
pictureInPictureArgs.copyOnlySet(p);
- getTask().getRootTask().setPictureInPictureParams(p);
+ getTask().getRootTask().onPictureInPictureParamsChanged();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index db5e97250cd2..1d7255578759 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -242,12 +242,6 @@ class ActivityStack extends Task {
*/
boolean mInResumeTopActivity = false;
- private boolean mUpdateBoundsDeferred;
- private boolean mUpdateBoundsDeferredCalled;
- private boolean mUpdateDisplayedBoundsDeferredCalled;
- private final Rect mDeferredBounds = new Rect();
- private final Rect mDeferredDisplayedBounds = new Rect();
-
int mCurrentUser;
/** For comparison with DisplayContent bounds. */
@@ -708,8 +702,10 @@ class ActivityStack extends Task {
// Need to make sure windowing mode is supported. If we in the process of creating the stack
// no need to resolve the windowing mode again as it is already resolved to the right mode.
if (!creating) {
- windowingMode = taskDisplayArea.validateWindowingMode(windowingMode,
- null /* ActivityRecord */, topTask, getActivityType());
+ if (!taskDisplayArea.isValidWindowingMode(windowingMode, null /* ActivityRecord */,
+ topTask, getActivityType())) {
+ windowingMode = WINDOWING_MODE_UNDEFINED;
+ }
}
if (taskDisplayArea.getRootSplitScreenPrimaryTask() == this
&& windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
@@ -846,58 +842,6 @@ class ActivityStack extends Task {
return getDisplayContent();
}
- /**
- * Defers updating the bounds of the stack. If the stack was resized/repositioned while
- * deferring, the bounds will update in {@link #continueUpdateBounds()}.
- */
- void deferUpdateBounds() {
- if (!mUpdateBoundsDeferred) {
- mUpdateBoundsDeferred = true;
- mUpdateBoundsDeferredCalled = false;
- }
- }
-
- /**
- * Continues updating bounds after updates have been deferred. If there was a resize attempt
- * between {@link #deferUpdateBounds()} and {@link #continueUpdateBounds()}, the stack will
- * be resized to that bounds.
- */
- void continueUpdateBounds() {
- if (mUpdateBoundsDeferred) {
- mUpdateBoundsDeferred = false;
- if (mUpdateBoundsDeferredCalled) {
- setTaskBounds(mDeferredBounds);
- setBounds(mDeferredBounds);
- }
- }
- }
-
- private boolean updateBoundsAllowed(Rect bounds) {
- if (!mUpdateBoundsDeferred) {
- return true;
- }
- if (bounds != null) {
- mDeferredBounds.set(bounds);
- } else {
- mDeferredBounds.setEmpty();
- }
- mUpdateBoundsDeferredCalled = true;
- return false;
- }
-
- private boolean updateDisplayedBoundsAllowed(Rect bounds) {
- if (!mUpdateBoundsDeferred) {
- return true;
- }
- if (bounds != null) {
- mDeferredDisplayedBounds.set(bounds);
- } else {
- mDeferredDisplayedBounds.setEmpty();
- }
- mUpdateDisplayedBoundsDeferredCalled = true;
- return false;
- }
-
/** @return true if the stack can only contain one task */
boolean isSingleTaskInstance() {
final DisplayContent display = getDisplay();
@@ -2687,10 +2631,6 @@ class ActivityStack extends Task {
// TODO: Can only be called from special methods in ActivityStackSupervisor.
// Need to consolidate those calls points into this resize method so anyone can call directly.
void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) {
- if (!updateBoundsAllowed(displayedBounds)) {
- return;
- }
-
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + getRootTaskId());
mAtmService.deferWindowLayout();
try {
@@ -2730,10 +2670,6 @@ class ActivityStack extends Task {
* basically resizes both stack and task bounds to the same bounds.
*/
private void setTaskBounds(Rect bounds) {
- if (!updateBoundsAllowed(bounds)) {
- return;
- }
-
final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds,
PooledLambda.__(Task.class), bounds);
forAllLeafTasks(c, true /* traverseTopToBottom */);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index ed2153960754..33715207c6ce 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -69,7 +69,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_STACK_MSG;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_ONLY;
import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
import static com.android.server.wm.RootWindowContainer.TAG_STATES;
@@ -125,7 +124,6 @@ import android.os.UserManager;
import android.os.WorkSource;
import android.provider.MediaStore;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
@@ -364,11 +362,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
*/
boolean mAppVisibilitiesChangedSinceLastPause;
- /**
- * Set of tasks that are in resizing mode during an app transition to fill the "void".
- */
- private final ArraySet<Integer> mResizingTasksDuringAnimation = new ArraySet<>();
-
private KeyguardController mKeyguardController;
private PowerManager mPowerManager;
@@ -1415,29 +1408,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
return mLaunchParamsController;
}
- private void deferUpdateRecentsHomeStackBounds() {
- mRootWindowContainer.deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
- mRootWindowContainer.deferUpdateBounds(ACTIVITY_TYPE_HOME);
- }
-
- private void continueUpdateRecentsHomeStackBounds() {
- mRootWindowContainer.continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
- mRootWindowContainer.continueUpdateBounds(ACTIVITY_TYPE_HOME);
- }
-
- void notifyAppTransitionDone() {
- continueUpdateRecentsHomeStackBounds();
- for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
- final int taskId = mResizingTasksDuringAnimation.valueAt(i);
- final Task task =
- mRootWindowContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_ONLY);
- if (task != null) {
- task.setTaskDockedResizing(false);
- }
- }
- mResizingTasksDuringAnimation.clear();
- }
-
void setSplitScreenResizing(boolean resizing) {
if (resizing == mDockedStackResizing) {
return;
@@ -1470,6 +1440,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
mService.deferWindowLayout();
try {
stack.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ stack.setBounds(null);
if (toDisplay.getDisplayId() != stack.getDisplayId()) {
stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */);
} else {
@@ -2471,16 +2442,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
}
- /**
- * Puts a task into resizing mode during the next app transition.
- *
- * @param task The task to put into resizing mode
- */
- void setResizingDuringAnimation(Task task) {
- mResizingTasksDuringAnimation.add(task.mTaskId);
- task.setTaskDockedResizing(true);
- }
-
int startActivityFromRecents(int callingPid, int callingUid, int taskId,
SafeActivityOptions options) {
Task task = null;
@@ -2508,27 +2469,12 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
mService.deferWindowLayout();
try {
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- // Defer updating the stack in which recents is until the app transition is done, to
- // not run into issues where we still need to draw the task in recents but the
- // docked stack is already created.
- deferUpdateRecentsHomeStackBounds();
- // TODO(task-hierarchy): Remove when tiles are in hierarchy.
- // Unset launching windowing mode to prevent creating split-screen-primary stack
- // in RWC#anyTaskForId() below.
- activityOptions.setLaunchWindowingMode(WINDOWING_MODE_UNDEFINED);
- }
-
task = mRootWindowContainer.anyTaskForId(taskId,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
if (task == null) {
- continueUpdateRecentsHomeStackBounds();
mWindowManager.executeAppTransition();
throw new IllegalArgumentException(
"startActivityFromRecents: Task " + taskId + " not found.");
- } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- && task.getWindowingMode() != windowingMode) {
- mService.moveTaskToSplitScreenPrimaryTask(task, true /* toTop */);
}
if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
@@ -2577,12 +2523,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
false /* validateIncomingUser */, null /* originatingPendingIntent */,
false /* allowBackgroundActivityStart */);
} finally {
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && task != null) {
- // If we are launching the task in the docked stack, put it into resizing mode so
- // the window renders full-screen with the background filling the void. Also only
- // call this at the end to make sure that tasks exists on the window manager side.
- setResizingDuringAnimation(task);
- }
mService.continueWindowLayout();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 4181f4be30f7..d5df9068e81d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -152,17 +152,6 @@ public abstract class ActivityTaskManagerInternal {
IVoiceInteractor mInteractor);
/**
- * Callback for window manager to let activity manager know that the app transition was
- * cancelled.
- */
- public abstract void notifyAppTransitionCancelled();
-
- /**
- * Callback for window manager to let activity manager know that the app transition is finished.
- */
- public abstract void notifyAppTransitionFinished();
-
- /**
* Returns the top activity from each of the currently visible stacks. The first entry will be
* the focused activity.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6a8d5d905a00..36caeecbfec2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6079,20 +6079,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void notifyAppTransitionFinished() {
- synchronized (mGlobalLock) {
- mStackSupervisor.notifyAppTransitionDone();
- }
- }
-
- @Override
- public void notifyAppTransitionCancelled() {
- synchronized (mGlobalLock) {
- mStackSupervisor.notifyAppTransitionDone();
- }
- }
-
- @Override
public List<IBinder> getTopVisibleActivities() {
synchronized (mGlobalLock) {
return mRootWindowContainer.getTopVisibleActivities();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 8b9e9fe132b7..c93b7354999b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2170,7 +2170,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final boolean singleActivity = task.getChildCount() == 1;
final ActivityStack stack;
if (singleActivity) {
- stack = r.getRootTask();
+ stack = (ActivityStack) task;
} else {
// In the case of multiple activities, we will create a new task for it and then
// move the PIP activity into the task.
@@ -2183,6 +2183,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// up-to-dated pinned stack information on this newly created stack.
r.reparent(stack, MAX_VALUE, reason);
}
+ if (stack.getParent() != taskDisplayArea) {
+ // stack is nested, but pinned tasks need to be direct children of their
+ // display area, so reparent.
+ stack.reparent(taskDisplayArea, true /* onTop */);
+ }
stack.setWindowingMode(WINDOWING_MODE_PINNED);
// Reset the state that indicates it can enter PiP while pausing after we've moved it
@@ -2504,20 +2509,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return list;
}
- void deferUpdateBounds(int activityType) {
- final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
- if (stack != null) {
- stack.deferUpdateBounds();
- }
- }
-
- void continueUpdateBounds(int activityType) {
- final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
- if (stack != null) {
- stack.continueUpdateBounds();
- }
- }
-
@Override
public void onDisplayAdded(int displayId) {
if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 44a8daaba1b1..85a31610964e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -82,7 +82,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -110,7 +109,6 @@ import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
-import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -491,12 +489,6 @@ class Task extends WindowContainer<WindowContainer> {
boolean mTaskAppearedSent;
/**
- * Last Picture-in-Picture params applicable to the task. Updated when the app
- * enters Picture-in-Picture or when setPictureInPictureParams is called.
- */
- PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
-
- /**
* This task was created by the task organizer which has the following implementations.
* <ul>
* <lis>The task won't be removed when it is empty. Removal has to be an explicit request
@@ -2013,7 +2005,7 @@ class Task extends WindowContainer<WindowContainer> {
}
void updateSurfaceSize(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null || mCreatedByOrganizer) {
+ if (mSurfaceControl == null || isOrganized()) {
return;
}
@@ -3059,15 +3051,6 @@ class Task extends WindowContainer<WindowContainer> {
return mDragResizeMode;
}
- /**
- * Puts this task into docked drag resizing mode. See {@link DragResizeMode}.
- *
- * @param resizing Whether to put the task into drag resize mode.
- */
- public void setTaskDockedResizing(boolean resizing) {
- setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
- }
-
void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
if (displayContent == null) {
return;
@@ -3602,10 +3585,11 @@ class Task extends WindowContainer<WindowContainer> {
info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
info.topActivityType = top.getActivityType();
- if (mPictureInPictureParams.empty()) {
+ ActivityRecord rootActivity = top.getRootActivity();
+ if (rootActivity == null || rootActivity.pictureInPictureArgs.empty()) {
info.pictureInPictureParams = null;
} else {
- info.pictureInPictureParams = mPictureInPictureParams;
+ info.pictureInPictureParams = rootActivity.pictureInPictureArgs;
}
info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info
@@ -4521,8 +4505,7 @@ class Task extends WindowContainer<WindowContainer> {
updateShadowsRadius(hasFocus, getPendingTransaction());
}
- void setPictureInPictureParams(PictureInPictureParams p) {
- mPictureInPictureParams.copyOnlySet(p);
+ void onPictureInPictureParamsChanged() {
if (isOrganized()) {
mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, true /* force */);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 0a1ee2b79711..37a4c1f6849b 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -21,7 +21,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -1333,16 +1332,16 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> {
}
/**
- * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+ * Check if the requested windowing-mode is appropriate for the specified task and/or activity
* on this display.
*
* @param windowingMode The windowing-mode to validate.
* @param r The {@link ActivityRecord} to check against.
* @param task The {@link Task} to check against.
* @param activityType An activity type.
- * @return The provided windowingMode or the closest valid mode which is appropriate.
+ * @return {@code true} if windowingMode is valid, {@code false} otherwise.
*/
- int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+ boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
int activityType) {
// Make sure the windowing mode we are trying to use makes sense for what is supported.
boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
@@ -1362,24 +1361,35 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> {
}
}
+ return windowingMode != WINDOWING_MODE_UNDEFINED
+ && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ supportsFreeform, supportsPip, activityType);
+ }
+
+ /**
+ * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+ * on this display.
+ *
+ * @param windowingMode The windowing-mode to validate.
+ * @param r The {@link ActivityRecord} to check against.
+ * @param task The {@link Task} to check against.
+ * @param activityType An activity type.
+ * @return The provided windowingMode or the closest valid mode which is appropriate.
+ */
+ int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+ int activityType) {
final boolean inSplitScreenMode = isSplitScreenModeActivated();
- if (!inSplitScreenMode
- && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Switch to the display's windowing mode if we are not in split-screen mode and we are
// trying to launch in split-screen secondary.
windowingMode = WINDOWING_MODE_UNDEFINED;
- } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_UNDEFINED)
- && supportsSplitScreen) {
+ } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
-
- if (windowingMode != WINDOWING_MODE_UNDEFINED
- && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
- supportsFreeform, supportsPip, activityType)) {
- return windowingMode;
+ if (!isValidWindowingMode(windowingMode, r, task, activityType)) {
+ return WINDOWING_MODE_UNDEFINED;
}
- return WINDOWING_MODE_UNDEFINED;
+ return windowingMode;
}
boolean isTopStack(ActivityStack stack) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 73126c8b6b6a..ae0c9b15689a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1064,12 +1064,10 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void onAppTransitionCancelledLocked(int transit) {
- mAtmInternal.notifyAppTransitionCancelled();
}
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
- mAtmInternal.notifyAppTransitionFinished();
final ActivityRecord atoken = mRoot.getActivityRecord(token);
if (atoken == null) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0e83beed6b90..c570cf1d949f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -846,6 +846,23 @@ class WindowStateAnimator {
}
}
+ private boolean shouldConsumeMainWindowSizeTransaction() {
+ // We only consume the transaction when the client is calling relayout
+ // because this is the only time we know the frameNumber will be valid
+ // due to the client renderer being paused. Put otherwise, only when
+ // mInRelayout is true can we guarantee the next frame will contain
+ // the most recent configuration.
+ if (!mWin.mInRelayout) return false;
+ // Since we can only do this for one window, we focus on the main application window
+ if (mAttrType != TYPE_BASE_APPLICATION) return false;
+ final Task task = mWin.getTask();
+ if (task == null) return false;
+ if (task.getMainWindowSizeChangeTransaction() == null) return false;
+ // Likewise we only focus on the task root, since we can only use one window
+ if (!mWin.mActivityRecord.isRootOfTask()) return false;
+ return true;
+ }
+
void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
if (mSurfaceController == null) {
return;
@@ -886,8 +903,9 @@ class WindowStateAnimator {
clipRect = mTmpClipRect;
}
- if (w.mInRelayout && (mAttrType == TYPE_BASE_APPLICATION) && (task != null)
- && (task.getMainWindowSizeChangeTransaction() != null)) {
+ if (shouldConsumeMainWindowSizeTransaction()) {
+ task.getSurfaceControl().deferTransactionUntil(mWin.getClientViewRootSurface(),
+ mWin.getFrameNumber());
mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(),
mWin.getFrameNumber());
SurfaceControl.mergeToGlobalTransaction(task.getMainWindowSizeChangeTransaction());
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 9bc5d34c11af..20139451e4b9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -237,27 +237,28 @@ public:
/* --- InputDispatcherPolicyInterface implementation --- */
virtual void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
- uint32_t policyFlags);
+ uint32_t policyFlags) override;
virtual void notifyConfigurationChanged(nsecs_t when);
- virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
- const sp<IBinder>& token,
- const std::string& reason);
+ virtual nsecs_t notifyAnr(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<IBinder>& token, const std::string& reason) override;
virtual void notifyInputChannelBroken(const sp<IBinder>& token);
- virtual void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken);
- virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags);
- virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig);
- virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
+ virtual void notifyFocusChanged(const sp<IBinder>& oldToken,
+ const sp<IBinder>& newToken) override;
+ virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override;
+ virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override;
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
+ uint32_t& policyFlags) override;
virtual void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
- uint32_t& policyFlags);
- virtual nsecs_t interceptKeyBeforeDispatching(
- const sp<IBinder>& token,
- const KeyEvent* keyEvent, uint32_t policyFlags);
- virtual bool dispatchUnhandledKey(const sp<IBinder>& token,
- const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent);
- virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
- virtual bool checkInjectEventsPermissionNonReentrant(
- int32_t injectorPid, int32_t injectorUid);
- virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken);
+ uint32_t& policyFlags) override;
+ virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token,
+ const KeyEvent* keyEvent,
+ uint32_t policyFlags) override;
+ virtual bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
+ uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) override;
+ virtual bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid,
+ int32_t injectorUid) override;
+ virtual void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -692,9 +693,8 @@ static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env,
return handle->getInputApplicationHandleObjLocalRef(env);
}
-
-nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
- const sp<IBinder>& token, const std::string& reason) {
+nsecs_t NativeInputManager::notifyAnr(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<IBinder>& token, const std::string& reason) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifyANR");
#endif
@@ -1453,9 +1453,13 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
return INPUT_EVENT_INJECTION_FAILED;
}
- return (jint) im->getInputManager()->getDispatcher()->injectInputEvent(
- & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis,
- uint32_t(policyFlags));
+ const int32_t result =
+ im->getInputManager()->getDispatcher()->injectInputEvent(&keyEvent, injectorPid,
+ injectorUid, syncMode,
+ std::chrono::milliseconds(
+ timeoutMillis),
+ uint32_t(policyFlags));
+ return static_cast<jint>(result);
} else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
if (!motionEvent) {
@@ -1463,9 +1467,13 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
return INPUT_EVENT_INJECTION_FAILED;
}
- return (jint) im->getInputManager()->getDispatcher()->injectInputEvent(
- motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis,
- uint32_t(policyFlags));
+ const int32_t result =
+ (jint)im->getInputManager()
+ ->getDispatcher()
+ ->injectInputEvent(motionEvent, injectorPid, injectorUid, syncMode,
+ std::chrono::milliseconds(timeoutMillis),
+ uint32_t(policyFlags));
+ return static_cast<jint>(result);
} else {
jniThrowRuntimeException(env, "Invalid input event type.");
return INPUT_EVENT_INJECTION_FAILED;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3323fa4b53e3..966694ad346c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4567,9 +4567,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (isProfileOwner(adminReceiver, userHandle)) {
if (isProfileOwnerOfOrganizationOwnedDevice(userHandle)) {
+ UserHandle parentUserHandle = UserHandle.of(getProfileParentId(userHandle));
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- false,
- UserHandle.of(getProfileParentId(userHandle)));
+ false, parentUserHandle);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER,
+ false, parentUserHandle);
}
final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver,
userHandle, /* parent */ false);
@@ -7213,6 +7215,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mUserManager.setUserRestriction(
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false,
UserHandle.SYSTEM);
+ mUserManager.setUserRestriction(
+ UserManager.DISALLOW_ADD_USER, false, UserHandle.SYSTEM);
// Device-wide policies set by the profile owner need to be cleaned up here.
mLockPatternUtils.setDeviceOwnerInfo(null);
@@ -13825,6 +13829,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
parentUser);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true,
+ parentUser);
});
// markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0fc333f4b38c..fa3f33067e8e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1891,6 +1891,10 @@ public final class SystemServer {
|| mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
t.traceBegin("StartTvInputManager");
mSystemServiceManager.startService(TvInputManagerService.class);
+ t.traceEnd();
+ }
+
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TUNER)) {
t.traceBegin("StartTunerResourceManager");
mSystemServiceManager.startService(TunerResourceManagerService.class);
t.traceEnd();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 2ce70b6f0889..b6cf2785d771 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -129,8 +129,11 @@ public class AccessibilityServiceConnectionTest {
public void bind_requestsContextToBindService() {
mConnection.bindLocked();
verify(mMockContext).bindServiceAsUser(any(Intent.class), eq(mConnection),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
- | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS), any(UserHandle.class));
+ eq(Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
+ | Context.BIND_INCLUDE_CAPABILITIES),
+ any(UserHandle.class));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index d2925263125d..285caf34ae67 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -186,7 +186,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
}
@Test
@@ -269,7 +270,8 @@ public class BiometricServiceTest {
eq(false) /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
}
@Test
@@ -407,7 +409,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
// Hardware authenticated
final byte[] HAT = generateRandomHAT();
@@ -462,7 +465,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
}
@Test
@@ -610,7 +614,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
anyString(),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
}
@Test
@@ -711,7 +716,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
}
@Test
@@ -1207,7 +1213,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
// Requesting strong and credential, when credential is setup
resetReceiver();
@@ -1227,7 +1234,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
// Un-downgrading the authenticator allows successful strong auth
for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
@@ -1250,7 +1258,8 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */);
+ anyLong() /* sessionId */,
+ anyInt() /* sysUiSessionId */);
}
@Test(expected = IllegalStateException.class)
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 6b36bc591b78..ed40fe756ea1 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1998,7 +1998,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
Sets.newSet(
UserManager.DISALLOW_CONFIG_DATE_TIME,
- UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_BLUETOOTH_SHARING,
UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
@@ -4005,6 +4004,12 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// Any caller should be able to call this method.
assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+ verify(getServices().userManager).setUserRestriction(
+ eq(UserManager.DISALLOW_ADD_USER),
+ eq(true),
+ eq(UserHandle.of(UserHandle.USER_SYSTEM)));
+
assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
// A random caller from another user should also be able to get the right result.
@@ -4012,6 +4017,35 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
}
+ public void testMarkOrganizationOwnedDevice_baseRestrictionsAdded() throws Exception {
+ addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1);
+
+ configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+
+ // Base restriction DISALLOW_REMOVE_MANAGED_PROFILE added
+ verify(getServices().userManager).setUserRestriction(
+ eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+ eq(true),
+ eq(UserHandle.of(UserHandle.USER_SYSTEM)));
+
+ // Base restriction DISALLOW_ADD_USER added
+ verify(getServices().userManager).setUserRestriction(
+ eq(UserManager.DISALLOW_ADD_USER),
+ eq(true),
+ eq(UserHandle.of(UserHandle.USER_SYSTEM)));
+
+ // Assert base restrictions cannot be added or removed by admin
+ assertExpectException(SecurityException.class, null, () ->
+ parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE));
+ assertExpectException(SecurityException.class, null, () ->
+ parentDpm.clearUserRestriction(admin1,
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE));
+ assertExpectException(SecurityException.class, null, () ->
+ parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER));
+ assertExpectException(SecurityException.class, null, () ->
+ parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER));
+ }
+
public void testSetTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index d4edab44bae3..63d797e9b95c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -178,6 +178,7 @@ public class PackageInstallerSessionTest {
/* files */ null,
/* prepared */ true,
/* committed */ true,
+ /* destroyed */ staged ? true : false,
/* sealed */ false, // Setting to true would trigger some PM logic.
/* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0],
/* parentSessionId */ parentSessionId,
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 39062f017a73..6718db768fdb 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -366,29 +366,87 @@ public class AppStandbyControllerTests {
mInjector.mElapsedRealtime, false));
}
+ private static class TestParoleListener extends AppIdleStateChangeListener {
+ private boolean mIsParoleOn = false;
+ private CountDownLatch mLatch;
+ private boolean mIsExpecting = false;
+ private boolean mExpectedParoleState;
+
+ boolean getParoleState() {
+ synchronized (this) {
+ return mIsParoleOn;
+ }
+ }
+
+ void rearmLatch(boolean expectedParoleState) {
+ synchronized (this) {
+ mLatch = new CountDownLatch(1);
+ mIsExpecting = true;
+ mExpectedParoleState = expectedParoleState;
+ }
+ }
+
+ void awaitOnLatch(long time) throws Exception {
+ mLatch.await(time, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
+ int bucket, int reason) {
+ }
+
+ @Override
+ public void onParoleStateChanged(boolean isParoleOn) {
+ synchronized (this) {
+ // Only record information if it is being looked for
+ if (mLatch != null && mLatch.getCount() > 0) {
+ mIsParoleOn = isParoleOn;
+ if (mIsExpecting && isParoleOn == mExpectedParoleState) {
+ mLatch.countDown();
+ }
+ }
+ }
+ }
+ }
+
@Test
public void testIsAppIdle_Charging() throws Exception {
+ TestParoleListener paroleListener = new TestParoleListener();
+ mController.addListener(paroleListener);
+
setChargingState(mController, false);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ assertFalse(mController.isInParole());
+ paroleListener.rearmLatch(true);
setChargingState(mController, true);
+ paroleListener.awaitOnLatch(2000);
+ assertTrue(paroleListener.getParoleState());
assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ assertTrue(mController.isInParole());
+ paroleListener.rearmLatch(false);
setChargingState(mController, false);
+ paroleListener.awaitOnLatch(2000);
+ assertFalse(paroleListener.getParoleState());
assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ assertFalse(mController.isInParole());
}
@Test
public void testIsAppIdle_Enabled() throws Exception {
setChargingState(mController, false);
+ TestParoleListener paroleListener = new TestParoleListener();
+ mController.addListener(paroleListener);
+
setAppIdleEnabled(mController, true);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
REASON_MAIN_FORCED_BY_SYSTEM);
@@ -396,11 +454,17 @@ public class AppStandbyControllerTests {
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ paroleListener.rearmLatch(false);
setAppIdleEnabled(mController, false);
+ paroleListener.awaitOnLatch(2000);
+ assertTrue(paroleListener.mIsParoleOn);
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ paroleListener.rearmLatch(true);
setAppIdleEnabled(mController, true);
+ paroleListener.awaitOnLatch(2000);
+ assertFalse(paroleListener.getParoleState());
assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index 3c2d55058c3e..182bf949af1f 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -41,6 +41,7 @@ public class UiServiceTestCase {
protected static final String PKG_N_MR1 = "com.example.n_mr1";
protected static final String PKG_O = "com.example.o";
protected static final String PKG_P = "com.example.p";
+ protected static final String PKG_R = "com.example.r";
@Rule
public final TestableContext mContext =
@@ -69,6 +70,8 @@ public class UiServiceTestCase {
return Build.VERSION_CODES.O;
case PKG_P:
return Build.VERSION_CODES.P;
+ case PKG_R:
+ return Build.VERSION_CODES.R;
default:
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index babe80e4b612..289933e5ecb2 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -250,6 +250,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
+ private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
+
@Mock
private NotificationListeners mListeners;
@Mock private NotificationAssistants mAssistants;
@@ -471,6 +473,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mShortcutHelper.setLauncherApps(mLauncherApps);
mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal);
+ // Pretend the shortcut exists
+ List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+ ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(PKG);
+ when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
+ when(info.isLongLived()).thenReturn(true);
+ when(info.isEnabled()).thenReturn(true);
+ shortcutInfos.add(info);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+ when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any())).thenReturn(true);
+
// Set the testable bubble extractor
RankingHelper rankingHelper = mService.getRankingHelper();
BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class);
@@ -704,6 +719,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
)
.setActions(replyAction)
.setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setShortcutId(VALID_CONVO_SHORTCUT_ID)
.setGroupSummary(isSummary);
if (groupKey != null) {
nb.setGroup(groupKey);
@@ -6100,7 +6116,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testNotificationBubbles_flagRemoved_whenShortcutRemoved()
throws RemoteException {
- final String shortcutId = "someshortcutId";
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
@@ -6111,27 +6126,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Messaging notification with shortcut info
Notification.BubbleMetadata metadata =
- new Notification.BubbleMetadata.Builder(shortcutId).build();
+ new Notification.BubbleMetadata.Builder(VALID_CONVO_SHORTCUT_ID).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
null /* groupKey */, false /* isSummary */);
- nb.setShortcutId(shortcutId);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
"tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- // Pretend the shortcut exists
- List<ShortcutInfo> shortcutInfos = new ArrayList<>();
- ShortcutInfo info = mock(ShortcutInfo.class);
- when(info.getPackage()).thenReturn(PKG);
- when(info.getId()).thenReturn(shortcutId);
- when(info.getUserId()).thenReturn(USER_SYSTEM);
- when(info.isLongLived()).thenReturn(true);
- when(info.isEnabled()).thenReturn(true);
- shortcutInfos.add(info);
- when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
- when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
- anyString(), anyInt(), any())).thenReturn(true);
+
// Test: Send the bubble notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
@@ -6149,7 +6153,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Make sure the shortcut is cached.
verify(mShortcutServiceInternal).cacheShortcuts(
- anyInt(), any(), eq(PKG), eq(Collections.singletonList(shortcutId)),
+ anyInt(), any(), eq(PKG), eq(Collections.singletonList(VALID_CONVO_SHORTCUT_ID)),
eq(USER_SYSTEM));
// Test: Remove the shortcut
@@ -6613,6 +6617,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
convo2.setNotificationChannel(channel2);
convos.add(convo2);
when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
List<ConversationChannelWrapper> conversations =
mBinderService.getConversationsForPackage(PKG_P, mUid).getList();
@@ -6640,6 +6645,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationRecord nr =
generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testRecordMessages_invalidMsg");
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
@@ -6660,17 +6666,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
"testRecordMessages_validMsg", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- // Pretend the shortcut exists
- List<ShortcutInfo> shortcutInfos = new ArrayList<>();
- ShortcutInfo info = mock(ShortcutInfo.class);
- when(info.getPackage()).thenReturn(PKG);
- when(info.getId()).thenReturn("id");
- when(info.getUserId()).thenReturn(USER_SYSTEM);
- when(info.isLongLived()).thenReturn(true);
- when(info.isEnabled()).thenReturn(true);
- shortcutInfos.add(info);
- when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
-
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 9f593ce42741..b03596a35c32 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -39,7 +39,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.Notification;
@@ -90,7 +89,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
@Mock private PackageManager mPm;
@Mock private ContentResolver mContentResolver;
- private final String pkg = PKG_N_MR1;
+ private final String mPkg = PKG_O;
private final int uid = 9583;
private final int id1 = 1;
private final String tag1 = "tag1";
@@ -198,10 +197,14 @@ public class NotificationRecordTest extends UiServiceTestCase {
}
Notification n = builder.build();
- return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
+ return new StatusBarNotification(mPkg, mPkg, id1, tag1, uid, uid, n, mUser, null, uid);
}
private StatusBarNotification getMessagingStyleNotification() {
+ return getMessagingStyleNotification(mPkg);
+ }
+
+ private StatusBarNotification getMessagingStyleNotification(String pkg) {
final Builder builder = new Builder(mMockContext)
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon);
@@ -658,7 +661,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
Bundle signals = new Bundle();
signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
- record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId()));
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
record.applyAdjustments();
@@ -687,7 +690,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
Bundle signals = new Bundle();
signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
- record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId()));
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
record.applyAdjustments();
assertEquals(USER_SENTIMENT_POSITIVE, record.getUserSentiment());
@@ -705,7 +708,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
Bundle signals = new Bundle();
signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
- record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId()));
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
record.applyAdjustments();
@@ -1134,6 +1137,15 @@ public class NotificationRecordTest extends UiServiceTestCase {
}
@Test
+ public void testIsConversation_noShortcut_targetsR() {
+ StatusBarNotification sbn = getMessagingStyleNotification(PKG_R);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ record.setShortcutInfo(null);
+
+ assertFalse(record.isConversation());
+ }
+
+ @Test
public void testIsConversation_channelDemoted() {
StatusBarNotification sbn = getMessagingStyleNotification();
channel.setDemoted(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 2ea58a028a0a..fdc5c7bf0ce1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -836,7 +836,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
spyOn(record);
doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
- record.getRootTask().setHasBeenVisible(true);
+ record.getTask().setHasBeenVisible(true);
return record;
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index b7779fd40990..4e75b7354baa 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -1166,6 +1166,7 @@ class UserUsageStatsService {
byte[] getBackupPayload(String key){
checkAndGetTimeLocked();
+ persistActiveStats();
return mDatabase.getBackupPayload(key);
}
diff --git a/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java
new file mode 100644
index 000000000000..6c6375586225
--- /dev/null
+++ b/telephony/common/com/android/internal/telephony/CellBroadcastUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Telephony;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * This class provides utility functions related to CellBroadcast.
+ */
+public class CellBroadcastUtils {
+ private static final String TAG = "CellBroadcastUtils";
+ private static final boolean VDBG = false;
+
+ /**
+ * Utility method to query the default CBR's package name.
+ */
+ public static String getDefaultCellBroadcastReceiverPackageName(Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ ResolveInfo resolveInfo = packageManager.resolveActivity(
+ new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION),
+ PackageManager.MATCH_SYSTEM_ONLY);
+ String packageName;
+
+ if (resolveInfo == null) {
+ Log.e(TAG, "getDefaultCellBroadcastReceiverPackageName: no package found");
+ return null;
+ }
+
+ packageName = resolveInfo.activityInfo.applicationInfo.packageName;
+
+ if (VDBG) {
+ Log.d(TAG, "getDefaultCellBroadcastReceiverPackageName: found package: " + packageName);
+ }
+
+ if (TextUtils.isEmpty(packageName) || packageManager.checkPermission(
+ android.Manifest.permission.READ_CELL_BROADCASTS, packageName)
+ == PackageManager.PERMISSION_DENIED) {
+ Log.e(TAG, "getDefaultCellBroadcastReceiverPackageName: returning null; "
+ + "permission check failed for : " + packageName);
+ return null;
+ }
+
+ return packageName;
+ }
+}