Merge "Rename to VsyncCallback & presentation time."
diff --git a/Android.bp b/Android.bp
index ee5db70..f805947 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,7 +110,7 @@
// AIDL sources from external directories
":android.hardware.graphics.common-V3-java-source",
- ":android.hardware.security.keymint-V1-java-source",
+ ":android.hardware.security.keymint-V2-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.tv.tuner-V1-java-source",
":android.security.apc-java-source",
@@ -191,7 +191,6 @@
"sax/java",
"telecomm/java",
- "apex/media/aidl/stable",
// TODO(b/147699819): remove this
"telephony/java",
],
@@ -289,6 +288,7 @@
// TODO: remove when moved to the below package
"frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
"packages/modules/Connectivity/framework/aidl-export",
+ "packages/modules/Media/apex/aidl/stable",
"hardware/interfaces/graphics/common/aidl",
],
},
@@ -538,6 +538,7 @@
"frameworks/native/libs/permission/aidl",
// TODO: remove when moved to the below package
"frameworks/base/packages/ConnectivityT/framework-t/aidl-export",
+ "packages/modules/Media/apex/aidl/stable",
"packages/modules/Connectivity/framework/aidl-export",
"hardware/interfaces/graphics/common/aidl",
],
@@ -575,11 +576,9 @@
stubs_defaults {
name: "module-classpath-stubs-defaults",
aidl: {
- local_include_dirs: [
- "apex/media/aidl/stable",
- ],
include_dirs: [
"packages/modules/Connectivity/framework/aidl-export",
+ "packages/modules/Media/apex/aidl/stable",
],
},
libs: [
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 996cdc9..ba31161 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -145,11 +145,9 @@
"api-versions-jars-dir",
],
aidl: {
- local_include_dirs: [
- "apex/media/aidl/stable",
- ],
include_dirs: [
"packages/modules/Connectivity/framework/aidl-export",
+ "packages/modules/Media/apex/aidl/stable",
],
},
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 8237383..e9ce87f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -3331,7 +3331,7 @@
mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
mMaintenanceStartTime = 0;
mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
- cancelLightAlarmLocked();
+ cancelAllLightAlarmsLocked();
}
@GuardedBy("this")
@@ -3406,7 +3406,7 @@
mLightState == LIGHT_STATE_IDLE
|| mLightState == LIGHT_STATE_WAITING_FOR_NETWORK;
} else {
- Slog.wtfStack(TAG, "stepLightIdleStateLocked called in invalid state");
+ Slog.wtfStack(TAG, "stepLightIdleStateLocked called in invalid state: " + mLightState);
return;
}
@@ -3441,7 +3441,7 @@
// connectivity... let's try to wait until the network comes back.
// We'll only wait for another full idle period, however, and then give up.
scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay);
- mNextLightAlarmTime = 0;
+ cancelLightAlarmLocked();
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
@@ -3467,7 +3467,7 @@
// We're entering IDLE. We may have used less than curLightIdleBudget for the
// maintenance window, so reschedule the alarm starting from now.
scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay);
- mNextLightAlarmTime = 0;
+ cancelLightAlarmLocked();
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
mLightState = LIGHT_STATE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
@@ -3609,7 +3609,7 @@
moveToStateLocked(STATE_IDLE, reason);
if (mLightState != LIGHT_STATE_OVERRIDE) {
mLightState = LIGHT_STATE_OVERRIDE;
- cancelLightAlarmLocked();
+ cancelAllLightAlarmsLocked();
}
addEvent(EVENT_DEEP_IDLE, null);
mGoingIdleWakeLock.acquire();
@@ -3950,11 +3950,21 @@
}
@GuardedBy("this")
- void cancelLightAlarmLocked() {
+ private void cancelAllLightAlarmsLocked() {
+ cancelLightAlarmLocked();
+ cancelLightMaintenanceAlarmLocked();
+ }
+
+ @GuardedBy("this")
+ private void cancelLightAlarmLocked() {
if (mNextLightAlarmTime != 0) {
mNextLightAlarmTime = 0;
mAlarmManager.cancel(mLightAlarmListener);
}
+ }
+
+ @GuardedBy("this")
+ private void cancelLightMaintenanceAlarmLocked() {
if (mNextLightMaintenanceAlarmTime != 0) {
mNextLightMaintenanceAlarmTime = 0;
mAlarmManager.cancel(mLightMaintenanceAlarmListener);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index a5c2bcc..90ec700 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -149,6 +149,8 @@
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import dalvik.annotation.optimization.NeverCompile;
+
import libcore.util.EmptyArray;
import java.io.FileDescriptor;
@@ -2869,6 +2871,7 @@
packageName, UserHandle.of(userId));
}
+ @NeverCompile // Avoid size overhead of debugging code.
void dumpImpl(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index bdfdd55..4e73b02 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -127,6 +127,8 @@
import com.android.server.utils.quota.Category;
import com.android.server.utils.quota.CountQuotaTracker;
+import dalvik.annotation.optimization.NeverCompile;
+
import libcore.util.EmptyArray;
import java.io.FileDescriptor;
@@ -3821,6 +3823,7 @@
});
}
+ @NeverCompile // Avoid size overhead of debugging code.
void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
final int filterAppId = UserHandle.getAppId(filterUid);
final long now = sSystemClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 649aa39..efcf14f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -56,6 +56,8 @@
import com.android.server.job.JobStatusDumpProto;
import com.android.server.job.JobStatusShortInfoProto;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1971,6 +1973,7 @@
}
// Dumpsys infrastructure
+ @NeverCompile // Avoid size overhead of debugging code.
public void dump(IndentingPrintWriter pw, boolean full, long nowElapsed) {
UserHandle.formatUid(pw, callingUid);
pw.print(" tag="); pw.println(tag);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index b96055f..48f8581 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -76,6 +76,8 @@
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import com.android.server.utils.AlarmQueue;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -4358,6 +4360,7 @@
//////////////////////////// DATA DUMP //////////////////////////////
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
diff --git a/apex/media/Android.bp b/apex/media/Android.bp
deleted file mode 100644
index 1a710a98b..0000000
--- a/apex/media/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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 {
- default_visibility: [
- ":__subpackages__",
- "//frameworks/av/apex",
- "//frameworks/av/apex/testing",
- ],
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-sdk {
- name: "media-module-sdk",
- bootclasspath_fragments: ["com.android.media-bootclasspath-fragment"],
- systemserverclasspath_fragments: ["com.android.media-systemserverclasspath-fragment"],
- java_sdk_libs: [
- "framework-media",
- ],
-}
diff --git a/apex/media/OWNERS b/apex/media/OWNERS
deleted file mode 100644
index 2c5965c..0000000
--- a/apex/media/OWNERS
+++ /dev/null
@@ -1,12 +0,0 @@
-# Bug component: 1344
-hdmoon@google.com
-jinpark@google.com
-klhyun@google.com
-lnilsson@google.com
-sungsoo@google.com
-
-# go/android-fwk-media-solutions for info on areas of ownership.
-include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
-
-# media reliability team packages/delivers the media mainline builds.
-include platform/frameworks/av:/media/janitors/reliability_mainline_OWNERS
diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp
deleted file mode 100644
index 545a0cd..0000000
--- a/apex/media/aidl/Android.bp
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// Copyright 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
- name: "stable-media-aidl-srcs",
- srcs: ["stable/**/*.aidl"],
- path: "stable",
-}
-
-filegroup {
- name: "private-media-aidl-srcs",
- srcs: ["private/**/I*.aidl"],
- path: "private",
-}
-
-filegroup {
- name: "media-aidl-srcs",
- srcs: [
- ":private-media-aidl-srcs",
- ":stable-media-aidl-srcs",
- ],
-}
diff --git a/apex/media/aidl/private/android/media/Controller2Link.aidl b/apex/media/aidl/private/android/media/Controller2Link.aidl
deleted file mode 100644
index 64edafc..0000000
--- a/apex/media/aidl/private/android/media/Controller2Link.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-parcelable Controller2Link;
diff --git a/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl b/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl
deleted file mode 100644
index e1c89e9..0000000
--- a/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Copyright 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.media;
-
-import android.media.Session2Token;
-import android.media.IMediaCommunicationServiceCallback;
-import android.media.MediaParceledListSlice;
-import android.view.KeyEvent;
-
-/** {@hide} */
-interface IMediaCommunicationService {
- void notifySession2Created(in Session2Token sessionToken);
- boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid);
- MediaParceledListSlice getSession2Tokens(int userId);
-
- void dispatchMediaKeyEvent(String packageName, in KeyEvent keyEvent, boolean asSystemService);
-
- void registerCallback(IMediaCommunicationServiceCallback callback, String packageName);
- void unregisterCallback(IMediaCommunicationServiceCallback callback);
-}
-
diff --git a/apex/media/aidl/private/android/media/IMediaCommunicationServiceCallback.aidl b/apex/media/aidl/private/android/media/IMediaCommunicationServiceCallback.aidl
deleted file mode 100644
index e347ebf..0000000
--- a/apex/media/aidl/private/android/media/IMediaCommunicationServiceCallback.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media;
-
-import android.media.Session2Token;
-import android.media.MediaParceledListSlice;
-
-/** {@hide} */
-oneway interface IMediaCommunicationServiceCallback {
- void onSession2Created(in Session2Token token);
- void onSession2Changed(in MediaParceledListSlice tokens);
-}
-
diff --git a/apex/media/aidl/private/android/media/IMediaController2.aidl b/apex/media/aidl/private/android/media/IMediaController2.aidl
deleted file mode 100644
index 42c6e70..0000000
--- a/apex/media/aidl/private/android/media/IMediaController2.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.media.Session2Command;
-
-/**
- * Interface from MediaSession2 to MediaController2.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- * @hide
- */
- // Code for AML only
-oneway interface IMediaController2 {
- void notifyConnected(int seq, in Bundle connectionResult) = 0;
- void notifyDisconnected(int seq) = 1;
- void notifyPlaybackActiveChanged(int seq, boolean playbackActive) = 2;
- void sendSessionCommand(int seq, in Session2Command command, in Bundle args,
- in ResultReceiver resultReceiver) = 3;
- void cancelSessionCommand(int seq) = 4;
- // Next Id : 5
-}
diff --git a/apex/media/aidl/private/android/media/IMediaSession2.aidl b/apex/media/aidl/private/android/media/IMediaSession2.aidl
deleted file mode 100644
index 26e717b..0000000
--- a/apex/media/aidl/private/android/media/IMediaSession2.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.media.Controller2Link;
-import android.media.Session2Command;
-
-/**
- * Interface from MediaController2 to MediaSession2.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from session to make session owner(s) frozen.
- * @hide
- */
- // Code for AML only
-oneway interface IMediaSession2 {
- void connect(in Controller2Link caller, int seq, in Bundle connectionRequest) = 0;
- void disconnect(in Controller2Link caller, int seq) = 1;
- void sendSessionCommand(in Controller2Link caller, int seq, in Session2Command sessionCommand,
- in Bundle args, in ResultReceiver resultReceiver) = 2;
- void cancelSessionCommand(in Controller2Link caller, int seq) = 3;
- // Next Id : 4
-}
diff --git a/apex/media/aidl/private/android/media/IMediaSession2Service.aidl b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl
deleted file mode 100644
index 10ac1be..0000000
--- a/apex/media/aidl/private/android/media/IMediaSession2Service.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2019 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.media;
-
-import android.os.Bundle;
-import android.media.Controller2Link;
-
-/**
- * Interface from MediaController2 to MediaSession2Service.
- * <p>
- * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
- * and holds calls from controller to make controller owner(s) frozen.
- * @hide
- */
-oneway interface IMediaSession2Service {
- void connect(in Controller2Link caller, int seq, in Bundle connectionRequest) = 0;
- // Next Id : 1
-}
diff --git a/apex/media/aidl/stable/android/media/Session2Token.aidl b/apex/media/aidl/stable/android/media/Session2Token.aidl
deleted file mode 100644
index c5980e9..0000000
--- a/apex/media/aidl/stable/android/media/Session2Token.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2019 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.media;
-
-parcelable Session2Token;
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
deleted file mode 100644
index 2c2af28..0000000
--- a/apex/media/framework/Android.bp
+++ /dev/null
@@ -1,167 +0,0 @@
-// 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-java_library {
- name: "updatable-media",
-
- srcs: [
- ":updatable-media-srcs",
- ],
-
- permitted_packages: [
- "android.media",
- ],
-
- optimize: {
- enabled: true,
- shrink: true,
- proguard_flags_files: ["updatable-media-proguard.flags"],
- },
-
- installable: true,
-
- sdk_version: "module_current",
- libs: [
- "androidx.annotation_annotation",
- "framework-annotations-lib",
- ],
- static_libs: [
- "exoplayer2-extractor",
- "mediatranscoding_aidl_interface-java",
- "modules-annotation-minsdk",
- "modules-utils-build",
- ],
- jarjar_rules: "jarjar_rules.txt",
-
- plugins: ["java_api_finder"],
-
- hostdex: true, // for hiddenapi check
- apex_available: [
- "com.android.media",
- "test_com.android.media",
- ],
- min_sdk_version: "29",
- lint: {
- strict_updatability_linting: true,
- },
- visibility: [
- "//frameworks/av/apex:__subpackages__",
- "//frameworks/base/apex/media/service",
- "//frameworks/base/api", // For framework-all
- ],
-}
-
-filegroup {
- name: "updatable-media-srcs",
- srcs: [
- "java/android/media/MediaFrameworkInitializer.java",
- ":media-aidl-srcs",
- ":mediaparceledlistslice-java-srcs",
- ":mediaparser-srcs",
- ":mediasession2-java-srcs",
- ":mediatranscoding-srcs",
- ],
- visibility: ["//frameworks/base"],
-}
-
-filegroup {
- name: "mediasession2-java-srcs",
- srcs: [
- "java/android/media/Controller2Link.java",
- "java/android/media/MediaConstants.java",
- "java/android/media/MediaController2.java",
- "java/android/media/MediaSession2.java",
- "java/android/media/MediaSession2Service.java",
- "java/android/media/Session2Command.java",
- "java/android/media/Session2CommandGroup.java",
- "java/android/media/Session2Link.java",
- "java/android/media/Session2Token.java",
- "java/android/media/MediaCommunicationManager.java",
- ],
- path: "java",
-}
-
-filegroup {
- name: "mediaparceledlistslice-java-srcs",
- srcs: [
- "java/android/media/MediaParceledListSlice.java",
- "java/android/media/BaseMediaParceledListSlice.java",
- ],
- path: "java",
-}
-
-filegroup {
- name: "mediaparser-srcs",
- srcs: [
- "java/android/media/MediaParser.java",
- ],
- path: "java",
-}
-
-filegroup {
- name: "mediatranscoding-srcs",
- srcs: [
- "java/android/media/ApplicationMediaCapabilities.java",
- "java/android/media/MediaFeature.java",
- "java/android/media/MediaTranscodingManager.java",
- ],
- path: "java",
-}
-
-java_sdk_library {
- name: "framework-media",
- defaults: ["framework-module-defaults"],
-
- // This is only used to define the APIs for updatable-media.
- api_only: true,
-
- srcs: [
- ":updatable-media-srcs",
- ],
-
- impl_library_visibility: ["//frameworks/av/apex:__subpackages__"],
-}
-
-cc_library_shared {
- name: "libmediaparser-jni",
- srcs: [
- "jni/android_media_MediaParserJNI.cpp",
- ],
- header_libs: ["jni_headers"],
- shared_libs: [
- "libandroid",
- "liblog",
- "libmediametrics",
- ],
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- "-Wunreachable-code",
- "-Wunused",
- ],
- apex_available: [
- "com.android.media",
- ],
- min_sdk_version: "29",
-}
diff --git a/apex/media/framework/TEST_MAPPING b/apex/media/framework/TEST_MAPPING
deleted file mode 100644
index 3d21914..0000000
--- a/apex/media/framework/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsMediaParserTestCases"
- },
- {
- "name": "CtsMediaParserHostTestCases"
- }
- ]
-}
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
deleted file mode 100644
index b7d7ed8..0000000
--- a/apex/media/framework/api/current.txt
+++ /dev/null
@@ -1,267 +0,0 @@
-// Signature format: 2.0
-package android.media {
-
- public final class ApplicationMediaCapabilities implements android.os.Parcelable {
- method @NonNull public static android.media.ApplicationMediaCapabilities createFromXml(@NonNull org.xmlpull.v1.XmlPullParser);
- method public int describeContents();
- method @NonNull public java.util.List<java.lang.String> getSupportedHdrTypes();
- method @NonNull public java.util.List<java.lang.String> getSupportedVideoMimeTypes();
- method @NonNull public java.util.List<java.lang.String> getUnsupportedHdrTypes();
- method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
- method public boolean isFormatSpecified(@NonNull String);
- method public boolean isHdrTypeSupported(@NonNull String);
- method public boolean isVideoMimeTypeSupported(@NonNull String);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
- }
-
- public static final class ApplicationMediaCapabilities.Builder {
- ctor public ApplicationMediaCapabilities.Builder();
- method @NonNull public android.media.ApplicationMediaCapabilities.Builder addSupportedHdrType(@NonNull String);
- method @NonNull public android.media.ApplicationMediaCapabilities.Builder addSupportedVideoMimeType(@NonNull String);
- method @NonNull public android.media.ApplicationMediaCapabilities.Builder addUnsupportedHdrType(@NonNull String);
- method @NonNull public android.media.ApplicationMediaCapabilities.Builder addUnsupportedVideoMimeType(@NonNull String);
- method @NonNull public android.media.ApplicationMediaCapabilities build();
- }
-
- public class MediaCommunicationManager {
- method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
- method @IntRange(from=1) public int getVersion();
- }
-
- public class MediaController2 implements java.lang.AutoCloseable {
- method public void cancelSessionCommand(@NonNull Object);
- method public void close();
- method @Nullable public android.media.Session2Token getConnectedToken();
- method public boolean isPlaybackActive();
- method @NonNull public Object sendSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle);
- }
-
- public static final class MediaController2.Builder {
- ctor public MediaController2.Builder(@NonNull android.content.Context, @NonNull android.media.Session2Token);
- method @NonNull public android.media.MediaController2 build();
- method @NonNull public android.media.MediaController2.Builder setConnectionHints(@NonNull android.os.Bundle);
- method @NonNull public android.media.MediaController2.Builder setControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaController2.ControllerCallback);
- }
-
- public abstract static class MediaController2.ControllerCallback {
- ctor public MediaController2.ControllerCallback();
- method public void onCommandResult(@NonNull android.media.MediaController2, @NonNull Object, @NonNull android.media.Session2Command, @NonNull android.media.Session2Command.Result);
- method public void onConnected(@NonNull android.media.MediaController2, @NonNull android.media.Session2CommandGroup);
- method public void onDisconnected(@NonNull android.media.MediaController2);
- method public void onPlaybackActiveChanged(@NonNull android.media.MediaController2, boolean);
- method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaController2, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
- }
-
- public final class MediaFeature {
- ctor public MediaFeature();
- }
-
- public static final class MediaFeature.HdrType {
- field public static final String DOLBY_VISION = "android.media.feature.hdr.dolby_vision";
- field public static final String HDR10 = "android.media.feature.hdr.hdr10";
- field public static final String HDR10_PLUS = "android.media.feature.hdr.hdr10_plus";
- field public static final String HLG = "android.media.feature.hdr.hlg";
- }
-
- public final class MediaParser {
- method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException;
- method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...);
- method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer);
- method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
- method @NonNull public String getParserName();
- method @NonNull public static java.util.List<java.lang.String> getParserNames(@NonNull android.media.MediaFormat);
- method public void release();
- method public void seek(@NonNull android.media.MediaParser.SeekPoint);
- method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
- method @NonNull public android.media.MediaParser setParameter(@NonNull String, @NonNull Object);
- method public boolean supportsParameter(@NonNull String);
- field public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = "android.media.mediaparser.adts.enableCbrSeeking";
- field public static final String PARAMETER_AMR_ENABLE_CBR_SEEKING = "android.media.mediaparser.amr.enableCbrSeeking";
- field public static final String PARAMETER_FLAC_DISABLE_ID3 = "android.media.mediaparser.flac.disableId3";
- field public static final String PARAMETER_MATROSKA_DISABLE_CUES_SEEKING = "android.media.mediaparser.matroska.disableCuesSeeking";
- field public static final String PARAMETER_MP3_DISABLE_ID3 = "android.media.mediaparser.mp3.disableId3";
- field public static final String PARAMETER_MP3_ENABLE_CBR_SEEKING = "android.media.mediaparser.mp3.enableCbrSeeking";
- field public static final String PARAMETER_MP3_ENABLE_INDEX_SEEKING = "android.media.mediaparser.mp3.enableIndexSeeking";
- field public static final String PARAMETER_MP4_IGNORE_EDIT_LISTS = "android.media.mediaparser.mp4.ignoreEditLists";
- field public static final String PARAMETER_MP4_IGNORE_TFDT_BOX = "android.media.mediaparser.mp4.ignoreTfdtBox";
- field public static final String PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES = "android.media.mediaparser.mp4.treatVideoFramesAsKeyframes";
- field public static final String PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES = "android.media.mediaparser.ts.allowNonIdrAvcKeyframes";
- field public static final String PARAMETER_TS_DETECT_ACCESS_UNITS = "android.media.mediaparser.ts.ignoreDetectAccessUnits";
- field public static final String PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS = "android.media.mediaparser.ts.enableHdmvDtsAudioStreams";
- field public static final String PARAMETER_TS_IGNORE_AAC_STREAM = "android.media.mediaparser.ts.ignoreAacStream";
- field public static final String PARAMETER_TS_IGNORE_AVC_STREAM = "android.media.mediaparser.ts.ignoreAvcStream";
- field public static final String PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM = "android.media.mediaparser.ts.ignoreSpliceInfoStream";
- field public static final String PARAMETER_TS_MODE = "android.media.mediaparser.ts.mode";
- field public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser";
- field public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser";
- field public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser";
- field public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser";
- field public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser";
- field public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser";
- field public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser";
- field public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser";
- field public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser";
- field public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser";
- field public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser";
- field public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser";
- field public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser";
- field public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN";
- field public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser";
- field public static final int SAMPLE_FLAG_DECODE_ONLY = -2147483648; // 0x80000000
- field public static final int SAMPLE_FLAG_ENCRYPTED = 1073741824; // 0x40000000
- field public static final int SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA = 268435456; // 0x10000000
- field public static final int SAMPLE_FLAG_KEY_FRAME = 1; // 0x1
- field public static final int SAMPLE_FLAG_LAST_SAMPLE = 536870912; // 0x20000000
- }
-
- public static interface MediaParser.InputReader {
- method public long getLength();
- method public long getPosition();
- method public int read(@NonNull byte[], int, int) throws java.io.IOException;
- }
-
- public static interface MediaParser.OutputConsumer {
- method public void onSampleCompleted(int, long, int, int, int, @Nullable android.media.MediaCodec.CryptoInfo);
- method public void onSampleDataFound(int, @NonNull android.media.MediaParser.InputReader) throws java.io.IOException;
- method public void onSeekMapFound(@NonNull android.media.MediaParser.SeekMap);
- method public void onTrackCountFound(int);
- method public void onTrackDataFound(int, @NonNull android.media.MediaParser.TrackData);
- }
-
- public static final class MediaParser.ParsingException extends java.io.IOException {
- }
-
- public static final class MediaParser.SeekMap {
- method public long getDurationMicros();
- method @NonNull public android.util.Pair<android.media.MediaParser.SeekPoint,android.media.MediaParser.SeekPoint> getSeekPoints(long);
- method public boolean isSeekable();
- field public static final int UNKNOWN_DURATION = -2147483648; // 0x80000000
- }
-
- public static final class MediaParser.SeekPoint {
- field @NonNull public static final android.media.MediaParser.SeekPoint START;
- field public final long position;
- field public final long timeMicros;
- }
-
- public static interface MediaParser.SeekableInputReader extends android.media.MediaParser.InputReader {
- method public void seekToPosition(long);
- }
-
- public static final class MediaParser.TrackData {
- field @Nullable public final android.media.DrmInitData drmInitData;
- field @NonNull public final android.media.MediaFormat mediaFormat;
- }
-
- public static final class MediaParser.UnrecognizedInputFormatException extends java.io.IOException {
- }
-
- public class MediaSession2 implements java.lang.AutoCloseable {
- method public void broadcastSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle);
- method public void cancelSessionCommand(@NonNull android.media.MediaSession2.ControllerInfo, @NonNull Object);
- method public void close();
- method @NonNull public java.util.List<android.media.MediaSession2.ControllerInfo> getConnectedControllers();
- method @NonNull public String getId();
- method @NonNull public android.media.Session2Token getToken();
- method public boolean isPlaybackActive();
- method @NonNull public Object sendSessionCommand(@NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
- method public void setPlaybackActive(boolean);
- }
-
- public static final class MediaSession2.Builder {
- ctor public MediaSession2.Builder(@NonNull android.content.Context);
- method @NonNull public android.media.MediaSession2 build();
- method @NonNull public android.media.MediaSession2.Builder setExtras(@NonNull android.os.Bundle);
- method @NonNull public android.media.MediaSession2.Builder setId(@NonNull String);
- method @NonNull public android.media.MediaSession2.Builder setSessionActivity(@Nullable android.app.PendingIntent);
- method @NonNull public android.media.MediaSession2.Builder setSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaSession2.SessionCallback);
- }
-
- public static final class MediaSession2.ControllerInfo {
- method @NonNull public android.os.Bundle getConnectionHints();
- method @NonNull public String getPackageName();
- method @NonNull public android.media.session.MediaSessionManager.RemoteUserInfo getRemoteUserInfo();
- method public int getUid();
- }
-
- public abstract static class MediaSession2.SessionCallback {
- ctor public MediaSession2.SessionCallback();
- method public void onCommandResult(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull Object, @NonNull android.media.Session2Command, @NonNull android.media.Session2Command.Result);
- method @Nullable public android.media.Session2CommandGroup onConnect(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo);
- method public void onDisconnected(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo);
- method public void onPostConnect(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo);
- method @Nullable public android.media.Session2Command.Result onSessionCommand(@NonNull android.media.MediaSession2, @NonNull android.media.MediaSession2.ControllerInfo, @NonNull android.media.Session2Command, @Nullable android.os.Bundle);
- }
-
- public abstract class MediaSession2Service extends android.app.Service {
- ctor public MediaSession2Service();
- method public final void addSession(@NonNull android.media.MediaSession2);
- method @NonNull public final java.util.List<android.media.MediaSession2> getSessions();
- method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
- method @Nullable public abstract android.media.MediaSession2 onGetSession(@NonNull android.media.MediaSession2.ControllerInfo);
- method @Nullable public abstract android.media.MediaSession2Service.MediaNotification onUpdateNotification(@NonNull android.media.MediaSession2);
- method public final void removeSession(@NonNull android.media.MediaSession2);
- field public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
- }
-
- public static class MediaSession2Service.MediaNotification {
- ctor public MediaSession2Service.MediaNotification(int, @NonNull android.app.Notification);
- method @NonNull public android.app.Notification getNotification();
- method public int getNotificationId();
- }
-
- public final class Session2Command implements android.os.Parcelable {
- ctor public Session2Command(int);
- ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle);
- method public int describeContents();
- method public int getCommandCode();
- method @Nullable public String getCustomAction();
- method @Nullable public android.os.Bundle getCustomExtras();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0
- field @NonNull public static final android.os.Parcelable.Creator<android.media.Session2Command> CREATOR;
- }
-
- public static final class Session2Command.Result {
- ctor public Session2Command.Result(int, @Nullable android.os.Bundle);
- method public int getResultCode();
- method @Nullable public android.os.Bundle getResultData();
- field public static final int RESULT_ERROR_UNKNOWN_ERROR = -1; // 0xffffffff
- field public static final int RESULT_INFO_SKIPPED = 1; // 0x1
- field public static final int RESULT_SUCCESS = 0; // 0x0
- }
-
- public final class Session2CommandGroup implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public java.util.Set<android.media.Session2Command> getCommands();
- method public boolean hasCommand(@NonNull android.media.Session2Command);
- method public boolean hasCommand(int);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.media.Session2CommandGroup> CREATOR;
- }
-
- public static final class Session2CommandGroup.Builder {
- ctor public Session2CommandGroup.Builder();
- ctor public Session2CommandGroup.Builder(@NonNull android.media.Session2CommandGroup);
- method @NonNull public android.media.Session2CommandGroup.Builder addCommand(@NonNull android.media.Session2Command);
- method @NonNull public android.media.Session2CommandGroup build();
- method @NonNull public android.media.Session2CommandGroup.Builder removeCommand(@NonNull android.media.Session2Command);
- }
-
- public final class Session2Token implements android.os.Parcelable {
- ctor public Session2Token(@NonNull android.content.Context, @NonNull android.content.ComponentName);
- method public int describeContents();
- method @NonNull public android.os.Bundle getExtras();
- method @NonNull public String getPackageName();
- method @Nullable public String getServiceName();
- method public int getType();
- method public int getUid();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.media.Session2Token> CREATOR;
- field public static final int TYPE_SESSION = 0; // 0x0
- field public static final int TYPE_SESSION_SERVICE = 1; // 0x1
- }
-
-}
-
diff --git a/apex/media/framework/api/module-lib-current.txt b/apex/media/framework/api/module-lib-current.txt
deleted file mode 100644
index 7317f14..0000000
--- a/apex/media/framework/api/module-lib-current.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-// Signature format: 2.0
-package android.media {
-
- public class MediaCommunicationManager {
- method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean);
- method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaCommunicationManager.SessionCallback);
- method public void unregisterSessionCallback(@NonNull android.media.MediaCommunicationManager.SessionCallback);
- }
-
- public static interface MediaCommunicationManager.SessionCallback {
- method public default void onSession2TokenCreated(@NonNull android.media.Session2Token);
- method public default void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>);
- }
-
- public class MediaFrameworkInitializer {
- method public static void registerServiceWrappers();
- method public static void setMediaServiceManager(@NonNull android.media.MediaServiceManager);
- }
-
- @Deprecated public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable {
- ctor @Deprecated public MediaParceledListSlice(@NonNull java.util.List<T>);
- method @Deprecated public int describeContents();
- method @Deprecated @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList();
- method @Deprecated public java.util.List<T> getList();
- method @Deprecated public void setInlineCountLimit(int);
- method @Deprecated public void writeToParcel(android.os.Parcel, int);
- field @Deprecated @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR;
- }
-
-}
-
diff --git a/apex/media/framework/api/module-lib-removed.txt b/apex/media/framework/api/module-lib-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/framework/api/module-lib-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/framework/api/removed.txt b/apex/media/framework/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/framework/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/framework/api/system-current.txt b/apex/media/framework/api/system-current.txt
deleted file mode 100644
index 6eea769..0000000
--- a/apex/media/framework/api/system-current.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-// Signature format: 2.0
-package android.media {
-
- public final class MediaTranscodingManager {
- method @Nullable public android.media.MediaTranscodingManager.TranscodingSession enqueueRequest(@NonNull android.media.MediaTranscodingManager.TranscodingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaTranscodingManager.OnTranscodingFinishedListener);
- }
-
- @java.lang.FunctionalInterface public static interface MediaTranscodingManager.OnTranscodingFinishedListener {
- method public void onTranscodingFinished(@NonNull android.media.MediaTranscodingManager.TranscodingSession);
- }
-
- public abstract static class MediaTranscodingManager.TranscodingRequest {
- method public int getClientPid();
- method public int getClientUid();
- method @Nullable public android.os.ParcelFileDescriptor getDestinationFileDescriptor();
- method @NonNull public android.net.Uri getDestinationUri();
- method @Nullable public android.os.ParcelFileDescriptor getSourceFileDescriptor();
- method @NonNull public android.net.Uri getSourceUri();
- }
-
- public static class MediaTranscodingManager.TranscodingRequest.VideoFormatResolver {
- ctor public MediaTranscodingManager.TranscodingRequest.VideoFormatResolver(@NonNull android.media.ApplicationMediaCapabilities, @NonNull android.media.MediaFormat);
- method @Nullable public android.media.MediaFormat resolveVideoFormat();
- method public boolean shouldTranscode();
- }
-
- public static final class MediaTranscodingManager.TranscodingSession {
- method public boolean addClientUid(int);
- method public void cancel();
- method @NonNull public java.util.List<java.lang.Integer> getClientUids();
- method public int getErrorCode();
- method @IntRange(from=0, to=100) public int getProgress();
- method public int getResult();
- method public int getSessionId();
- method public int getStatus();
- method public void setOnProgressUpdateListener(@NonNull java.util.concurrent.Executor, @Nullable android.media.MediaTranscodingManager.TranscodingSession.OnProgressUpdateListener);
- field public static final int ERROR_DROPPED_BY_SERVICE = 1; // 0x1
- field public static final int ERROR_NONE = 0; // 0x0
- field public static final int ERROR_SERVICE_DIED = 2; // 0x2
- field public static final int RESULT_CANCELED = 4; // 0x4
- field public static final int RESULT_ERROR = 3; // 0x3
- field public static final int RESULT_NONE = 1; // 0x1
- field public static final int RESULT_SUCCESS = 2; // 0x2
- field public static final int STATUS_FINISHED = 3; // 0x3
- field public static final int STATUS_PAUSED = 4; // 0x4
- field public static final int STATUS_PENDING = 1; // 0x1
- field public static final int STATUS_RUNNING = 2; // 0x2
- }
-
- @java.lang.FunctionalInterface public static interface MediaTranscodingManager.TranscodingSession.OnProgressUpdateListener {
- method public void onProgressUpdate(@NonNull android.media.MediaTranscodingManager.TranscodingSession, @IntRange(from=0, to=100) int);
- }
-
- public static final class MediaTranscodingManager.VideoTranscodingRequest extends android.media.MediaTranscodingManager.TranscodingRequest {
- method @NonNull public android.media.MediaFormat getVideoTrackFormat();
- }
-
- public static final class MediaTranscodingManager.VideoTranscodingRequest.Builder {
- ctor public MediaTranscodingManager.VideoTranscodingRequest.Builder(@NonNull android.net.Uri, @NonNull android.net.Uri, @NonNull android.media.MediaFormat);
- method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest build();
- method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setClientPid(int);
- method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setClientUid(int);
- method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setDestinationFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
- method @NonNull public android.media.MediaTranscodingManager.VideoTranscodingRequest.Builder setSourceFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
- }
-
-}
-
diff --git a/apex/media/framework/api/system-removed.txt b/apex/media/framework/api/system-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/framework/api/system-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt
deleted file mode 100644
index 91489dc..0000000
--- a/apex/media/framework/jarjar_rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-rule com.android.modules.** android.media.internal.@1
-rule com.google.android.exoplayer2.** android.media.internal.exo.@1
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
deleted file mode 100644
index 97fa0ec..0000000
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * 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.media;
-
-import android.annotation.NonNull;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-
-import com.android.modules.annotation.MinSdk;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- ApplicationMediaCapabilities is an immutable class that encapsulates an application's capabilities
- for handling newer video codec format and media features.
-
- <p>
- Android 12 introduces Compatible media transcoding feature. See
- <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding">
- Compatible media transcoding</a>. By default, Android assumes apps can support playback of all
- media formats. Apps that would like to request that media be transcoded into a more compatible
- format should declare their media capabilities in a media_capabilities.xml resource file and add it
- as a property tag in the AndroidManifest.xml file. Here is a example:
- <pre>
- {@code
- <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
- <format android:name="HEVC" supported="true"/>
- <format android:name="HDR10" supported="false"/>
- <format android:name="HDR10Plus" supported="false"/>
- </media-capabilities>
- }
- </pre>
- The ApplicationMediaCapabilities class is generated from this xml and used by the platform to
- represent an application's media capabilities in order to determine whether modern media files need
- to be transcoded for that application.
- </p>
-
- <p>
- ApplicationMediaCapabilities objects can also be built by applications at runtime for use with
- {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more
- control over the transcoding that is built into the platform. ApplicationMediaCapabilities
- provided by applications at runtime like this override the default manifest capabilities for that
- media access.The object could be build either through {@link #createFromXml(XmlPullParser)} or
- through the builder class {@link ApplicationMediaCapabilities.Builder}
-
- <h3> Video Codec Support</h3>
- <p>
- Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support
- for newer format with this class as they are assumed to support older format like h.264.
-
- <h3>Capability of handling HDR(high dynamic range) video</h3>
- <p>
- There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform,
- application will only need to specify individual types they supported.
- */
-@MinSdk(Build.VERSION_CODES.S)
-public final class ApplicationMediaCapabilities implements Parcelable {
- private static final String TAG = "ApplicationMediaCapabilities";
-
- /** List of supported video codec mime types. */
- private Set<String> mSupportedVideoMimeTypes = new HashSet<>();
-
- /** List of unsupported video codec mime types. */
- private Set<String> mUnsupportedVideoMimeTypes = new HashSet<>();
-
- /** List of supported hdr types. */
- private Set<String> mSupportedHdrTypes = new HashSet<>();
-
- /** List of unsupported hdr types. */
- private Set<String> mUnsupportedHdrTypes = new HashSet<>();
-
- private boolean mIsSlowMotionSupported = false;
-
- private ApplicationMediaCapabilities(Builder b) {
- mSupportedVideoMimeTypes.addAll(b.getSupportedVideoMimeTypes());
- mUnsupportedVideoMimeTypes.addAll(b.getUnsupportedVideoMimeTypes());
- mSupportedHdrTypes.addAll(b.getSupportedHdrTypes());
- mUnsupportedHdrTypes.addAll(b.getUnsupportedHdrTypes());
- mIsSlowMotionSupported = b.mIsSlowMotionSupported;
- }
-
- /**
- * Query if a video codec format is supported by the application.
- * <p>
- * If the application has not specified supporting the format or not, this will return false.
- * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
- *
- * @param videoMime The mime type of the video codec format. Must be the one used in
- * {@link MediaFormat#KEY_MIME}.
- * @return true if application supports the video codec format, false otherwise.
- */
- public boolean isVideoMimeTypeSupported(
- @NonNull String videoMime) {
- if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
- return true;
- }
- return false;
- }
-
- /**
- * Query if a HDR type is supported by the application.
- * <p>
- * If the application has not specified supporting the format or not, this will return false.
- * Use {@link #isFormatSpecified(String)} to query if a format is specified or not.
- *
- * @param hdrType The type of the HDR format.
- * @return true if application supports the HDR format, false otherwise.
- */
- public boolean isHdrTypeSupported(
- @NonNull @MediaFeature.MediaHdrType String hdrType) {
- if (mSupportedHdrTypes.contains(hdrType)) {
- return true;
- }
- return false;
- }
-
- /**
- * Query if a format is specified by the application.
- * <p>
- * The format could be either the video format or the hdr format.
- *
- * @param format The name of the format.
- * @return true if application specifies the format, false otherwise.
- */
- public boolean isFormatSpecified(@NonNull String format) {
- if (mSupportedVideoMimeTypes.contains(format) || mUnsupportedVideoMimeTypes.contains(format)
- || mSupportedHdrTypes.contains(format) || mUnsupportedHdrTypes.contains(format)) {
- return true;
-
- }
- return false;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // Write out the supported video mime types.
- dest.writeInt(mSupportedVideoMimeTypes.size());
- for (String cap : mSupportedVideoMimeTypes) {
- dest.writeString(cap);
- }
- // Write out the unsupported video mime types.
- dest.writeInt(mUnsupportedVideoMimeTypes.size());
- for (String cap : mUnsupportedVideoMimeTypes) {
- dest.writeString(cap);
- }
- // Write out the supported hdr types.
- dest.writeInt(mSupportedHdrTypes.size());
- for (String cap : mSupportedHdrTypes) {
- dest.writeString(cap);
- }
- // Write out the unsupported hdr types.
- dest.writeInt(mUnsupportedHdrTypes.size());
- for (String cap : mUnsupportedHdrTypes) {
- dest.writeString(cap);
- }
- // Write out the supported slow motion.
- dest.writeBoolean(mIsSlowMotionSupported);
- }
-
- @Override
- public String toString() {
- String caps = new String(
- "Supported Video MimeTypes: " + mSupportedVideoMimeTypes.toString());
- caps += "Unsupported Video MimeTypes: " + mUnsupportedVideoMimeTypes.toString();
- caps += "Supported HDR types: " + mSupportedHdrTypes.toString();
- caps += "Unsupported HDR types: " + mUnsupportedHdrTypes.toString();
- caps += "Supported slow motion: " + mIsSlowMotionSupported;
- return caps;
- }
-
- @NonNull
- public static final Creator<ApplicationMediaCapabilities> CREATOR =
- new Creator<ApplicationMediaCapabilities>() {
- public ApplicationMediaCapabilities createFromParcel(Parcel in) {
- ApplicationMediaCapabilities.Builder builder =
- new ApplicationMediaCapabilities.Builder();
-
- // Parse supported video codec mime types.
- int count = in.readInt();
- for (int readCount = 0; readCount < count; ++readCount) {
- builder.addSupportedVideoMimeType(in.readString());
- }
-
- // Parse unsupported video codec mime types.
- count = in.readInt();
- for (int readCount = 0; readCount < count; ++readCount) {
- builder.addUnsupportedVideoMimeType(in.readString());
- }
-
- // Parse supported hdr types.
- count = in.readInt();
- for (int readCount = 0; readCount < count; ++readCount) {
- builder.addSupportedHdrType(in.readString());
- }
-
- // Parse unsupported hdr types.
- count = in.readInt();
- for (int readCount = 0; readCount < count; ++readCount) {
- builder.addUnsupportedHdrType(in.readString());
- }
-
- boolean supported = in.readBoolean();
- builder.setSlowMotionSupported(supported);
-
- return builder.build();
- }
-
- public ApplicationMediaCapabilities[] newArray(int size) {
- return new ApplicationMediaCapabilities[size];
- }
- };
-
- /**
- * Query the video codec mime types supported by the application.
- * @return List of supported video codec mime types. The list will be empty if there are none.
- */
- @NonNull
- public List<String> getSupportedVideoMimeTypes() {
- return new ArrayList<>(mSupportedVideoMimeTypes);
- }
-
- /**
- * Query the video codec mime types that are not supported by the application.
- * @return List of unsupported video codec mime types. The list will be empty if there are none.
- */
- @NonNull
- public List<String> getUnsupportedVideoMimeTypes() {
- return new ArrayList<>(mUnsupportedVideoMimeTypes);
- }
-
- /**
- * Query all hdr types that are supported by the application.
- * @return List of supported hdr types. The list will be empty if there are none.
- */
- @NonNull
- public List<String> getSupportedHdrTypes() {
- return new ArrayList<>(mSupportedHdrTypes);
- }
-
- /**
- * Query all hdr types that are not supported by the application.
- * @return List of unsupported hdr types. The list will be empty if there are none.
- */
- @NonNull
- public List<String> getUnsupportedHdrTypes() {
- return new ArrayList<>(mUnsupportedHdrTypes);
- }
-
- /**
- * Whether handling of slow-motion video is supported
- * @hide
- */
- public boolean isSlowMotionSupported() {
- return mIsSlowMotionSupported;
- }
-
- /**
- * Creates {@link ApplicationMediaCapabilities} from an xml.
- *
- * The xml's syntax is the same as the media_capabilities.xml used by the AndroidManifest.xml.
- * <p> Here is an example:
- *
- * <pre>
- * {@code
- * <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
- * <format android:name="HEVC" supported="true"/>
- * <format android:name="HDR10" supported="false"/>
- * <format android:name="HDR10Plus" supported="false"/>
- * </media-capabilities>
- * }
- * </pre>
- * <p>
- *
- * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
- * @return An ApplicationMediaCapabilities object.
- * @throws UnsupportedOperationException if the capabilities in xml config are invalid or
- * incompatible.
- */
- // TODO: Add developer.android.com link for the format of the xml.
- @NonNull
- public static ApplicationMediaCapabilities createFromXml(@NonNull XmlPullParser xmlParser) {
- ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();
- builder.parseXml(xmlParser);
- return builder.build();
- }
-
- /**
- * Builder class for {@link ApplicationMediaCapabilities} objects.
- * Use this class to configure and create an ApplicationMediaCapabilities instance. Builder
- * could be created from an existing ApplicationMediaCapabilities object, from a xml file or
- * MediaCodecList.
- * //TODO(hkuang): Add xml parsing support to the builder.
- */
- public final static class Builder {
- /** List of supported video codec mime types. */
- private Set<String> mSupportedVideoMimeTypes = new HashSet<>();
-
- /** List of supported hdr types. */
- private Set<String> mSupportedHdrTypes = new HashSet<>();
-
- /** List of unsupported video codec mime types. */
- private Set<String> mUnsupportedVideoMimeTypes = new HashSet<>();
-
- /** List of unsupported hdr types. */
- private Set<String> mUnsupportedHdrTypes = new HashSet<>();
-
- private boolean mIsSlowMotionSupported = false;
-
- /* Map to save the format read from the xml. */
- private Map<String, Boolean> mFormatSupportedMap = new HashMap<String, Boolean>();
-
- /**
- * Constructs a new Builder with all the supports default to false.
- */
- public Builder() {
- }
-
- private void parseXml(@NonNull XmlPullParser xmlParser)
- throws UnsupportedOperationException {
- if (xmlParser == null) {
- throw new IllegalArgumentException("XmlParser must not be null");
- }
-
- try {
- while (xmlParser.next() != XmlPullParser.START_TAG) {
- continue;
- }
-
- // Validates the tag is "media-capabilities".
- if (!xmlParser.getName().equals("media-capabilities")) {
- throw new UnsupportedOperationException("Invalid tag");
- }
-
- xmlParser.next();
- while (xmlParser.getEventType() != XmlPullParser.END_TAG) {
- while (xmlParser.getEventType() != XmlPullParser.START_TAG) {
- if (xmlParser.getEventType() == XmlPullParser.END_DOCUMENT) {
- return;
- }
- xmlParser.next();
- }
-
- // Validates the tag is "format".
- if (xmlParser.getName().equals("format")) {
- parseFormatTag(xmlParser);
- } else {
- throw new UnsupportedOperationException("Invalid tag");
- }
- while (xmlParser.getEventType() != XmlPullParser.END_TAG) {
- xmlParser.next();
- }
- xmlParser.next();
- }
- } catch (XmlPullParserException xppe) {
- throw new UnsupportedOperationException("Ill-formatted xml file");
- } catch (java.io.IOException ioe) {
- throw new UnsupportedOperationException("Unable to read xml file");
- }
- }
-
- private void parseFormatTag(XmlPullParser xmlParser) {
- String name = null;
- String supported = null;
- for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
- String attrName = xmlParser.getAttributeName(i);
- if (attrName.equals("name")) {
- name = xmlParser.getAttributeValue(i);
- } else if (attrName.equals("supported")) {
- supported = xmlParser.getAttributeValue(i);
- } else {
- throw new UnsupportedOperationException("Invalid attribute name " + attrName);
- }
- }
-
- if (name != null && supported != null) {
- if (!supported.equals("true") && !supported.equals("false")) {
- throw new UnsupportedOperationException(
- ("Supported value must be either true or false"));
- }
- boolean isSupported = Boolean.parseBoolean(supported);
-
- // Check if the format is already found before.
- if (mFormatSupportedMap.get(name) != null && mFormatSupportedMap.get(name)
- != isSupported) {
- throw new UnsupportedOperationException(
- "Format: " + name + " has conflict supported value");
- }
-
- switch (name) {
- case "HEVC":
- if (isSupported) {
- mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
- } else {
- mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
- }
- break;
- case "VP9":
- if (isSupported) {
- mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_VP9);
- } else {
- mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_VP9);
- }
- break;
- case "AV1":
- if (isSupported) {
- mSupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_AV1);
- } else {
- mUnsupportedVideoMimeTypes.add(MediaFormat.MIMETYPE_VIDEO_AV1);
- }
- break;
- case "HDR10":
- if (isSupported) {
- mSupportedHdrTypes.add(MediaFeature.HdrType.HDR10);
- } else {
- mUnsupportedHdrTypes.add(MediaFeature.HdrType.HDR10);
- }
- break;
- case "HDR10Plus":
- if (isSupported) {
- mSupportedHdrTypes.add(MediaFeature.HdrType.HDR10_PLUS);
- } else {
- mUnsupportedHdrTypes.add(MediaFeature.HdrType.HDR10_PLUS);
- }
- break;
- case "Dolby-Vision":
- if (isSupported) {
- mSupportedHdrTypes.add(MediaFeature.HdrType.DOLBY_VISION);
- } else {
- mUnsupportedHdrTypes.add(MediaFeature.HdrType.DOLBY_VISION);
- }
- break;
- case "HLG":
- if (isSupported) {
- mSupportedHdrTypes.add(MediaFeature.HdrType.HLG);
- } else {
- mUnsupportedHdrTypes.add(MediaFeature.HdrType.HLG);
- }
- break;
- case "SlowMotion":
- mIsSlowMotionSupported = isSupported;
- break;
- default:
- Log.w(TAG, "Invalid format name " + name);
- }
- // Save the name and isSupported into the map for validate later.
- mFormatSupportedMap.put(name, isSupported);
- } else {
- throw new UnsupportedOperationException(
- "Format name and supported must both be specified");
- }
- }
-
- /**
- * Builds a {@link ApplicationMediaCapabilities} object.
- *
- * @return a new {@link ApplicationMediaCapabilities} instance successfully initialized
- * with all the parameters set on this <code>Builder</code>.
- * @throws UnsupportedOperationException if the parameters set on the
- * <code>Builder</code> were incompatible, or if they
- * are not supported by the
- * device.
- */
- @NonNull
- public ApplicationMediaCapabilities build() {
- Log.d(TAG,
- "Building ApplicationMediaCapabilities with: (Supported HDR: "
- + mSupportedHdrTypes.toString() + " Unsupported HDR: "
- + mUnsupportedHdrTypes.toString() + ") (Supported Codec: "
- + " " + mSupportedVideoMimeTypes.toString() + " Unsupported Codec:"
- + mUnsupportedVideoMimeTypes.toString() + ") "
- + mIsSlowMotionSupported);
-
- // If hdr is supported, application must also support hevc.
- if (!mSupportedHdrTypes.isEmpty() && !mSupportedVideoMimeTypes.contains(
- MediaFormat.MIMETYPE_VIDEO_HEVC)) {
- throw new UnsupportedOperationException("Only support HEVC mime type");
- }
- return new ApplicationMediaCapabilities(this);
- }
-
- /**
- * Adds a supported video codec mime type.
- *
- * @param codecMime Supported codec mime types. Must be one of the mime type defined
- * in {@link MediaFormat}.
- * @throws IllegalArgumentException if mime type is not valid.
- */
- @NonNull
- public Builder addSupportedVideoMimeType(
- @NonNull String codecMime) {
- mSupportedVideoMimeTypes.add(codecMime);
- return this;
- }
-
- private List<String> getSupportedVideoMimeTypes() {
- return new ArrayList<>(mSupportedVideoMimeTypes);
- }
-
- private boolean isValidVideoCodecMimeType(@NonNull String codecMime) {
- if (!codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)
- && !codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)
- && !codecMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
- return false;
- }
- return true;
- }
-
- /**
- * Adds an unsupported video codec mime type.
- *
- * @param codecMime Unsupported codec mime type. Must be one of the mime type defined
- * in {@link MediaFormat}.
- * @throws IllegalArgumentException if mime type is not valid.
- */
- @NonNull
- public Builder addUnsupportedVideoMimeType(
- @NonNull String codecMime) {
- if (!isValidVideoCodecMimeType(codecMime)) {
- throw new IllegalArgumentException("Invalid codec mime type: " + codecMime);
- }
- mUnsupportedVideoMimeTypes.add(codecMime);
- return this;
- }
-
- private List<String> getUnsupportedVideoMimeTypes() {
- return new ArrayList<>(mUnsupportedVideoMimeTypes);
- }
-
- /**
- * Adds a supported hdr type.
- *
- * @param hdrType Supported hdr type. Must be one of the String defined in
- * {@link MediaFeature.HdrType}.
- * @throws IllegalArgumentException if hdrType is not valid.
- */
- @NonNull
- public Builder addSupportedHdrType(
- @NonNull @MediaFeature.MediaHdrType String hdrType) {
- if (!isValidVideoCodecHdrType(hdrType)) {
- throw new IllegalArgumentException("Invalid hdr type: " + hdrType);
- }
- mSupportedHdrTypes.add(hdrType);
- return this;
- }
-
- private List<String> getSupportedHdrTypes() {
- return new ArrayList<>(mSupportedHdrTypes);
- }
-
- private boolean isValidVideoCodecHdrType(@NonNull String hdrType) {
- if (!hdrType.equals(MediaFeature.HdrType.DOLBY_VISION)
- && !hdrType.equals(MediaFeature.HdrType.HDR10)
- && !hdrType.equals(MediaFeature.HdrType.HDR10_PLUS)
- && !hdrType.equals(MediaFeature.HdrType.HLG)) {
- return false;
- }
- return true;
- }
-
- /**
- * Adds an unsupported hdr type.
- *
- * @param hdrType Unsupported hdr type. Must be one of the String defined in
- * {@link MediaFeature.HdrType}.
- * @throws IllegalArgumentException if hdrType is not valid.
- */
- @NonNull
- public Builder addUnsupportedHdrType(
- @NonNull @MediaFeature.MediaHdrType String hdrType) {
- if (!isValidVideoCodecHdrType(hdrType)) {
- throw new IllegalArgumentException("Invalid hdr type: " + hdrType);
- }
- mUnsupportedHdrTypes.add(hdrType);
- return this;
- }
-
- private List<String> getUnsupportedHdrTypes() {
- return new ArrayList<>(mUnsupportedHdrTypes);
- }
-
- /**
- * Sets whether slow-motion video is supported.
- * If an application indicates support for slow-motion, it is application's responsibility
- * to parse the slow-motion videos using their own parser or using support library.
- * @see android.media.MediaFormat#KEY_SLOW_MOTION_MARKERS
- * @hide
- */
- @NonNull
- public Builder setSlowMotionSupported(boolean slowMotionSupported) {
- mIsSlowMotionSupported = slowMotionSupported;
- return this;
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
deleted file mode 100644
index 915f3f6a3..0000000
--- a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright 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.media;
-
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This is a copied version of BaseParceledListSlice in framework with hidden API usages
- * removed.
- *
- * Transfer a large list of Parcelable objects across an IPC. Splits into
- * multiple transactions if needed.
- *
- * Caveat: for efficiency and security, all elements must be the same concrete type.
- * In order to avoid writing the class name of each object, we must ensure that
- * each object is the same type, or else unparceling then reparceling the data may yield
- * a different result if the class name encoded in the Parcelable is a Base type.
- * See b/17671747.
- *
- * @hide
- */
-abstract class BaseMediaParceledListSlice<T> implements Parcelable {
- private static String TAG = "BaseMediaParceledListSlice";
- private static final boolean DEBUG = false;
-
- /*
- * TODO get this number from somewhere else. For now set it to a quarter of
- * the 1MB limit.
- */
- // private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
- private static final int MAX_IPC_SIZE = 64 * 1024;
-
- private final List<T> mList;
-
- private int mInlineCountLimit = Integer.MAX_VALUE;
-
- public BaseMediaParceledListSlice(List<T> list) {
- mList = list;
- }
-
- @SuppressWarnings("unchecked")
- BaseMediaParceledListSlice(Parcel p, ClassLoader loader) {
- final int N = p.readInt();
- mList = new ArrayList<T>(N);
- if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
- if (N <= 0) {
- return;
- }
-
- Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
- Class<?> listElementClass = null;
-
- int i = 0;
- while (i < N) {
- if (p.readInt() == 0) {
- break;
- }
-
- final T parcelable = readCreator(creator, p, loader);
- if (listElementClass == null) {
- listElementClass = parcelable.getClass();
- } else {
- verifySameType(listElementClass, parcelable.getClass());
- }
-
- mList.add(parcelable);
-
- if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
- i++;
- }
- if (i >= N) {
- return;
- }
- final IBinder retriever = p.readStrongBinder();
- while (i < N) {
- if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInt(i);
- try {
- retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
- return;
- }
- while (i < N && reply.readInt() != 0) {
- final T parcelable = readCreator(creator, reply, loader);
- verifySameType(listElementClass, parcelable.getClass());
-
- mList.add(parcelable);
-
- if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
- i++;
- }
- reply.recycle();
- data.recycle();
- }
- }
-
- private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
- if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- Parcelable.ClassLoaderCreator<?> classLoaderCreator =
- (Parcelable.ClassLoaderCreator<?>) creator;
- return (T) classLoaderCreator.createFromParcel(p, loader);
- }
- return (T) creator.createFromParcel(p);
- }
-
- private static void verifySameType(final Class<?> expected, final Class<?> actual) {
- if (!actual.equals(expected)) {
- throw new IllegalArgumentException("Can't unparcel type "
- + (actual == null ? null : actual.getName()) + " in list of type "
- + (expected == null ? null : expected.getName()));
- }
- }
-
- public List<T> getList() {
- return mList;
- }
-
- /**
- * Set a limit on the maximum number of entries in the array that will be included
- * inline in the initial parcelling of this object.
- */
- public void setInlineCountLimit(int maxCount) {
- mInlineCountLimit = maxCount;
- }
-
- /**
- * Write this to another Parcel. Note that this discards the internal Parcel
- * and should not be used anymore. This is so we can pass this to a Binder
- * where we won't have a chance to call recycle on this.
- */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- final int N = mList.size();
- final int callFlags = flags;
- dest.writeInt(N);
- if (DEBUG) Log.d(TAG, "Writing " + N + " items");
- if (N > 0) {
- final Class<?> listElementClass = mList.get(0).getClass();
- writeParcelableCreator(mList.get(0), dest);
- int i = 0;
- while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
- dest.writeInt(1);
-
- final T parcelable = mList.get(i);
- verifySameType(listElementClass, parcelable.getClass());
- writeElement(parcelable, dest, callFlags);
-
- if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
- i++;
- }
- if (i < N) {
- dest.writeInt(0);
- Binder retriever = new Binder() {
- @Override
- protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- if (code != FIRST_CALL_TRANSACTION) {
- return super.onTransact(code, data, reply, flags);
- }
- int i = data.readInt();
- if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
- while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
- reply.writeInt(1);
-
- final T parcelable = mList.get(i);
- verifySameType(listElementClass, parcelable.getClass());
- writeElement(parcelable, reply, callFlags);
-
- if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
- i++;
- }
- if (i < N) {
- if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
- reply.writeInt(0);
- }
- return true;
- }
- };
- if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
- dest.writeStrongBinder(retriever);
- }
- }
- }
-
- abstract void writeElement(T parcelable, Parcel reply, int callFlags);
-
- abstract void writeParcelableCreator(T parcelable, Parcel dest);
-
- abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
-}
diff --git a/apex/media/framework/java/android/media/BufferingParams.java b/apex/media/framework/java/android/media/BufferingParams.java
deleted file mode 100644
index 04af028..0000000
--- a/apex/media/framework/java/android/media/BufferingParams.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2017 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.media;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Structure for source buffering management params.
- *
- * Used by {@link MediaPlayer#getBufferingParams()} and
- * {@link MediaPlayer#setBufferingParams(BufferingParams)}
- * to control source buffering behavior.
- *
- * <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
- * (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
- * is playing back source). {@link BufferingParams} includes corresponding marks for each
- * stage of source buffering. The marks are time based (in milliseconds).
- *
- * <p>{@link MediaPlayer} source component has default marks which can be queried by
- * calling {@link MediaPlayer#getBufferingParams()} before any change is made by
- * {@link MediaPlayer#setBufferingParams()}.
- * <ul>
- * <li><strong>initial buffering:</strong> initialMarkMs is used when
- * {@link MediaPlayer} is being prepared. When cached data amount exceeds this mark
- * {@link MediaPlayer} is prepared. </li>
- * <li><strong>rebuffering during playback:</strong> resumePlaybackMarkMs is used when
- * {@link MediaPlayer} is playing back content.
- * <ul>
- * <li> {@link MediaPlayer} has internal mark, namely pausePlaybackMarkMs, to decide when
- * to pause playback if cached data amount runs low. This internal mark varies based on
- * type of data source. </li>
- * <li> When cached data amount exceeds resumePlaybackMarkMs, {@link MediaPlayer} will
- * resume playback if it has been paused due to low cached data amount. The internal mark
- * pausePlaybackMarkMs shall be less than resumePlaybackMarkMs. </li>
- * <li> {@link MediaPlayer} has internal mark, namely pauseRebufferingMarkMs, to decide
- * when to pause rebuffering. Apparently, this internal mark shall be no less than
- * resumePlaybackMarkMs. </li>
- * <li> {@link MediaPlayer} has internal mark, namely resumeRebufferingMarkMs, to decide
- * when to resume buffering. This internal mark varies based on type of data source. This
- * mark shall be larger than pausePlaybackMarkMs, and less than pauseRebufferingMarkMs.
- * </li>
- * </ul> </li>
- * </ul>
- * <p>Users should use {@link Builder} to change {@link BufferingParams}.
- * @hide
- */
-public final class BufferingParams implements Parcelable {
- private static final int BUFFERING_NO_MARK = -1;
-
- // params
- private int mInitialMarkMs = BUFFERING_NO_MARK;
-
- private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
-
- private BufferingParams() {
- }
-
- /**
- * Return initial buffering mark in milliseconds.
- * @return initial buffering mark in milliseconds
- */
- public int getInitialMarkMs() {
- return mInitialMarkMs;
- }
-
- /**
- * Return the mark in milliseconds for resuming playback.
- * @return the mark for resuming playback in milliseconds
- */
- public int getResumePlaybackMarkMs() {
- return mResumePlaybackMarkMs;
- }
-
- /**
- * Builder class for {@link BufferingParams} objects.
- * <p> Here is an example where <code>Builder</code> is used to define the
- * {@link BufferingParams} to be used by a {@link MediaPlayer} instance:
- *
- * <pre class="prettyprint">
- * BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
- * myParams = new BufferingParams.Builder(myParams)
- * .setInitialMarkMs(10000)
- * .setResumePlaybackMarkMs(15000)
- * .build();
- * mediaplayer.setBufferingParams(myParams);
- * </pre>
- */
- public static class Builder {
- private int mInitialMarkMs = BUFFERING_NO_MARK;
- private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
-
- /**
- * Constructs a new Builder with the defaults.
- * By default, all marks are -1.
- */
- public Builder() {
- }
-
- /**
- * Constructs a new Builder from a given {@link BufferingParams} instance
- * @param bp the {@link BufferingParams} object whose data will be reused
- * in the new Builder.
- */
- public Builder(BufferingParams bp) {
- mInitialMarkMs = bp.mInitialMarkMs;
- mResumePlaybackMarkMs = bp.mResumePlaybackMarkMs;
- }
-
- /**
- * Combines all of the fields that have been set and return a new
- * {@link BufferingParams} object. <code>IllegalStateException</code> will be
- * thrown if there is conflict between fields.
- * @return a new {@link BufferingParams} object
- */
- public BufferingParams build() {
- BufferingParams bp = new BufferingParams();
- bp.mInitialMarkMs = mInitialMarkMs;
- bp.mResumePlaybackMarkMs = mResumePlaybackMarkMs;
-
- return bp;
- }
-
- /**
- * Sets the time based mark in milliseconds for initial buffering.
- * @param markMs time based mark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setInitialMarkMs(int markMs) {
- mInitialMarkMs = markMs;
- return this;
- }
-
- /**
- * Sets the time based mark in milliseconds for resuming playback.
- * @param markMs time based mark in milliseconds for resuming playback
- * @return the same Builder instance.
- */
- public Builder setResumePlaybackMarkMs(int markMs) {
- mResumePlaybackMarkMs = markMs;
- return this;
- }
- }
-
- private BufferingParams(Parcel in) {
- mInitialMarkMs = in.readInt();
- mResumePlaybackMarkMs = in.readInt();
- }
-
- public static final @android.annotation.NonNull Parcelable.Creator<BufferingParams> CREATOR =
- new Parcelable.Creator<BufferingParams>() {
- @Override
- public BufferingParams createFromParcel(Parcel in) {
- return new BufferingParams(in);
- }
-
- @Override
- public BufferingParams[] newArray(int size) {
- return new BufferingParams[size];
- }
- };
-
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mInitialMarkMs);
- dest.writeInt(mResumePlaybackMarkMs);
- }
-}
diff --git a/apex/media/framework/java/android/media/Controller2Link.java b/apex/media/framework/java/android/media/Controller2Link.java
deleted file mode 100644
index 8eefec7..0000000
--- a/apex/media/framework/java/android/media/Controller2Link.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-
-import java.util.Objects;
-
-/**
- * Handles incoming commands from {@link MediaSession2} to {@link MediaController2}.
- * @hide
- */
-// @SystemApi
-public final class Controller2Link implements Parcelable {
- private static final String TAG = "Controller2Link";
- private static final boolean DEBUG = MediaController2.DEBUG;
-
- public static final @android.annotation.NonNull Parcelable.Creator<Controller2Link> CREATOR =
- new Parcelable.Creator<Controller2Link>() {
- @Override
- public Controller2Link createFromParcel(Parcel in) {
- return new Controller2Link(in);
- }
-
- @Override
- public Controller2Link[] newArray(int size) {
- return new Controller2Link[size];
- }
- };
-
-
- private final MediaController2 mController;
- private final IMediaController2 mIController;
-
- public Controller2Link(MediaController2 controller) {
- mController = controller;
- mIController = new Controller2Stub();
- }
-
- Controller2Link(Parcel in) {
- mController = null;
- mIController = IMediaController2.Stub.asInterface(in.readStrongBinder());
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mIController.asBinder());
- }
-
- @Override
- public int hashCode() {
- return mIController.asBinder().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Controller2Link)) {
- return false;
- }
- Controller2Link other = (Controller2Link) obj;
- return Objects.equals(mIController.asBinder(), other.mIController.asBinder());
- }
-
- /** Interface method for IMediaController2.notifyConnected */
- public void notifyConnected(int seq, Bundle connectionResult) {
- try {
- mIController.notifyConnected(seq, connectionResult);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaController2.notifyDisonnected */
- public void notifyDisconnected(int seq) {
- try {
- mIController.notifyDisconnected(seq);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaController2.notifyPlaybackActiveChanged */
- public void notifyPlaybackActiveChanged(int seq, boolean playbackActive) {
- try {
- mIController.notifyPlaybackActiveChanged(seq, playbackActive);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaController2.sendSessionCommand */
- public void sendSessionCommand(int seq, Session2Command command, Bundle args,
- ResultReceiver resultReceiver) {
- try {
- mIController.sendSessionCommand(seq, command, args, resultReceiver);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaController2.cancelSessionCommand */
- public void cancelSessionCommand(int seq) {
- try {
- mIController.cancelSessionCommand(seq);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Stub implementation for IMediaController2.notifyConnected */
- public void onConnected(int seq, Bundle connectionResult) {
- if (connectionResult == null) {
- onDisconnected(seq);
- return;
- }
- mController.onConnected(seq, connectionResult);
- }
-
- /** Stub implementation for IMediaController2.notifyDisonnected */
- public void onDisconnected(int seq) {
- mController.onDisconnected(seq);
- }
-
- /** Stub implementation for IMediaController2.notifyPlaybackActiveChanged */
- public void onPlaybackActiveChanged(int seq, boolean playbackActive) {
- mController.onPlaybackActiveChanged(seq, playbackActive);
- }
-
- /** Stub implementation for IMediaController2.sendSessionCommand */
- public void onSessionCommand(int seq, Session2Command command, Bundle args,
- ResultReceiver resultReceiver) {
- mController.onSessionCommand(seq, command, args, resultReceiver);
- }
-
- /** Stub implementation for IMediaController2.cancelSessionCommand */
- public void onCancelCommand(int seq) {
- mController.onCancelCommand(seq);
- }
-
- private class Controller2Stub extends IMediaController2.Stub {
- @Override
- public void notifyConnected(int seq, Bundle connectionResult) {
- final long token = Binder.clearCallingIdentity();
- try {
- Controller2Link.this.onConnected(seq, connectionResult);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void notifyDisconnected(int seq) {
- final long token = Binder.clearCallingIdentity();
- try {
- Controller2Link.this.onDisconnected(seq);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void notifyPlaybackActiveChanged(int seq, boolean playbackActive) {
- final long token = Binder.clearCallingIdentity();
- try {
- Controller2Link.this.onPlaybackActiveChanged(seq, playbackActive);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void sendSessionCommand(int seq, Session2Command command, Bundle args,
- ResultReceiver resultReceiver) {
- final long token = Binder.clearCallingIdentity();
- try {
- Controller2Link.this.onSessionCommand(seq, command, args, resultReceiver);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void cancelSessionCommand(int seq) {
- final long token = Binder.clearCallingIdentity();
- try {
- Controller2Link.this.onCancelCommand(seq);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/DataSourceCallback.java b/apex/media/framework/java/android/media/DataSourceCallback.java
deleted file mode 100644
index c297ecd..0000000
--- a/apex/media/framework/java/android/media/DataSourceCallback.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2017 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.media;
-
-import android.annotation.NonNull;
-
-import java.io.Closeable;
-import java.io.IOException;
-
-/**
- * For supplying media data to the framework. Implement this if your app has
- * special requirements for the way media data is obtained.
- *
- * <p class="note">Methods of this interface may be called on multiple different
- * threads. There will be a thread synchronization point between each call to ensure that
- * modifications to the state of your DataSourceCallback are visible to future calls. This means
- * you don't need to do your own synchronization unless you're modifying the
- * DataSourceCallback from another thread while it's being used by the framework.</p>
- *
- * @hide
- */
-public abstract class DataSourceCallback implements Closeable {
-
- public static final int END_OF_STREAM = -1;
-
- /**
- * Called to request data from the given position.
- *
- * Implementations should should write up to {@code size} bytes into
- * {@code buffer}, and return the number of bytes written.
- *
- * Return {@code 0} if size is zero (thus no bytes are read).
- *
- * Return {@code -1} to indicate that end of stream is reached.
- *
- * @param position the position in the data source to read from.
- * @param buffer the buffer to read the data into.
- * @param offset the offset within buffer to read the data into.
- * @param size the number of bytes to read.
- * @throws IOException on fatal errors.
- * @return the number of bytes read, or {@link #END_OF_STREAM} if end of stream is reached.
- */
- public abstract int readAt(long position, @NonNull byte[] buffer, int offset, int size)
- throws IOException;
-
- /**
- * Called to get the size of the data source.
- *
- * @throws IOException on fatal errors
- * @return the size of data source in bytes, or -1 if the size is unknown.
- */
- public abstract long getSize() throws IOException;
-}
diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java
deleted file mode 100644
index ef5552e..0000000
--- a/apex/media/framework/java/android/media/MediaCommunicationManager.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright 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.media;
-
-import static android.Manifest.permission.MEDIA_CONTENT_CONTROL;
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.os.Build;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.media.MediaBrowserService;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import androidx.annotation.RequiresApi;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.modules.annotation.MinSdk;
-import com.android.modules.utils.build.SdkLevel;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
-
-/**
- * Provides support for interacting with {@link android.media.MediaSession2 MediaSession2s}
- * that applications have published to express their ongoing media playback state.
- */
-@MinSdk(Build.VERSION_CODES.S)
-@RequiresApi(Build.VERSION_CODES.S)
-@SystemService(Context.MEDIA_COMMUNICATION_SERVICE)
-public class MediaCommunicationManager {
- private static final String TAG = "MediaCommunicationManager";
-
- /**
- * The manager version used from beginning.
- */
- private static final int VERSION_1 = 1;
-
- /**
- * Current manager version.
- */
- private static final int CURRENT_VERSION = VERSION_1;
-
- private final Context mContext;
- // Do not access directly use getService().
- private IMediaCommunicationService mService;
-
- private final Object mLock = new Object();
- private final CopyOnWriteArrayList<SessionCallbackRecord> mTokenCallbackRecords =
- new CopyOnWriteArrayList<>();
-
- @GuardedBy("mLock")
- private MediaCommunicationServiceCallbackStub mCallbackStub;
-
- // TODO: remove this when MCS implements dispatchMediaKeyEvent.
- private MediaSessionManager mMediaSessionManager;
-
- /**
- * @hide
- */
- public MediaCommunicationManager(@NonNull Context context) {
- if (!SdkLevel.isAtLeastS()) {
- throw new UnsupportedOperationException("Android version must be S or greater.");
- }
- mContext = context;
- }
-
- /**
- * Gets the version of this {@link MediaCommunicationManager}.
- */
- public @IntRange(from = 1) int getVersion() {
- return CURRENT_VERSION;
- }
-
- /**
- * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
- * created.
- * @param token newly created session2 token
- * @hide
- */
- public void notifySession2Created(@NonNull Session2Token token) {
- Objects.requireNonNull(token, "token shouldn't be null");
- if (token.getType() != Session2Token.TYPE_SESSION) {
- throw new IllegalArgumentException("token's type should be TYPE_SESSION");
- }
- try {
- getService().notifySession2Created(token);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Checks whether the remote user is a trusted app.
- * <p>
- * An app is trusted if the app holds the
- * {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission or has an enabled
- * notification listener.
- *
- * @param userInfo The remote user info from either
- * {@link MediaSession#getCurrentControllerInfo()} or
- * {@link MediaBrowserService#getCurrentBrowserInfo()}.
- * @return {@code true} if the remote user is trusted or {@code false} otherwise.
- * @hide
- */
- public boolean isTrustedForMediaControl(@NonNull MediaSessionManager.RemoteUserInfo userInfo) {
- Objects.requireNonNull(userInfo, "userInfo shouldn't be null");
- if (userInfo.getPackageName() == null) {
- return false;
- }
- try {
- return getService().isTrusted(
- userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid());
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot communicate with the service.", e);
- }
- return false;
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Gets a list of {@link Session2Token} with type {@link Session2Token#TYPE_SESSION} for the
- * current user.
- * <p>
- * Although this API can be used without any restriction, each session owners can accept or
- * reject your uses of {@link MediaSession2}.
- *
- * @return A list of {@link Session2Token}.
- */
- @NonNull
- public List<Session2Token> getSession2Tokens() {
- return getSession2Tokens(UserHandle.myUserId());
- }
-
- /**
- * Adds a callback to be notified when the list of active sessions changes.
- * <p>
- * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be
- * held by the calling app.
- * </p>
- * @hide
- */
- @SystemApi(client = MODULE_LIBRARIES)
- @RequiresPermission(MEDIA_CONTENT_CONTROL)
- public void registerSessionCallback(@CallbackExecutor @NonNull Executor executor,
- @NonNull SessionCallback callback) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(callback, "callback must not be null");
-
- if (!mTokenCallbackRecords.addIfAbsent(
- new SessionCallbackRecord(executor, callback))) {
- Log.w(TAG, "registerSession2TokenCallback: Ignoring the same callback");
- return;
- }
- synchronized (mLock) {
- if (mCallbackStub == null) {
- MediaCommunicationServiceCallbackStub callbackStub =
- new MediaCommunicationServiceCallbackStub();
- try {
- getService().registerCallback(callbackStub, mContext.getPackageName());
- mCallbackStub = callbackStub;
- } catch (RemoteException ex) {
- Log.e(TAG, "Failed to register callback.", ex);
- }
- }
- }
- }
-
- /**
- * Stops receiving active sessions updates on the specified callback.
- * @hide
- */
- @SystemApi(client = MODULE_LIBRARIES)
- public void unregisterSessionCallback(@NonNull SessionCallback callback) {
- if (!mTokenCallbackRecords.remove(
- new SessionCallbackRecord(null, callback))) {
- Log.w(TAG, "unregisterSession2TokenCallback: Ignoring an unknown callback.");
- return;
- }
- synchronized (mLock) {
- if (mCallbackStub != null && mTokenCallbackRecords.isEmpty()) {
- try {
- getService().unregisterCallback(mCallbackStub);
- } catch (RemoteException ex) {
- Log.e(TAG, "Failed to unregister callback.", ex);
- }
- mCallbackStub = null;
- }
- }
- }
-
- private IMediaCommunicationService getService() {
- if (mService == null) {
- mService = IMediaCommunicationService.Stub.asInterface(
- MediaFrameworkInitializer.getMediaServiceManager()
- .getMediaCommunicationServiceRegisterer()
- .get());
- }
- return mService;
- }
-
- // TODO: remove this when MCS implements dispatchMediaKeyEvent.
- private MediaSessionManager getMediaSessionManager() {
- if (mMediaSessionManager == null) {
- mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
- }
- return mMediaSessionManager;
- }
-
- private List<Session2Token> getSession2Tokens(int userId) {
- try {
- MediaParceledListSlice slice = getService().getSession2Tokens(userId);
- return slice == null ? Collections.emptyList() : slice.getList();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get session tokens", e);
- }
- return Collections.emptyList();
- }
-
- /**
- * Sends a media key event. The receiver will be selected automatically.
- *
- * @param keyEvent the key event to send
- * @param asSystemService if {@code true}, the event sent to the session as if it was come from
- * the system service instead of the app process.
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean asSystemService) {
- Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null");
-
- // When MCS handles this, caller is changed.
- // TODO: remove this when MCS implementation is done.
- if (!asSystemService) {
- getMediaSessionManager().dispatchMediaKeyEvent(keyEvent, false);
- return;
- }
-
- try {
- getService().dispatchMediaKeyEvent(mContext.getPackageName(),
- keyEvent, asSystemService);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to send key event.", e);
- }
- }
-
- /**
- * Callback for listening to changes to the sessions.
- * @see #registerSessionCallback(Executor, SessionCallback)
- * @hide
- */
- @SystemApi(client = MODULE_LIBRARIES)
- public interface SessionCallback {
- /**
- * Called when a new {@link MediaSession2 media session2} is created.
- * @param token the newly created token
- */
- default void onSession2TokenCreated(@NonNull Session2Token token) {}
-
- /**
- * Called when {@link #getSession2Tokens() session tokens} are changed.
- */
- default void onSession2TokensChanged(@NonNull List<Session2Token> tokens) {}
- }
-
- private static final class SessionCallbackRecord {
- public final Executor executor;
- public final SessionCallback callback;
-
- SessionCallbackRecord(Executor executor, SessionCallback callback) {
- this.executor = executor;
- this.callback = callback;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(callback);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof SessionCallbackRecord)) {
- return false;
- }
- return Objects.equals(this.callback, ((SessionCallbackRecord) obj).callback);
- }
- }
-
- class MediaCommunicationServiceCallbackStub extends IMediaCommunicationServiceCallback.Stub {
- @Override
- public void onSession2Created(Session2Token token) throws RemoteException {
- for (SessionCallbackRecord record : mTokenCallbackRecords) {
- record.executor.execute(() -> record.callback.onSession2TokenCreated(token));
- }
- }
-
- @Override
- public void onSession2Changed(MediaParceledListSlice tokens) throws RemoteException {
- List<Session2Token> tokenList = tokens.getList();
- for (SessionCallbackRecord record : mTokenCallbackRecords) {
- record.executor.execute(() -> record.callback.onSession2TokensChanged(tokenList));
- }
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaConstants.java b/apex/media/framework/java/android/media/MediaConstants.java
deleted file mode 100644
index ce10889..0000000
--- a/apex/media/framework/java/android/media/MediaConstants.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-class MediaConstants {
- // Bundle key for int
- static final String KEY_PID = "android.media.key.PID";
-
- // Bundle key for String
- static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME";
-
- // Bundle key for Parcelable
- static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK";
- static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS";
- static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE";
- static final String KEY_TOKEN_EXTRAS = "android.media.key.TOKEN_EXTRAS";
- static final String KEY_CONNECTION_HINTS = "android.media.key.CONNECTION_HINTS";
-
- private MediaConstants() {
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java
deleted file mode 100644
index 159e8e5..0000000
--- a/apex/media/framework/java/android/media/MediaController2.java
+++ /dev/null
@@ -1,637 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
-import static android.media.MediaConstants.KEY_CONNECTION_HINTS;
-import static android.media.MediaConstants.KEY_PACKAGE_NAME;
-import static android.media.MediaConstants.KEY_PID;
-import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
-import static android.media.MediaConstants.KEY_TOKEN_EXTRAS;
-import static android.media.Session2Command.Result.RESULT_ERROR_UNKNOWN_ERROR;
-import static android.media.Session2Command.Result.RESULT_INFO_SKIPPED;
-import static android.media.Session2Token.TYPE_SESSION;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.util.concurrent.Executor;
-
-/**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- *
- * Allows an app to interact with an active {@link MediaSession2} or a
- * {@link MediaSession2Service} which would provide {@link MediaSession2}. Media buttons and other
- * commands can be sent to the session.
- */
-public class MediaController2 implements AutoCloseable {
- static final String TAG = "MediaController2";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ControllerCallback mCallback;
-
- private final IBinder.DeathRecipient mDeathRecipient = () -> close();
- private final Context mContext;
- private final Session2Token mSessionToken;
- private final Executor mCallbackExecutor;
- private final Controller2Link mControllerStub;
- private final Handler mResultHandler;
- private final SessionServiceConnection mServiceConnection;
-
- private final Object mLock = new Object();
- //@GuardedBy("mLock")
- private boolean mClosed;
- //@GuardedBy("mLock")
- private int mNextSeqNumber;
- //@GuardedBy("mLock")
- private Session2Link mSessionBinder;
- //@GuardedBy("mLock")
- private Session2CommandGroup mAllowedCommands;
- //@GuardedBy("mLock")
- private Session2Token mConnectedToken;
- //@GuardedBy("mLock")
- private ArrayMap<ResultReceiver, Integer> mPendingCommands;
- //@GuardedBy("mLock")
- private ArraySet<Integer> mRequestedCommandSeqNumbers;
- //@GuardedBy("mLock")
- private boolean mPlaybackActive;
-
- /**
- * Create a {@link MediaController2} from the {@link Session2Token}.
- * This connects to the session and may wake up the service if it's not available.
- *
- * @param context context
- * @param token token to connect to
- * @param connectionHints a session-specific argument to send to the session when connecting.
- * The contents of this bundle may affect the connection result.
- * @param executor executor to run callbacks on.
- * @param callback controller callback to receive changes in.
- */
- MediaController2(@NonNull Context context, @NonNull Session2Token token,
- @NonNull Bundle connectionHints, @NonNull Executor executor,
- @NonNull ControllerCallback callback) {
- if (context == null) {
- throw new IllegalArgumentException("context shouldn't be null");
- }
- if (token == null) {
- throw new IllegalArgumentException("token shouldn't be null");
- }
- mContext = context;
- mSessionToken = token;
- mCallbackExecutor = (executor == null) ? context.getMainExecutor() : executor;
- mCallback = (callback == null) ? new ControllerCallback() {} : callback;
- mControllerStub = new Controller2Link(this);
- // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked.
- mResultHandler = new Handler(context.getMainLooper());
-
- mNextSeqNumber = 0;
- mPendingCommands = new ArrayMap<>();
- mRequestedCommandSeqNumbers = new ArraySet<>();
-
- boolean connectRequested;
- if (token.getType() == TYPE_SESSION) {
- mServiceConnection = null;
- connectRequested = requestConnectToSession(connectionHints);
- } else {
- mServiceConnection = new SessionServiceConnection(connectionHints);
- connectRequested = requestConnectToService();
- }
- if (!connectRequested) {
- close();
- }
- }
-
- @Override
- public void close() {
- synchronized (mLock) {
- if (mClosed) {
- // Already closed. Ignore rest of clean up code.
- // Note: unbindService() throws IllegalArgumentException when it's called twice.
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "closing " + this);
- }
- mClosed = true;
- if (mServiceConnection != null) {
- // Note: This should be called even when the bindService() has returned false.
- mContext.unbindService(mServiceConnection);
- }
- if (mSessionBinder != null) {
- try {
- mSessionBinder.disconnect(mControllerStub, getNextSeqNumber());
- mSessionBinder.unlinkToDeath(mDeathRecipient, 0);
- } catch (RuntimeException e) {
- // No-op
- }
- }
- mConnectedToken = null;
- mPendingCommands.clear();
- mRequestedCommandSeqNumbers.clear();
- mCallbackExecutor.execute(() -> {
- mCallback.onDisconnected(MediaController2.this);
- });
- mSessionBinder = null;
- }
- }
-
- /**
- * Returns {@link Session2Token} of the connected session.
- * If it is not connected yet, it returns {@code null}.
- * <p>
- * This may differ with the {@link Session2Token} from the constructor. For example, if the
- * controller is created with the token for {@link MediaSession2Service}, this would return
- * token for the {@link MediaSession2} in the service.
- *
- * @return Session2Token of the connected session, or {@code null} if not connected
- */
- @Nullable
- public Session2Token getConnectedToken() {
- synchronized (mLock) {
- return mConnectedToken;
- }
- }
-
- /**
- * Returns whether the session's playback is active.
- *
- * @return {@code true} if playback active. {@code false} otherwise.
- * @see ControllerCallback#onPlaybackActiveChanged(MediaController2, boolean)
- */
- public boolean isPlaybackActive() {
- synchronized (mLock) {
- return mPlaybackActive;
- }
- }
-
- /**
- * Sends a session command to the session
- * <p>
- * @param command the session command
- * @param args optional arguments
- * @return a token which will be sent together in {@link ControllerCallback#onCommandResult}
- * when its result is received.
- */
- @NonNull
- public Object sendSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
-
- ResultReceiver resultReceiver = new ResultReceiver(mResultHandler) {
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- synchronized (mLock) {
- mPendingCommands.remove(this);
- }
- mCallbackExecutor.execute(() -> {
- mCallback.onCommandResult(MediaController2.this, this,
- command, new Session2Command.Result(resultCode, resultData));
- });
- }
- };
-
- synchronized (mLock) {
- if (mSessionBinder != null) {
- int seq = getNextSeqNumber();
- mPendingCommands.put(resultReceiver, seq);
- try {
- mSessionBinder.sendSessionCommand(mControllerStub, seq, command, args,
- resultReceiver);
- } catch (RuntimeException e) {
- mPendingCommands.remove(resultReceiver);
- resultReceiver.send(RESULT_ERROR_UNKNOWN_ERROR, null);
- }
- }
- }
- return resultReceiver;
- }
-
- /**
- * Cancels the session command previously sent.
- *
- * @param token the token which is returned from {@link #sendSessionCommand}.
- */
- public void cancelSessionCommand(@NonNull Object token) {
- if (token == null) {
- throw new IllegalArgumentException("token shouldn't be null");
- }
- synchronized (mLock) {
- if (mSessionBinder == null) return;
- Integer seq = mPendingCommands.remove(token);
- if (seq != null) {
- mSessionBinder.cancelSessionCommand(mControllerStub, seq);
- }
- }
- }
-
- // Called by Controller2Link.onConnected
- void onConnected(int seq, Bundle connectionResult) {
- Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK);
- Session2CommandGroup allowedCommands =
- connectionResult.getParcelable(KEY_ALLOWED_COMMANDS);
- boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE);
-
- Bundle tokenExtras = connectionResult.getBundle(KEY_TOKEN_EXTRAS);
- if (tokenExtras == null) {
- Log.w(TAG, "extras shouldn't be null.");
- tokenExtras = Bundle.EMPTY;
- } else if (MediaSession2.hasCustomParcelable(tokenExtras)) {
- Log.w(TAG, "extras contain custom parcelable. Ignoring.");
- tokenExtras = Bundle.EMPTY;
- }
-
- if (DEBUG) {
- Log.d(TAG, "notifyConnected sessionBinder=" + sessionBinder
- + ", allowedCommands=" + allowedCommands);
- }
- if (sessionBinder == null || allowedCommands == null) {
- // Connection rejected.
- close();
- return;
- }
- synchronized (mLock) {
- mSessionBinder = sessionBinder;
- mAllowedCommands = allowedCommands;
- mPlaybackActive = playbackActive;
-
- // Implementation for the local binder is no-op,
- // so can be used without worrying about deadlock.
- sessionBinder.linkToDeath(mDeathRecipient, 0);
- mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION,
- mSessionToken.getPackageName(), sessionBinder, tokenExtras);
- }
- mCallbackExecutor.execute(() -> {
- mCallback.onConnected(MediaController2.this, allowedCommands);
- });
- }
-
- // Called by Controller2Link.onDisconnected
- void onDisconnected(int seq) {
- // close() will call mCallback.onDisconnected
- close();
- }
-
- // Called by Controller2Link.onPlaybackActiveChanged
- void onPlaybackActiveChanged(int seq, boolean playbackActive) {
- synchronized (mLock) {
- mPlaybackActive = playbackActive;
- }
- mCallbackExecutor.execute(() -> {
- mCallback.onPlaybackActiveChanged(MediaController2.this, playbackActive);
- });
- }
-
- // Called by Controller2Link.onSessionCommand
- void onSessionCommand(int seq, Session2Command command, Bundle args,
- @Nullable ResultReceiver resultReceiver) {
- synchronized (mLock) {
- mRequestedCommandSeqNumbers.add(seq);
- }
- mCallbackExecutor.execute(() -> {
- boolean isCanceled;
- synchronized (mLock) {
- isCanceled = !mRequestedCommandSeqNumbers.remove(seq);
- }
- if (isCanceled) {
- if (resultReceiver != null) {
- resultReceiver.send(RESULT_INFO_SKIPPED, null);
- }
- return;
- }
- Session2Command.Result result = mCallback.onSessionCommand(
- MediaController2.this, command, args);
- if (resultReceiver != null) {
- if (result == null) {
- resultReceiver.send(RESULT_INFO_SKIPPED, null);
- } else {
- resultReceiver.send(result.getResultCode(), result.getResultData());
- }
- }
- });
- }
-
- // Called by Controller2Link.onSessionCommand
- void onCancelCommand(int seq) {
- synchronized (mLock) {
- mRequestedCommandSeqNumbers.remove(seq);
- }
- }
-
- private int getNextSeqNumber() {
- synchronized (mLock) {
- return mNextSeqNumber++;
- }
- }
-
- private Bundle createConnectionRequest(@NonNull Bundle connectionHints) {
- Bundle connectionRequest = new Bundle();
- connectionRequest.putString(KEY_PACKAGE_NAME, mContext.getPackageName());
- connectionRequest.putInt(KEY_PID, Process.myPid());
- connectionRequest.putBundle(KEY_CONNECTION_HINTS, connectionHints);
- return connectionRequest;
- }
-
- private boolean requestConnectToSession(@NonNull Bundle connectionHints) {
- Session2Link sessionBinder = mSessionToken.getSessionLink();
- Bundle connectionRequest = createConnectionRequest(connectionHints);
- try {
- sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
- } catch (RuntimeException e) {
- Log.w(TAG, "Failed to call connection request", e);
- return false;
- }
- return true;
- }
-
- private boolean requestConnectToService() {
- // Service. Needs to get fresh binder whenever connection is needed.
- final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
- intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName());
-
- // Use bindService() instead of startForegroundService() to start session service for three
- // reasons.
- // 1. Prevent session service owner's stopSelf() from destroying service.
- // With the startForegroundService(), service's call of stopSelf() will trigger immediate
- // onDestroy() calls on the main thread even when onConnect() is running in another
- // thread.
- // 2. Minimize APIs for developers to take care about.
- // With bindService(), developers only need to take care about Service.onBind()
- // but Service.onStartCommand() should be also taken care about with the
- // startForegroundService().
- // 3. Future support for UI-less playback
- // If a service wants to keep running, it should be either foreground service or
- // bound service. But there had been request for the feature for system apps
- // and using bindService() will be better fit with it.
- synchronized (mLock) {
- boolean result = mContext.bindService(
- intent, mServiceConnection, Context.BIND_AUTO_CREATE);
- if (!result) {
- Log.w(TAG, "bind to " + mSessionToken + " failed");
- return false;
- } else if (DEBUG) {
- Log.d(TAG, "bind to " + mSessionToken + " succeeded");
- }
- }
- return true;
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Builder for {@link MediaController2}.
- * <p>
- * Any incoming event from the {@link MediaSession2} will be handled on the callback
- * executor. If it's not set, {@link Context#getMainExecutor()} will be used by default.
- */
- public static final class Builder {
- private Context mContext;
- private Session2Token mToken;
- private Bundle mConnectionHints;
- private Executor mCallbackExecutor;
- private ControllerCallback mCallback;
-
- /**
- * Creates a builder for {@link MediaController2}.
- *
- * @param context context
- * @param token token of the session to connect to
- */
- public Builder(@NonNull Context context, @NonNull Session2Token token) {
- if (context == null) {
- throw new IllegalArgumentException("context shouldn't be null");
- }
- if (token == null) {
- throw new IllegalArgumentException("token shouldn't be null");
- }
- mContext = context;
- mToken = token;
- }
-
- /**
- * Set the connection hints for the controller.
- * <p>
- * {@code connectionHints} is a session-specific argument to send to the session when
- * connecting. The contents of this bundle may affect the connection result.
- * <p>
- * An {@link IllegalArgumentException} will be thrown if the bundle contains any
- * non-framework Parcelable objects.
- *
- * @param connectionHints a bundle which contains the connection hints
- * @return The Builder to allow chaining
- */
- @NonNull
- public Builder setConnectionHints(@NonNull Bundle connectionHints) {
- if (connectionHints == null) {
- throw new IllegalArgumentException("connectionHints shouldn't be null");
- }
- if (MediaSession2.hasCustomParcelable(connectionHints)) {
- throw new IllegalArgumentException("connectionHints shouldn't contain any custom "
- + "parcelables");
- }
- mConnectionHints = new Bundle(connectionHints);
- return this;
- }
-
- /**
- * Set callback for the controller and its executor.
- *
- * @param executor callback executor
- * @param callback session callback.
- * @return The Builder to allow chaining
- */
- @NonNull
- public Builder setControllerCallback(@NonNull Executor executor,
- @NonNull ControllerCallback callback) {
- if (executor == null) {
- throw new IllegalArgumentException("executor shouldn't be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
- mCallbackExecutor = executor;
- mCallback = callback;
- return this;
- }
-
- /**
- * Build {@link MediaController2}.
- *
- * @return a new controller
- */
- @NonNull
- public MediaController2 build() {
- if (mCallbackExecutor == null) {
- mCallbackExecutor = mContext.getMainExecutor();
- }
- if (mCallback == null) {
- mCallback = new ControllerCallback() {};
- }
- if (mConnectionHints == null) {
- mConnectionHints = Bundle.EMPTY;
- }
- return new MediaController2(
- mContext, mToken, mConnectionHints, mCallbackExecutor, mCallback);
- }
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Interface for listening to change in activeness of the {@link MediaSession2}.
- */
- public abstract static class ControllerCallback {
- /**
- * Called when the controller is successfully connected to the session. The controller
- * becomes available afterwards.
- *
- * @param controller the controller for this event
- * @param allowedCommands commands that's allowed by the session.
- */
- public void onConnected(@NonNull MediaController2 controller,
- @NonNull Session2CommandGroup allowedCommands) {}
-
- /**
- * Called when the session refuses the controller or the controller is disconnected from
- * the session. The controller becomes unavailable afterwards and the callback wouldn't
- * be called.
- * <p>
- * It will be also called after the {@link #close()}, so you can put clean up code here.
- * You don't need to call {@link #close()} after this.
- *
- * @param controller the controller for this event
- */
- public void onDisconnected(@NonNull MediaController2 controller) {}
-
- /**
- * Called when the session's playback activeness is changed.
- *
- * @param controller the controller for this event
- * @param playbackActive {@code true} if the session's playback is active.
- * {@code false} otherwise.
- * @see MediaController2#isPlaybackActive()
- */
- public void onPlaybackActiveChanged(@NonNull MediaController2 controller,
- boolean playbackActive) {}
-
- /**
- * Called when the connected session sent a session command.
- *
- * @param controller the controller for this event
- * @param command the session command
- * @param args optional arguments
- * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
- * will be sent to the session.
- */
- @Nullable
- public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller,
- @NonNull Session2Command command, @Nullable Bundle args) {
- return null;
- }
-
- /**
- * Called when the command sent to the connected session is finished.
- *
- * @param controller the controller for this event
- * @param token the token got from {@link MediaController2#sendSessionCommand}
- * @param command the session command
- * @param result the result of the session command
- */
- public void onCommandResult(@NonNull MediaController2 controller, @NonNull Object token,
- @NonNull Session2Command command, @NonNull Session2Command.Result result) {}
- }
-
- // This will be called on the main thread.
- private class SessionServiceConnection implements ServiceConnection {
- private final Bundle mConnectionHints;
-
- SessionServiceConnection(@Nullable Bundle connectionHints) {
- mConnectionHints = connectionHints;
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- // Note that it's always main-thread.
- boolean connectRequested = false;
- try {
- if (DEBUG) {
- Log.d(TAG, "onServiceConnected " + name + " " + this);
- }
- if (!mSessionToken.getPackageName().equals(name.getPackageName())) {
- Log.wtf(TAG, "Expected connection to " + mSessionToken.getPackageName()
- + " but is connected to " + name);
- return;
- }
- IMediaSession2Service iService = IMediaSession2Service.Stub.asInterface(service);
- if (iService == null) {
- Log.wtf(TAG, "Service interface is missing.");
- return;
- }
- Bundle connectionRequest = createConnectionRequest(mConnectionHints);
- iService.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
- connectRequested = true;
- } catch (RemoteException e) {
- Log.w(TAG, "Service " + name + " has died prematurely", e);
- } finally {
- if (!connectRequested) {
- close();
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- // Temporal lose of the binding because of the service crash. System will automatically
- // rebind, so just no-op.
- if (DEBUG) {
- Log.w(TAG, "Session service " + name + " is disconnected.");
- }
- close();
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- // Permanent lose of the binding because of the service package update or removed.
- // This SessionServiceRecord will be removed accordingly, but forget session binder here
- // for sure.
- close();
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaFeature.java b/apex/media/framework/java/android/media/MediaFeature.java
deleted file mode 100644
index 8d1b159..0000000
--- a/apex/media/framework/java/android/media/MediaFeature.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.media;
-
-import android.annotation.StringDef;
-import android.os.Build;
-
-import com.android.modules.annotation.MinSdk;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * MediaFeature defines various media features, e.g. hdr type.
- */
-@MinSdk(Build.VERSION_CODES.S)
-public final class MediaFeature {
- /**
- * Defines tye type of HDR(high dynamic range) video.
- */
- public static final class HdrType {
- private HdrType() {
- }
-
- /**
- * HDR type for dolby-vision.
- */
- public static final String DOLBY_VISION = "android.media.feature.hdr.dolby_vision";
- /**
- * HDR type for hdr10.
- */
- public static final String HDR10 = "android.media.feature.hdr.hdr10";
- /**
- * HDR type for hdr10+.
- */
- public static final String HDR10_PLUS = "android.media.feature.hdr.hdr10_plus";
- /**
- * HDR type for hlg.
- */
- public static final String HLG = "android.media.feature.hdr.hlg";
- }
-
- /** @hide */
- @StringDef({
- HdrType.DOLBY_VISION,
- HdrType.HDR10,
- HdrType.HDR10_PLUS,
- HdrType.HLG,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface MediaHdrType {
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
deleted file mode 100644
index d7ad97d..0000000
--- a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 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.media;
-
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.annotation.SystemApi.Client;
-import android.app.SystemServiceRegistry;
-import android.content.Context;
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.modules.annotation.MinSdk;
-import com.android.modules.utils.build.SdkLevel;
-
-/**
- * Class for performing registration for all media services on com.android.media apex.
- *
- * @hide
- */
-@MinSdk(Build.VERSION_CODES.S)
-@RequiresApi(Build.VERSION_CODES.S)
-@SystemApi(client = Client.MODULE_LIBRARIES)
-public class MediaFrameworkInitializer {
- private MediaFrameworkInitializer() {
- }
-
- private static volatile MediaServiceManager sMediaServiceManager;
-
- /**
- * Sets an instance of {@link MediaServiceManager} that allows
- * the media mainline module to register/obtain media binder services. This is called
- * by the platform during the system initialization.
- *
- * @param mediaServiceManager instance of {@link MediaServiceManager} that allows
- * the media mainline module to register/obtain media binder services.
- */
- public static void setMediaServiceManager(
- @NonNull MediaServiceManager mediaServiceManager) {
- if (sMediaServiceManager != null) {
- throw new IllegalStateException("setMediaServiceManager called twice!");
- }
-
- if (mediaServiceManager == null) {
- throw new NullPointerException("mediaServiceManager is null!");
- }
-
- sMediaServiceManager = mediaServiceManager;
- }
-
- /** @hide */
- public static MediaServiceManager getMediaServiceManager() {
- return sMediaServiceManager;
- }
-
- /**
- * Called by {@link SystemServiceRegistry}'s static initializer and registers all media
- * services to {@link Context}, so that {@link Context#getSystemService} can return them.
- *
- * @throws IllegalStateException if this is called from anywhere besides
- * {@link SystemServiceRegistry}
- */
- public static void registerServiceWrappers() {
- SystemServiceRegistry.registerContextAwareService(
- Context.MEDIA_TRANSCODING_SERVICE,
- MediaTranscodingManager.class,
- context -> new MediaTranscodingManager(context)
- );
- if (SdkLevel.isAtLeastS()) {
- SystemServiceRegistry.registerContextAwareService(
- Context.MEDIA_COMMUNICATION_SERVICE,
- MediaCommunicationManager.class,
- context -> new MediaCommunicationManager(context)
- );
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaParceledListSlice.java b/apex/media/framework/java/android/media/MediaParceledListSlice.java
deleted file mode 100644
index fb36e80..0000000
--- a/apex/media/framework/java/android/media/MediaParceledListSlice.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 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.media;
-
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This is a copied version of MediaParceledListSlice in framework with hidden API usages removed,
- * and also with some lint error fixed.
- *
- * Transfer a large list of Parcelable objects across an IPC. Splits into
- * multiple transactions if needed.
- *
- * TODO: Remove this from @SystemApi once all the MediaSession related classes are moved
- * to apex (or ParceledListSlice moved to apex). This class is temporaily added to system API
- * for moving classes step by step.
- *
- * @param <T> The type of the elements in the list.
- * @see BaseMediaParceledListSlice
- * @deprecated This is temporary marked as @SystemApi. Should be removed from the API surface.
- * @hide
- */
-@Deprecated
-@RequiresApi(Build.VERSION_CODES.S)
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-public final class MediaParceledListSlice<T extends Parcelable>
- extends BaseMediaParceledListSlice<T> {
- public MediaParceledListSlice(@NonNull List<T> list) {
- super(list);
- }
-
- private MediaParceledListSlice(Parcel in, ClassLoader loader) {
- super(in, loader);
- }
-
- @NonNull
- public static <T extends Parcelable> MediaParceledListSlice<T> emptyList() {
- return new MediaParceledListSlice<T>(Collections.<T> emptyList());
- }
-
- @Override
- public int describeContents() {
- int contents = 0;
- final List<T> list = getList();
- for (int i=0; i<list.size(); i++) {
- contents |= list.get(i).describeContents();
- }
- return contents;
- }
-
- @Override
- void writeElement(T parcelable, Parcel dest, int callFlags) {
- parcelable.writeToParcel(dest, callFlags);
- }
-
- @Override
- void writeParcelableCreator(T parcelable, Parcel dest) {
- dest.writeParcelableCreator((Parcelable) parcelable);
- }
-
- @Override
- Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
- return from.readParcelableCreator(loader);
- }
-
- @NonNull
- @SuppressWarnings("unchecked")
- public static final Parcelable.ClassLoaderCreator<MediaParceledListSlice> CREATOR =
- new Parcelable.ClassLoaderCreator<MediaParceledListSlice>() {
- public MediaParceledListSlice createFromParcel(Parcel in) {
- return new MediaParceledListSlice(in, null);
- }
-
- @Override
- public MediaParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
- return new MediaParceledListSlice(in, loader);
- }
-
- @Override
- public MediaParceledListSlice[] newArray(int size) {
- return new MediaParceledListSlice[size];
- }
- };
-}
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
deleted file mode 100644
index b6f85c7..0000000
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ /dev/null
@@ -1,2293 +0,0 @@
-/*
- * Copyright (C) 2019 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.media;
-
-import android.annotation.CheckResult;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.media.MediaCodec.CryptoInfo;
-import android.media.metrics.LogSessionId;
-import android.os.Build;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.modules.utils.build.SdkLevel;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.ParserException;
-import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
-import com.google.android.exoplayer2.extractor.ChunkIndex;
-import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
-import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
-import com.google.android.exoplayer2.extractor.ExtractorOutput;
-import com.google.android.exoplayer2.extractor.PositionHolder;
-import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints;
-import com.google.android.exoplayer2.extractor.TrackOutput;
-import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
-import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
-import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
-import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
-import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
-import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
-import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
-import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
-import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
-import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
-import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
-import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
-import com.google.android.exoplayer2.extractor.ts.PsExtractor;
-import com.google.android.exoplayer2.extractor.ts.TsExtractor;
-import com.google.android.exoplayer2.extractor.wav.WavExtractor;
-import com.google.android.exoplayer2.upstream.DataReader;
-import com.google.android.exoplayer2.util.ParsableByteArray;
-import com.google.android.exoplayer2.util.TimestampAdjuster;
-import com.google.android.exoplayer2.util.Util;
-import com.google.android.exoplayer2.video.ColorInfo;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.function.Function;
-
-/**
- * Parses media container formats and extracts contained media samples and metadata.
- *
- * <p>This class provides access to a battery of low-level media container parsers. Each instance of
- * this class is associated to a specific media parser implementation which is suitable for
- * extraction from a specific media container format. The media parser implementation assignment
- * depends on the factory method (see {@link #create} and {@link #createByName}) used to create the
- * instance.
- *
- * <p>Users must implement the following to use this class.
- *
- * <ul>
- * <li>{@link InputReader}: Provides the media container's bytes to parse.
- * <li>{@link OutputConsumer}: Provides a sink for all extracted data and metadata.
- * </ul>
- *
- * <p>The following code snippet includes a usage example:
- *
- * <pre>
- * MyOutputConsumer myOutputConsumer = new MyOutputConsumer();
- * MyInputReader myInputReader = new MyInputReader("www.example.com");
- * MediaParser mediaParser = MediaParser.create(myOutputConsumer);
- *
- * while (mediaParser.advance(myInputReader)) {}
- *
- * mediaParser.release();
- * mediaParser = null;
- * </pre>
- *
- * <p>The following code snippet provides a rudimentary {@link OutputConsumer} sample implementation
- * which extracts and publishes all video samples:
- *
- * <pre>
- * class VideoOutputConsumer implements MediaParser.OutputConsumer {
- *
- * private byte[] sampleDataBuffer = new byte[4096];
- * private byte[] discardedDataBuffer = new byte[4096];
- * private int videoTrackIndex = -1;
- * private int bytesWrittenCount = 0;
- *
- * @Override
- * public void onSeekMapFound(int i, @NonNull MediaFormat mediaFormat) {
- * // Do nothing.
- * }
- *
- * @Override
- * public void onTrackDataFound(int i, @NonNull TrackData trackData) {
- * MediaFormat mediaFormat = trackData.mediaFormat;
- * if (videoTrackIndex == -1 &&
- * mediaFormat
- * .getString(MediaFormat.KEY_MIME, /* defaultValue= */ "")
- * .startsWith("video/")) {
- * videoTrackIndex = i;
- * }
- * }
- *
- * @Override
- * public void onSampleDataFound(int trackIndex, @NonNull InputReader inputReader)
- * throws IOException {
- * int numberOfBytesToRead = (int) inputReader.getLength();
- * if (videoTrackIndex != trackIndex) {
- * // Discard contents.
- * inputReader.read(
- * discardedDataBuffer,
- * /* offset= */ 0,
- * Math.min(discardDataBuffer.length, numberOfBytesToRead));
- * } else {
- * ensureSpaceInBuffer(numberOfBytesToRead);
- * int bytesRead = inputReader.read(
- * sampleDataBuffer, bytesWrittenCount, numberOfBytesToRead);
- * bytesWrittenCount += bytesRead;
- * }
- * }
- *
- * @Override
- * public void onSampleCompleted(
- * int trackIndex,
- * long timeMicros,
- * int flags,
- * int size,
- * int offset,
- * @Nullable CryptoInfo cryptoData) {
- * if (videoTrackIndex != trackIndex) {
- * return; // It's not the video track. Ignore.
- * }
- * byte[] sampleData = new byte[size];
- * int sampleStartOffset = bytesWrittenCount - size - offset;
- * System.arraycopy(
- * sampleDataBuffer,
- * sampleStartOffset,
- * sampleData,
- * /* destPos= */ 0,
- * size);
- * // Place trailing bytes at the start of the buffer.
- * System.arraycopy(
- * sampleDataBuffer,
- * bytesWrittenCount - offset,
- * sampleDataBuffer,
- * /* destPos= */ 0,
- * /* size= */ offset);
- * bytesWrittenCount = bytesWrittenCount - offset;
- * publishSample(sampleData, timeMicros, flags);
- * }
- *
- * private void ensureSpaceInBuffer(int numberOfBytesToRead) {
- * int requiredLength = bytesWrittenCount + numberOfBytesToRead;
- * if (requiredLength > sampleDataBuffer.length) {
- * sampleDataBuffer = Arrays.copyOf(sampleDataBuffer, requiredLength);
- * }
- * }
- *
- * }
- *
- * </pre>
- */
-@RequiresApi(Build.VERSION_CODES.R)
-public final class MediaParser {
-
- /**
- * Maps seek positions to {@link SeekPoint SeekPoints} in the stream.
- *
- * <p>A {@link SeekPoint} is a position in the stream from which a player may successfully start
- * playing media samples.
- */
- public static final class SeekMap {
-
- /** Returned by {@link #getDurationMicros()} when the duration is unknown. */
- public static final int UNKNOWN_DURATION = Integer.MIN_VALUE;
-
- /**
- * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link
- * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link
- * SeekPoint#position} is 0.
- *
- * @hide
- */
- public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap());
-
- private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap;
-
- private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
- mExoPlayerSeekMap = exoplayerSeekMap;
- }
-
- /** Returns whether seeking is supported. */
- public boolean isSeekable() {
- return mExoPlayerSeekMap.isSeekable();
- }
-
- /**
- * Returns the duration of the stream in microseconds or {@link #UNKNOWN_DURATION} if the
- * duration is unknown.
- */
- public long getDurationMicros() {
- long durationUs = mExoPlayerSeekMap.getDurationUs();
- return durationUs != C.TIME_UNSET ? durationUs : UNKNOWN_DURATION;
- }
-
- /**
- * Obtains {@link SeekPoint SeekPoints} for the specified seek time in microseconds.
- *
- * <p>{@code getSeekPoints(timeMicros).first} contains the latest seek point for samples
- * with timestamp equal to or smaller than {@code timeMicros}.
- *
- * <p>{@code getSeekPoints(timeMicros).second} contains the earliest seek point for samples
- * with timestamp equal to or greater than {@code timeMicros}. If a seek point exists for
- * {@code timeMicros}, the returned pair will contain the same {@link SeekPoint} twice.
- *
- * @param timeMicros A seek time in microseconds.
- * @return The corresponding {@link SeekPoint SeekPoints}.
- */
- @NonNull
- public Pair<SeekPoint, SeekPoint> getSeekPoints(long timeMicros) {
- SeekPoints seekPoints = mExoPlayerSeekMap.getSeekPoints(timeMicros);
- return new Pair<>(toSeekPoint(seekPoints.first), toSeekPoint(seekPoints.second));
- }
- }
-
- /** Holds information associated with a track. */
- public static final class TrackData {
-
- /** Holds {@link MediaFormat} information for the track. */
- @NonNull public final MediaFormat mediaFormat;
-
- /**
- * Holds {@link DrmInitData} necessary to acquire keys associated with the track, or null if
- * the track has no encryption data.
- */
- @Nullable public final DrmInitData drmInitData;
-
- private TrackData(MediaFormat mediaFormat, DrmInitData drmInitData) {
- this.mediaFormat = mediaFormat;
- this.drmInitData = drmInitData;
- }
- }
-
- /** Defines a seek point in a media stream. */
- public static final class SeekPoint {
-
- /** A {@link SeekPoint} whose time and byte offset are both set to 0. */
- @NonNull public static final SeekPoint START = new SeekPoint(0, 0);
-
- /** The time of the seek point, in microseconds. */
- public final long timeMicros;
-
- /** The byte offset of the seek point. */
- public final long position;
-
- /**
- * @param timeMicros The time of the seek point, in microseconds.
- * @param position The byte offset of the seek point.
- */
- private SeekPoint(long timeMicros, long position) {
- this.timeMicros = timeMicros;
- this.position = position;
- }
-
- @Override
- @NonNull
- public String toString() {
- return "[timeMicros=" + timeMicros + ", position=" + position + "]";
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- SeekPoint other = (SeekPoint) obj;
- return timeMicros == other.timeMicros && position == other.position;
- }
-
- @Override
- public int hashCode() {
- int result = (int) timeMicros;
- result = 31 * result + (int) position;
- return result;
- }
- }
-
- /** Provides input data to {@link MediaParser}. */
- public interface InputReader {
-
- /**
- * Reads up to {@code readLength} bytes of data and stores them into {@code buffer},
- * starting at index {@code offset}.
- *
- * <p>This method blocks until at least one byte is read, the end of input is detected, or
- * an exception is thrown. The read position advances to the first unread byte.
- *
- * @param buffer The buffer into which the read data should be stored.
- * @param offset The start offset into {@code buffer} at which data should be written.
- * @param readLength The maximum number of bytes to read.
- * @return The non-zero number of bytes read, or -1 if no data is available because the end
- * of the input has been reached.
- * @throws java.io.IOException If an error occurs reading from the source.
- */
- int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException;
-
- /** Returns the current read position (byte offset) in the stream. */
- long getPosition();
-
- /** Returns the length of the input in bytes, or -1 if the length is unknown. */
- long getLength();
- }
-
- /** {@link InputReader} that allows setting the read position. */
- public interface SeekableInputReader extends InputReader {
-
- /**
- * Sets the read position at the given {@code position}.
- *
- * <p>{@link #advance} will immediately return after calling this method.
- *
- * @param position The position to seek to, in bytes.
- */
- void seekToPosition(long position);
- }
-
- /** Receives extracted media sample data and metadata from {@link MediaParser}. */
- public interface OutputConsumer {
-
- /**
- * Called when a {@link SeekMap} has been extracted from the stream.
- *
- * <p>This method is called at least once before any samples are {@link #onSampleCompleted
- * complete}. May be called multiple times after that in order to add {@link SeekPoint
- * SeekPoints}.
- *
- * @param seekMap The extracted {@link SeekMap}.
- */
- void onSeekMapFound(@NonNull SeekMap seekMap);
-
- /**
- * Called when the number of tracks is found.
- *
- * @param numberOfTracks The number of tracks in the stream.
- */
- void onTrackCountFound(int numberOfTracks);
-
- /**
- * Called when new {@link TrackData} is found in the stream.
- *
- * @param trackIndex The index of the track for which the {@link TrackData} was extracted.
- * @param trackData The extracted {@link TrackData}.
- */
- void onTrackDataFound(int trackIndex, @NonNull TrackData trackData);
-
- /**
- * Called when sample data is found in the stream.
- *
- * <p>If the invocation of this method returns before the entire {@code inputReader} {@link
- * InputReader#getLength() length} is consumed, the method will be called again for the
- * implementer to read the remaining data. Implementers should surface any thrown {@link
- * IOException} caused by reading from {@code input}.
- *
- * @param trackIndex The index of the track to which the sample data corresponds.
- * @param inputReader The {@link InputReader} from which to read the data.
- * @throws IOException If an exception occurs while reading from {@code inputReader}.
- */
- void onSampleDataFound(int trackIndex, @NonNull InputReader inputReader) throws IOException;
-
- /**
- * Called once all the data of a sample has been passed to {@link #onSampleDataFound}.
- *
- * <p>Includes sample metadata, like presentation timestamp and flags.
- *
- * @param trackIndex The index of the track to which the sample corresponds.
- * @param timeMicros The media timestamp associated with the sample, in microseconds.
- * @param flags Flags associated with the sample. See the {@code SAMPLE_FLAG_*} constants.
- * @param size The size of the sample data, in bytes.
- * @param offset The number of bytes that have been consumed by {@code
- * onSampleDataFound(int, MediaParser.InputReader)} for the specified track, since the
- * last byte belonging to the sample whose metadata is being passed.
- * @param cryptoInfo Encryption data required to decrypt the sample. May be null for
- * unencrypted samples. Implementors should treat any output {@link CryptoInfo}
- * instances as immutable. MediaParser will not modify any output {@code cryptoInfos}
- * and implementors should not modify them either.
- */
- void onSampleCompleted(
- int trackIndex,
- long timeMicros,
- @SampleFlags int flags,
- int size,
- int offset,
- @Nullable CryptoInfo cryptoInfo);
- }
-
- /**
- * Thrown if all parser implementations provided to {@link #create} failed to sniff the input
- * content.
- */
- public static final class UnrecognizedInputFormatException extends IOException {
-
- /**
- * Creates a new instance which signals that the parsers with the given names failed to
- * parse the input.
- */
- @NonNull
- @CheckResult
- private static UnrecognizedInputFormatException createForExtractors(
- @NonNull String... extractorNames) {
- StringBuilder builder = new StringBuilder();
- builder.append("None of the available parsers ( ");
- builder.append(extractorNames[0]);
- for (int i = 1; i < extractorNames.length; i++) {
- builder.append(", ");
- builder.append(extractorNames[i]);
- }
- builder.append(") could read the stream.");
- return new UnrecognizedInputFormatException(builder.toString());
- }
-
- private UnrecognizedInputFormatException(String extractorNames) {
- super(extractorNames);
- }
- }
-
- /** Thrown when an error occurs while parsing a media stream. */
- public static final class ParsingException extends IOException {
-
- private ParsingException(ParserException cause) {
- super(cause);
- }
- }
-
- // Sample flags.
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(
- flag = true,
- value = {
- SAMPLE_FLAG_KEY_FRAME,
- SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA,
- SAMPLE_FLAG_LAST_SAMPLE,
- SAMPLE_FLAG_ENCRYPTED,
- SAMPLE_FLAG_DECODE_ONLY
- })
- public @interface SampleFlags {}
- /** Indicates that the sample holds a synchronization sample. */
- public static final int SAMPLE_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME;
- /**
- * Indicates that the sample has supplemental data.
- *
- * <p>Samples will not have this flag set unless the {@code
- * "android.media.mediaparser.includeSupplementalData"} parameter is set to {@code true} via
- * {@link #setParameter}.
- *
- * <p>Samples with supplemental data have the following sample data format:
- *
- * <ul>
- * <li>If the {@code "android.media.mediaparser.inBandCryptoInfo"} parameter is set, all
- * encryption information.
- * <li>(4 bytes) {@code sample_data_size}: The size of the actual sample data, not including
- * supplemental data or encryption information.
- * <li>({@code sample_data_size} bytes): The media sample data.
- * <li>(remaining bytes) The supplemental data.
- * </ul>
- */
- public static final int SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28;
- /** Indicates that the sample is known to contain the last media sample of the stream. */
- public static final int SAMPLE_FLAG_LAST_SAMPLE = 1 << 29;
- /** Indicates that the sample is (at least partially) encrypted. */
- public static final int SAMPLE_FLAG_ENCRYPTED = 1 << 30;
- /** Indicates that the sample should be decoded but not rendered. */
- public static final int SAMPLE_FLAG_DECODE_ONLY = 1 << 31;
-
- // Parser implementation names.
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @StringDef(
- prefix = {"PARSER_NAME_"},
- value = {
- PARSER_NAME_UNKNOWN,
- PARSER_NAME_MATROSKA,
- PARSER_NAME_FMP4,
- PARSER_NAME_MP4,
- PARSER_NAME_MP3,
- PARSER_NAME_ADTS,
- PARSER_NAME_AC3,
- PARSER_NAME_TS,
- PARSER_NAME_FLV,
- PARSER_NAME_OGG,
- PARSER_NAME_PS,
- PARSER_NAME_WAV,
- PARSER_NAME_AMR,
- PARSER_NAME_AC4,
- PARSER_NAME_FLAC
- })
- public @interface ParserName {}
-
- /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */
- public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN";
- /**
- * Parser for the Matroska container format, as defined in the <a
- * href="https://matroska.org/technical/specs/">spec</a>.
- */
- public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser";
- /**
- * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12.
- */
- public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser";
- /**
- * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC
- * 14496-12.
- */
- public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser";
- /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */
- public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser";
- /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */
- public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser";
- /**
- * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard
- * (AC-3).
- */
- public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser";
- /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */
- public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser";
- /**
- * Parser for the FLV container format, as defined in Adobe Flash Video File Format
- * Specification.
- */
- public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser";
- /** Parser for the OGG container format, as defined in RFC 3533. */
- public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser";
- /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */
- public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser";
- /**
- * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data
- * Specifications.
- */
- public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser";
- /** Parser for the AMR container format, as defined in RFC 4867. */
- public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser";
- /**
- * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for
- * Next-Generation Entertainment Services.
- */
- public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser";
- /**
- * Parser for the FLAC container format, as defined in the <a
- * href="https://xiph.org/flac/">spec</a>.
- */
- public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser";
-
- // MediaParser parameters.
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @StringDef(
- prefix = {"PARAMETER_"},
- value = {
- PARAMETER_ADTS_ENABLE_CBR_SEEKING,
- PARAMETER_AMR_ENABLE_CBR_SEEKING,
- PARAMETER_FLAC_DISABLE_ID3,
- PARAMETER_MP4_IGNORE_EDIT_LISTS,
- PARAMETER_MP4_IGNORE_TFDT_BOX,
- PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES,
- PARAMETER_MATROSKA_DISABLE_CUES_SEEKING,
- PARAMETER_MP3_DISABLE_ID3,
- PARAMETER_MP3_ENABLE_CBR_SEEKING,
- PARAMETER_MP3_ENABLE_INDEX_SEEKING,
- PARAMETER_TS_MODE,
- PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES,
- PARAMETER_TS_IGNORE_AAC_STREAM,
- PARAMETER_TS_IGNORE_AVC_STREAM,
- PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM,
- PARAMETER_TS_DETECT_ACCESS_UNITS,
- PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS,
- PARAMETER_IN_BAND_CRYPTO_INFO,
- PARAMETER_INCLUDE_SUPPLEMENTAL_DATA
- })
- public @interface ParameterName {}
-
- /**
- * Sets whether constant bitrate seeking should be enabled for ADTS parsing. {@code boolean}
- * expected. Default value is {@code false}.
- */
- public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING =
- "android.media.mediaparser.adts.enableCbrSeeking";
- /**
- * Sets whether constant bitrate seeking should be enabled for AMR. {@code boolean} expected.
- * Default value is {@code false}.
- */
- public static final String PARAMETER_AMR_ENABLE_CBR_SEEKING =
- "android.media.mediaparser.amr.enableCbrSeeking";
- /**
- * Sets whether the ID3 track should be disabled for FLAC. {@code boolean} expected. Default
- * value is {@code false}.
- */
- public static final String PARAMETER_FLAC_DISABLE_ID3 =
- "android.media.mediaparser.flac.disableId3";
- /**
- * Sets whether MP4 parsing should ignore edit lists. {@code boolean} expected. Default value is
- * {@code false}.
- */
- public static final String PARAMETER_MP4_IGNORE_EDIT_LISTS =
- "android.media.mediaparser.mp4.ignoreEditLists";
- /**
- * Sets whether MP4 parsing should ignore the tfdt box. {@code boolean} expected. Default value
- * is {@code false}.
- */
- public static final String PARAMETER_MP4_IGNORE_TFDT_BOX =
- "android.media.mediaparser.mp4.ignoreTfdtBox";
- /**
- * Sets whether MP4 parsing should treat all video frames as key frames. {@code boolean}
- * expected. Default value is {@code false}.
- */
- public static final String PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES =
- "android.media.mediaparser.mp4.treatVideoFramesAsKeyframes";
- /**
- * Sets whether Matroska parsing should avoid seeking to the cues element. {@code boolean}
- * expected. Default value is {@code false}.
- *
- * <p>If this flag is enabled and the cues element occurs after the first cluster, then the
- * media is treated as unseekable.
- */
- public static final String PARAMETER_MATROSKA_DISABLE_CUES_SEEKING =
- "android.media.mediaparser.matroska.disableCuesSeeking";
- /**
- * Sets whether the ID3 track should be disabled for MP3. {@code boolean} expected. Default
- * value is {@code false}.
- */
- public static final String PARAMETER_MP3_DISABLE_ID3 =
- "android.media.mediaparser.mp3.disableId3";
- /**
- * Sets whether constant bitrate seeking should be enabled for MP3. {@code boolean} expected.
- * Default value is {@code false}.
- */
- public static final String PARAMETER_MP3_ENABLE_CBR_SEEKING =
- "android.media.mediaparser.mp3.enableCbrSeeking";
- /**
- * Sets whether MP3 parsing should generate a time-to-byte mapping. {@code boolean} expected.
- * Default value is {@code false}.
- *
- * <p>Enabling this flag may require to scan a significant portion of the file to compute a seek
- * point. Therefore, it should only be used if:
- *
- * <ul>
- * <li>the file is small, or
- * <li>the bitrate is variable (or the type of bitrate is unknown) and the seeking metadata
- * provided in the file is not precise enough (or is not present).
- * </ul>
- */
- public static final String PARAMETER_MP3_ENABLE_INDEX_SEEKING =
- "android.media.mediaparser.mp3.enableIndexSeeking";
- /**
- * Sets the operation mode for TS parsing. {@code String} expected. Valid values are {@code
- * "single_pmt"}, {@code "multi_pmt"}, and {@code "hls"}. Default value is {@code "single_pmt"}.
- *
- * <p>The operation modes alter the way TS behaves so that it can handle certain kinds of
- * commonly-occurring malformed media.
- *
- * <ul>
- * <li>{@code "single_pmt"}: Only the first found PMT is parsed. Others are ignored, even if
- * more PMTs are declared in the PAT.
- * <li>{@code "multi_pmt"}: Behave as described in ISO/IEC 13818-1.
- * <li>{@code "hls"}: Enable {@code "single_pmt"} mode, and ignore continuity counters.
- * </ul>
- */
- public static final String PARAMETER_TS_MODE = "android.media.mediaparser.ts.mode";
- /**
- * Sets whether TS should treat samples consisting of non-IDR I slices as synchronization
- * samples (key-frames). {@code boolean} expected. Default value is {@code false}.
- */
- public static final String PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES =
- "android.media.mediaparser.ts.allowNonIdrAvcKeyframes";
- /**
- * Sets whether TS parsing should ignore AAC elementary streams. {@code boolean} expected.
- * Default value is {@code false}.
- */
- public static final String PARAMETER_TS_IGNORE_AAC_STREAM =
- "android.media.mediaparser.ts.ignoreAacStream";
- /**
- * Sets whether TS parsing should ignore AVC elementary streams. {@code boolean} expected.
- * Default value is {@code false}.
- */
- public static final String PARAMETER_TS_IGNORE_AVC_STREAM =
- "android.media.mediaparser.ts.ignoreAvcStream";
- /**
- * Sets whether TS parsing should ignore splice information streams. {@code boolean} expected.
- * Default value is {@code false}.
- */
- public static final String PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM =
- "android.media.mediaparser.ts.ignoreSpliceInfoStream";
- /**
- * Sets whether TS parsing should split AVC stream into access units based on slice headers.
- * {@code boolean} expected. Default value is {@code false}.
- *
- * <p>This flag should be left disabled if the stream contains access units delimiters in order
- * to avoid unnecessary computational costs.
- */
- public static final String PARAMETER_TS_DETECT_ACCESS_UNITS =
- "android.media.mediaparser.ts.ignoreDetectAccessUnits";
- /**
- * Sets whether TS parsing should handle HDMV DTS audio streams. {@code boolean} expected.
- * Default value is {@code false}.
- *
- * <p>Enabling this flag will disable the detection of SCTE subtitles.
- */
- public static final String PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS =
- "android.media.mediaparser.ts.enableHdmvDtsAudioStreams";
- /**
- * Sets whether encryption data should be sent in-band with the sample data, as per {@link
- * OutputConsumer#onSampleDataFound}. {@code boolean} expected. Default value is {@code false}.
- *
- * <p>If this parameter is set, encrypted samples' data will be prefixed with the encryption
- * information bytes. The format for in-band encryption information is:
- *
- * <ul>
- * <li>(1 byte) {@code encryption_signal_byte}: Most significant bit signals whether the
- * encryption data contains subsample encryption data. The remaining bits contain {@code
- * initialization_vector_size}.
- * <li>({@code initialization_vector_size} bytes) Initialization vector.
- * <li>If subsample encryption data is present, as per {@code encryption_signal_byte}, the
- * encryption data also contains:
- * <ul>
- * <li>(2 bytes) {@code subsample_encryption_data_length}.
- * <li>({@code subsample_encryption_data_length * 6} bytes) Subsample encryption data
- * (repeated {@code subsample_encryption_data_length} times):
- * <ul>
- * <li>(2 bytes) Size of a clear section in sample.
- * <li>(4 bytes) Size of an encryption section in sample.
- * </ul>
- * </ul>
- * </ul>
- *
- * @hide
- */
- public static final String PARAMETER_IN_BAND_CRYPTO_INFO =
- "android.media.mediaparser.inBandCryptoInfo";
- /**
- * Sets whether supplemental data should be included as part of the sample data. {@code boolean}
- * expected. Default value is {@code false}. See {@link #SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA} for
- * information about the sample data format.
- *
- * @hide
- */
- public static final String PARAMETER_INCLUDE_SUPPLEMENTAL_DATA =
- "android.media.mediaparser.includeSupplementalData";
- /**
- * Sets whether sample timestamps may start from non-zero offsets. {@code boolean} expected.
- * Default value is {@code false}.
- *
- * <p>When set to true, sample timestamps will not be offset to start from zero, and the media
- * provided timestamps will be used instead. For example, transport stream sample timestamps
- * will not be converted to a zero-based timebase.
- *
- * @hide
- */
- public static final String PARAMETER_IGNORE_TIMESTAMP_OFFSET =
- "android.media.mediaparser.ignoreTimestampOffset";
- /**
- * Sets whether each track type should be eagerly exposed. {@code boolean} expected. Default
- * value is {@code false}.
- *
- * <p>When set to true, each track type will be eagerly exposed through a call to {@link
- * OutputConsumer#onTrackDataFound} containing a single-value {@link MediaFormat}. The key for
- * the track type is {@code "track-type-string"}, and the possible values are {@code "video"},
- * {@code "audio"}, {@code "text"}, {@code "metadata"}, and {@code "unknown"}.
- *
- * @hide
- */
- public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE =
- "android.media.mediaparser.eagerlyExposeTrackType";
- /**
- * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code
- * boolean} expected. Default value is {@code false}.
- *
- * <p>For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single
- * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and
- * whose {@link SeekPoint#position} is 0.
- *
- * @hide
- */
- public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP =
- "android.media.mediaparser.exposeDummySeekMap";
-
- /**
- * Sets whether chunk indices available in the extracted media should be exposed as {@link
- * MediaFormat MediaFormats}. {@code boolean} expected. Default value is {@link false}.
- *
- * <p>When set to true, any information about media segmentation will be exposed as a {@link
- * MediaFormat} (with track index 0) containing four {@link ByteBuffer} elements under the
- * following keys:
- *
- * <ul>
- * <li>"chunk-index-int-sizes": Contains {@code ints} representing the sizes in bytes of each
- * of the media segments.
- * <li>"chunk-index-long-offsets": Contains {@code longs} representing the byte offsets of
- * each segment in the stream.
- * <li>"chunk-index-long-us-durations": Contains {@code longs} representing the media duration
- * of each segment, in microseconds.
- * <li>"chunk-index-long-us-times": Contains {@code longs} representing the start time of each
- * segment, in microseconds.
- * </ul>
- *
- * @hide
- */
- public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT =
- "android.media.mediaParser.exposeChunkIndexAsMediaFormat";
- /**
- * Sets a list of closed-caption {@link MediaFormat MediaFormats} that should be exposed as part
- * of the extracted media. {@code List<MediaFormat>} expected. Default value is an empty list.
- *
- * <p>Expected keys in the {@link MediaFormat} are:
- *
- * <ul>
- * <p>{@link MediaFormat#KEY_MIME}: Determine the type of captions (for example,
- * application/cea-608). Mandatory.
- * <p>{@link MediaFormat#KEY_CAPTION_SERVICE_NUMBER}: Determine the channel on which the
- * captions are transmitted. Optional.
- * </ul>
- *
- * @hide
- */
- public static final String PARAMETER_EXPOSE_CAPTION_FORMATS =
- "android.media.mediaParser.exposeCaptionFormats";
- /**
- * Sets whether the value associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS} should
- * override any in-band caption service declarations. {@code boolean} expected. Default value is
- * {@link false}.
- *
- * <p>When {@code false}, any present in-band caption services information will override the
- * values associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS}.
- *
- * @hide
- */
- public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS =
- "android.media.mediaParser.overrideInBandCaptionDeclarations";
- /**
- * Sets whether a track for EMSG events should be exposed in case of parsing a container that
- * supports them. {@code boolean} expected. Default value is {@link false}.
- *
- * @hide
- */
- public static final String PARAMETER_EXPOSE_EMSG_TRACK =
- "android.media.mediaParser.exposeEmsgTrack";
-
- // Private constants.
-
- private static final String TAG = "MediaParser";
- private static final String JNI_LIBRARY_NAME = "mediaparser-jni";
- private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME;
- private static final Map<String, Class> EXPECTED_TYPE_BY_PARAMETER_NAME;
- private static final String TS_MODE_SINGLE_PMT = "single_pmt";
- private static final String TS_MODE_MULTI_PMT = "multi_pmt";
- private static final String TS_MODE_HLS = "hls";
- private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6;
- private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
- private static final String MEDIAMETRICS_ELEMENT_SEPARATOR = "|";
- private static final int MEDIAMETRICS_MAX_STRING_SIZE = 200;
- private static final int MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH;
- /**
- * Intentional error introduced to reported metrics to prevent identification of the parsed
- * media. Note: Increasing this value may cause older hostside CTS tests to fail.
- */
- private static final float MEDIAMETRICS_DITHER = .02f;
-
- @IntDef(
- value = {
- STATE_READING_SIGNAL_BYTE,
- STATE_READING_INIT_VECTOR,
- STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE,
- STATE_READING_SUBSAMPLE_ENCRYPTION_DATA
- })
- private @interface EncryptionDataReadState {}
-
- private static final int STATE_READING_SIGNAL_BYTE = 0;
- private static final int STATE_READING_INIT_VECTOR = 1;
- private static final int STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE = 2;
- private static final int STATE_READING_SUBSAMPLE_ENCRYPTION_DATA = 3;
-
- // Instance creation methods.
-
- /**
- * Creates an instance backed by the parser with the given {@code name}. The returned instance
- * will attempt parsing without sniffing the content.
- *
- * @param name The name of the parser that will be associated with the created instance.
- * @param outputConsumer The {@link OutputConsumer} to which track data and samples are pushed.
- * @return A new instance.
- * @throws IllegalArgumentException If an invalid name is provided.
- */
- @NonNull
- public static MediaParser createByName(
- @NonNull @ParserName String name, @NonNull OutputConsumer outputConsumer) {
- String[] nameAsArray = new String[] {name};
- assertValidNames(nameAsArray);
- return new MediaParser(outputConsumer, /* createdByName= */ true, name);
- }
-
- /**
- * Creates an instance whose backing parser will be selected by sniffing the content during the
- * first {@link #advance} call. Parser implementations will sniff the content in order of
- * appearance in {@code parserNames}.
- *
- * @param outputConsumer The {@link OutputConsumer} to which extracted data is output.
- * @param parserNames The names of the parsers to sniff the content with. If empty, a default
- * array of names is used.
- * @return A new instance.
- */
- @NonNull
- public static MediaParser create(
- @NonNull OutputConsumer outputConsumer, @NonNull @ParserName String... parserNames) {
- assertValidNames(parserNames);
- if (parserNames.length == 0) {
- parserNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]);
- }
- return new MediaParser(outputConsumer, /* createdByName= */ false, parserNames);
- }
-
- // Misc static methods.
-
- /**
- * Returns an immutable list with the names of the parsers that are suitable for container
- * formats with the given {@link MediaFormat}.
- *
- * <p>A parser supports a {@link MediaFormat} if the mime type associated with {@link
- * MediaFormat#KEY_MIME} corresponds to the supported container format.
- *
- * @param mediaFormat The {@link MediaFormat} to check support for.
- * @return The parser names that support the given {@code mediaFormat}, or the list of all
- * parsers available if no container specific format information is provided.
- */
- @NonNull
- @ParserName
- public static List<String> getParserNames(@NonNull MediaFormat mediaFormat) {
- String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
- mimeType = mimeType == null ? null : Util.toLowerInvariant(mimeType.trim());
- if (TextUtils.isEmpty(mimeType)) {
- // No MIME type provided. Return all.
- return Collections.unmodifiableList(
- new ArrayList<>(EXTRACTOR_FACTORIES_BY_NAME.keySet()));
- }
- ArrayList<String> result = new ArrayList<>();
- switch (mimeType) {
- case "video/x-matroska":
- case "audio/x-matroska":
- case "video/x-webm":
- case "audio/x-webm":
- result.add(PARSER_NAME_MATROSKA);
- break;
- case "video/mp4":
- case "audio/mp4":
- case "application/mp4":
- result.add(PARSER_NAME_MP4);
- result.add(PARSER_NAME_FMP4);
- break;
- case "audio/mpeg":
- result.add(PARSER_NAME_MP3);
- break;
- case "audio/aac":
- result.add(PARSER_NAME_ADTS);
- break;
- case "audio/ac3":
- result.add(PARSER_NAME_AC3);
- break;
- case "video/mp2t":
- case "audio/mp2t":
- result.add(PARSER_NAME_TS);
- break;
- case "video/x-flv":
- result.add(PARSER_NAME_FLV);
- break;
- case "video/ogg":
- case "audio/ogg":
- case "application/ogg":
- result.add(PARSER_NAME_OGG);
- break;
- case "video/mp2p":
- case "video/mp1s":
- result.add(PARSER_NAME_PS);
- break;
- case "audio/vnd.wave":
- case "audio/wav":
- case "audio/wave":
- case "audio/x-wav":
- result.add(PARSER_NAME_WAV);
- break;
- case "audio/amr":
- result.add(PARSER_NAME_AMR);
- break;
- case "audio/ac4":
- result.add(PARSER_NAME_AC4);
- break;
- case "audio/flac":
- case "audio/x-flac":
- result.add(PARSER_NAME_FLAC);
- break;
- default:
- // No parsers support the given mime type. Do nothing.
- break;
- }
- return Collections.unmodifiableList(result);
- }
-
- // Private fields.
-
- private final Map<String, Object> mParserParameters;
- private final OutputConsumer mOutputConsumer;
- private final String[] mParserNamesPool;
- private final PositionHolder mPositionHolder;
- private final InputReadingDataReader mExoDataReader;
- private final DataReaderAdapter mScratchDataReaderAdapter;
- private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter;
- @Nullable private final Constructor<DrmInitData.SchemeInitData> mSchemeInitDataConstructor;
- private final ArrayList<Format> mMuxedCaptionFormats;
- private boolean mInBandCryptoInfo;
- private boolean mIncludeSupplementalData;
- private boolean mIgnoreTimestampOffset;
- private boolean mEagerlyExposeTrackType;
- private boolean mExposeDummySeekMap;
- private boolean mExposeChunkIndexAsMediaFormat;
- private String mParserName;
- private Extractor mExtractor;
- private ExtractorInput mExtractorInput;
- private boolean mPendingExtractorInit;
- private long mPendingSeekPosition;
- private long mPendingSeekTimeMicros;
- private boolean mLoggedSchemeInitDataCreationException;
- private boolean mReleased;
-
- // MediaMetrics fields.
- @Nullable private LogSessionId mLogSessionId;
- private final boolean mCreatedByName;
- private final SparseArray<Format> mTrackFormats;
- private String mLastObservedExceptionName;
- private long mDurationMillis;
- private long mResourceByteCount;
-
- // Public methods.
-
- /**
- * Sets parser-specific parameters which allow customizing behavior.
- *
- * <p>Must be called before the first call to {@link #advance}.
- *
- * @param parameterName The name of the parameter to set. See {@code PARAMETER_*} constants for
- * documentation on possible values.
- * @param value The value to set for the given {@code parameterName}. See {@code PARAMETER_*}
- * constants for documentation on the expected types.
- * @return This instance, for convenience.
- * @throws IllegalStateException If called after calling {@link #advance} on the same instance.
- */
- @NonNull
- public MediaParser setParameter(
- @NonNull @ParameterName String parameterName, @NonNull Object value) {
- if (mExtractor != null) {
- throw new IllegalStateException(
- "setParameters() must be called before the first advance() call.");
- }
- Class expectedType = EXPECTED_TYPE_BY_PARAMETER_NAME.get(parameterName);
- // Ignore parameter names that are not contained in the map, in case the client is passing
- // a parameter that is being added in a future version of this library.
- if (expectedType != null && !expectedType.isInstance(value)) {
- throw new IllegalArgumentException(
- parameterName
- + " expects a "
- + expectedType.getSimpleName()
- + " but a "
- + value.getClass().getSimpleName()
- + " was passed.");
- }
- if (PARAMETER_TS_MODE.equals(parameterName)
- && !TS_MODE_SINGLE_PMT.equals(value)
- && !TS_MODE_HLS.equals(value)
- && !TS_MODE_MULTI_PMT.equals(value)) {
- throw new IllegalArgumentException(PARAMETER_TS_MODE + " does not accept: " + value);
- }
- if (PARAMETER_IN_BAND_CRYPTO_INFO.equals(parameterName)) {
- mInBandCryptoInfo = (boolean) value;
- }
- if (PARAMETER_INCLUDE_SUPPLEMENTAL_DATA.equals(parameterName)) {
- mIncludeSupplementalData = (boolean) value;
- }
- if (PARAMETER_IGNORE_TIMESTAMP_OFFSET.equals(parameterName)) {
- mIgnoreTimestampOffset = (boolean) value;
- }
- if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) {
- mEagerlyExposeTrackType = (boolean) value;
- }
- if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) {
- mExposeDummySeekMap = (boolean) value;
- }
- if (PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT.equals(parameterName)) {
- mExposeChunkIndexAsMediaFormat = (boolean) value;
- }
- if (PARAMETER_EXPOSE_CAPTION_FORMATS.equals(parameterName)) {
- setMuxedCaptionFormats((List<MediaFormat>) value);
- }
- mParserParameters.put(parameterName, value);
- return this;
- }
-
- /**
- * Returns whether the given {@code parameterName} is supported by this parser.
- *
- * @param parameterName The parameter name to check support for. One of the {@code PARAMETER_*}
- * constants.
- * @return Whether the given {@code parameterName} is supported.
- */
- public boolean supportsParameter(@NonNull @ParameterName String parameterName) {
- return EXPECTED_TYPE_BY_PARAMETER_NAME.containsKey(parameterName);
- }
-
- /**
- * Returns the name of the backing parser implementation.
- *
- * <p>If this instance was creating using {@link #createByName}, the provided name is returned.
- * If this instance was created using {@link #create}, this method will return {@link
- * #PARSER_NAME_UNKNOWN} until the first call to {@link #advance}, after which the name of the
- * backing parser implementation is returned.
- *
- * @return The name of the backing parser implementation, or null if the backing parser
- * implementation has not yet been selected.
- */
- @NonNull
- @ParserName
- public String getParserName() {
- return mParserName;
- }
-
- /**
- * Makes progress in the extraction of the input media stream, unless the end of the input has
- * been reached.
- *
- * <p>This method will block until some progress has been made.
- *
- * <p>If this instance was created using {@link #create}, the first call to this method will
- * sniff the content using the selected parser implementations.
- *
- * @param seekableInputReader The {@link SeekableInputReader} from which to obtain the media
- * container data.
- * @return Whether there is any data left to extract. Returns false if the end of input has been
- * reached.
- * @throws IOException If an error occurs while reading from the {@link SeekableInputReader}.
- * @throws UnrecognizedInputFormatException If the format cannot be recognized by any of the
- * underlying parser implementations.
- */
- public boolean advance(@NonNull SeekableInputReader seekableInputReader) throws IOException {
- if (mExtractorInput == null) {
- // TODO: For efficiency, the same implementation should be used, by providing a
- // clearBuffers() method, or similar.
- long resourceLength = seekableInputReader.getLength();
- if (mResourceByteCount == 0) {
- // For resource byte count metric collection, we only take into account the length
- // of the first provided input reader.
- mResourceByteCount = resourceLength;
- }
- mExtractorInput =
- new DefaultExtractorInput(
- mExoDataReader, seekableInputReader.getPosition(), resourceLength);
- }
- mExoDataReader.mInputReader = seekableInputReader;
-
- if (mExtractor == null) {
- mPendingExtractorInit = true;
- if (!mParserName.equals(PARSER_NAME_UNKNOWN)) {
- mExtractor = createExtractor(mParserName);
- } else {
- for (String parserName : mParserNamesPool) {
- Extractor extractor = createExtractor(parserName);
- try {
- if (extractor.sniff(mExtractorInput)) {
- mParserName = parserName;
- mExtractor = extractor;
- mPendingExtractorInit = true;
- break;
- }
- } catch (EOFException e) {
- // Do nothing.
- } finally {
- mExtractorInput.resetPeekPosition();
- }
- }
- if (mExtractor == null) {
- UnrecognizedInputFormatException exception =
- UnrecognizedInputFormatException.createForExtractors(mParserNamesPool);
- mLastObservedExceptionName = exception.getClass().getName();
- throw exception;
- }
- return true;
- }
- }
-
- if (mPendingExtractorInit) {
- if (mExposeDummySeekMap) {
- // We propagate the dummy seek map before initializing the extractor, in case the
- // extractor initialization outputs a seek map.
- mOutputConsumer.onSeekMapFound(SeekMap.DUMMY);
- }
- mExtractor.init(new ExtractorOutputAdapter());
- mPendingExtractorInit = false;
- // We return after initialization to allow clients use any output information before
- // starting actual extraction.
- return true;
- }
-
- if (isPendingSeek()) {
- mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros);
- removePendingSeek();
- }
-
- mPositionHolder.position = seekableInputReader.getPosition();
- int result;
- try {
- result = mExtractor.read(mExtractorInput, mPositionHolder);
- } catch (Exception e) {
- mLastObservedExceptionName = e.getClass().getName();
- if (e instanceof ParserException) {
- throw new ParsingException((ParserException) e);
- } else {
- throw e;
- }
- }
- if (result == Extractor.RESULT_END_OF_INPUT) {
- mExtractorInput = null;
- return false;
- }
- if (result == Extractor.RESULT_SEEK) {
- mExtractorInput = null;
- seekableInputReader.seekToPosition(mPositionHolder.position);
- }
- return true;
- }
-
- /**
- * Seeks within the media container being extracted.
- *
- * <p>{@link SeekPoint SeekPoints} can be obtained from the {@link SeekMap} passed to {@link
- * OutputConsumer#onSeekMapFound(SeekMap)}.
- *
- * <p>Following a call to this method, the {@link InputReader} passed to the next invocation of
- * {@link #advance} must provide data starting from {@link SeekPoint#position} in the stream.
- *
- * @param seekPoint The {@link SeekPoint} to seek to.
- */
- public void seek(@NonNull SeekPoint seekPoint) {
- if (mExtractor == null) {
- mPendingSeekPosition = seekPoint.position;
- mPendingSeekTimeMicros = seekPoint.timeMicros;
- } else {
- mExtractor.seek(seekPoint.position, seekPoint.timeMicros);
- }
- }
-
- /**
- * Releases any acquired resources.
- *
- * <p>After calling this method, this instance becomes unusable and no other methods should be
- * invoked.
- */
- public void release() {
- mExtractorInput = null;
- mExtractor = null;
- if (mReleased) {
- // Nothing to do.
- return;
- }
- mReleased = true;
-
- String trackMimeTypes = buildMediaMetricsString(format -> format.sampleMimeType);
- String trackCodecs = buildMediaMetricsString(format -> format.codecs);
- int videoWidth = -1;
- int videoHeight = -1;
- for (int i = 0; i < mTrackFormats.size(); i++) {
- Format format = mTrackFormats.valueAt(i);
- if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) {
- videoWidth = format.width;
- videoHeight = format.height;
- break;
- }
- }
-
- String alteredParameters =
- String.join(
- MEDIAMETRICS_ELEMENT_SEPARATOR,
- mParserParameters.keySet().toArray(new String[0]));
- alteredParameters =
- alteredParameters.substring(
- 0,
- Math.min(
- alteredParameters.length(),
- MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
-
- nativeSubmitMetrics(
- SdkLevel.isAtLeastS() ? getLogSessionIdStringV31() : "",
- mParserName,
- mCreatedByName,
- String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
- mLastObservedExceptionName,
- addDither(mResourceByteCount),
- addDither(mDurationMillis),
- trackMimeTypes,
- trackCodecs,
- alteredParameters,
- videoWidth,
- videoHeight);
- }
-
- @RequiresApi(31)
- public void setLogSessionId(@NonNull LogSessionId logSessionId) {
- this.mLogSessionId = Objects.requireNonNull(logSessionId);
- }
-
- @RequiresApi(31)
- @NonNull
- public LogSessionId getLogSessionId() {
- return mLogSessionId != null ? mLogSessionId : LogSessionId.LOG_SESSION_ID_NONE;
- }
-
- // Private methods.
-
- private MediaParser(
- OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
- throw new UnsupportedOperationException("Android version must be R or greater.");
- }
- mParserParameters = new HashMap<>();
- mOutputConsumer = outputConsumer;
- mParserNamesPool = parserNamesPool;
- mCreatedByName = createdByName;
- mParserName = createdByName ? parserNamesPool[0] : PARSER_NAME_UNKNOWN;
- mPositionHolder = new PositionHolder();
- mExoDataReader = new InputReadingDataReader();
- removePendingSeek();
- mScratchDataReaderAdapter = new DataReaderAdapter();
- mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter();
- mSchemeInitDataConstructor = getSchemeInitDataConstructor();
- mMuxedCaptionFormats = new ArrayList<>();
-
- // MediaMetrics.
- mTrackFormats = new SparseArray<>();
- mLastObservedExceptionName = "";
- mDurationMillis = -1;
- }
-
- private String buildMediaMetricsString(Function<Format, String> formatFieldGetter) {
- StringBuilder stringBuilder = new StringBuilder();
- for (int i = 0; i < mTrackFormats.size(); i++) {
- if (i > 0) {
- stringBuilder.append(MEDIAMETRICS_ELEMENT_SEPARATOR);
- }
- String fieldValue = formatFieldGetter.apply(mTrackFormats.valueAt(i));
- stringBuilder.append(fieldValue != null ? fieldValue : "");
- }
- return stringBuilder.substring(
- 0, Math.min(stringBuilder.length(), MEDIAMETRICS_MAX_STRING_SIZE));
- }
-
- private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) {
- mMuxedCaptionFormats.clear();
- for (MediaFormat mediaFormat : mediaFormats) {
- mMuxedCaptionFormats.add(toExoPlayerCaptionFormat(mediaFormat));
- }
- }
-
- private boolean isPendingSeek() {
- return mPendingSeekPosition >= 0;
- }
-
- private void removePendingSeek() {
- mPendingSeekPosition = -1;
- mPendingSeekTimeMicros = -1;
- }
-
- private Extractor createExtractor(String parserName) {
- int flags = 0;
- TimestampAdjuster timestampAdjuster = null;
- if (mIgnoreTimestampOffset) {
- timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.DO_NOT_OFFSET);
- }
- switch (parserName) {
- case PARSER_NAME_MATROSKA:
- flags =
- getBooleanParameter(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING)
- ? MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES
- : 0;
- return new MatroskaExtractor(flags);
- case PARSER_NAME_FMP4:
- flags |=
- getBooleanParameter(PARAMETER_EXPOSE_EMSG_TRACK)
- ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS)
- ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_MP4_IGNORE_TFDT_BOX)
- ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES)
- ? FragmentedMp4Extractor
- .FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
- : 0;
- return new FragmentedMp4Extractor(
- flags,
- timestampAdjuster,
- /* sideloadedTrack= */ null,
- mMuxedCaptionFormats);
- case PARSER_NAME_MP4:
- flags |=
- getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS)
- ? Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS
- : 0;
- return new Mp4Extractor(flags);
- case PARSER_NAME_MP3:
- flags |=
- getBooleanParameter(PARAMETER_MP3_DISABLE_ID3)
- ? Mp3Extractor.FLAG_DISABLE_ID3_METADATA
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_MP3_ENABLE_CBR_SEEKING)
- ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
- : 0;
- // TODO: Add index seeking once we update the ExoPlayer version.
- return new Mp3Extractor(flags);
- case PARSER_NAME_ADTS:
- flags |=
- getBooleanParameter(PARAMETER_ADTS_ENABLE_CBR_SEEKING)
- ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
- : 0;
- return new AdtsExtractor(flags);
- case PARSER_NAME_AC3:
- return new Ac3Extractor();
- case PARSER_NAME_TS:
- flags |=
- getBooleanParameter(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES)
- ? DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_TS_DETECT_ACCESS_UNITS)
- ? DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS)
- ? DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_TS_IGNORE_AAC_STREAM)
- ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_TS_IGNORE_AVC_STREAM)
- ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM)
- ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM
- : 0;
- flags |=
- getBooleanParameter(PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS)
- ? DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS
- : 0;
- String tsMode = getStringParameter(PARAMETER_TS_MODE, TS_MODE_SINGLE_PMT);
- int hlsMode =
- TS_MODE_SINGLE_PMT.equals(tsMode)
- ? TsExtractor.MODE_SINGLE_PMT
- : TS_MODE_HLS.equals(tsMode)
- ? TsExtractor.MODE_HLS
- : TsExtractor.MODE_MULTI_PMT;
- return new TsExtractor(
- hlsMode,
- timestampAdjuster != null
- ? timestampAdjuster
- : new TimestampAdjuster(/* firstSampleTimestampUs= */ 0),
- new DefaultTsPayloadReaderFactory(flags, mMuxedCaptionFormats));
- case PARSER_NAME_FLV:
- return new FlvExtractor();
- case PARSER_NAME_OGG:
- return new OggExtractor();
- case PARSER_NAME_PS:
- return new PsExtractor();
- case PARSER_NAME_WAV:
- return new WavExtractor();
- case PARSER_NAME_AMR:
- flags |=
- getBooleanParameter(PARAMETER_AMR_ENABLE_CBR_SEEKING)
- ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
- : 0;
- return new AmrExtractor(flags);
- case PARSER_NAME_AC4:
- return new Ac4Extractor();
- case PARSER_NAME_FLAC:
- flags |=
- getBooleanParameter(PARAMETER_FLAC_DISABLE_ID3)
- ? FlacExtractor.FLAG_DISABLE_ID3_METADATA
- : 0;
- return new FlacExtractor(flags);
- default:
- // Should never happen.
- throw new IllegalStateException("Unexpected attempt to create: " + parserName);
- }
- }
-
- private boolean getBooleanParameter(String name) {
- return (boolean) mParserParameters.getOrDefault(name, false);
- }
-
- private String getStringParameter(String name, String defaultValue) {
- return (String) mParserParameters.getOrDefault(name, defaultValue);
- }
-
- @RequiresApi(31)
- private String getLogSessionIdStringV31() {
- return mLogSessionId != null ? mLogSessionId.getStringId() : "";
- }
-
- // Private classes.
-
- private static final class InputReadingDataReader implements DataReader {
-
- public InputReader mInputReader;
-
- @Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
- return mInputReader.read(buffer, offset, readLength);
- }
- }
-
- private final class MediaParserDrmInitData extends DrmInitData {
-
- private final SchemeInitData[] mSchemeDatas;
-
- private MediaParserDrmInitData(com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData)
- throws IllegalAccessException, InstantiationException, InvocationTargetException {
- mSchemeDatas = new SchemeInitData[exoDrmInitData.schemeDataCount];
- for (int i = 0; i < mSchemeDatas.length; i++) {
- mSchemeDatas[i] = toFrameworkSchemeInitData(exoDrmInitData.get(i));
- }
- }
-
- @Override
- @Nullable
- public SchemeInitData get(UUID schemeUuid) {
- for (SchemeInitData schemeInitData : mSchemeDatas) {
- if (schemeInitData.uuid.equals(schemeUuid)) {
- return schemeInitData;
- }
- }
- return null;
- }
-
- @Override
- public SchemeInitData getSchemeInitDataAt(int index) {
- return mSchemeDatas[index];
- }
-
- @Override
- public int getSchemeInitDataCount() {
- return mSchemeDatas.length;
- }
-
- private DrmInitData.SchemeInitData toFrameworkSchemeInitData(SchemeData exoSchemeData)
- throws IllegalAccessException, InvocationTargetException, InstantiationException {
- return mSchemeInitDataConstructor.newInstance(
- exoSchemeData.uuid, exoSchemeData.mimeType, exoSchemeData.data);
- }
- }
-
- private final class ExtractorOutputAdapter implements ExtractorOutput {
-
- private final SparseArray<TrackOutput> mTrackOutputAdapters;
- private boolean mTracksEnded;
-
- private ExtractorOutputAdapter() {
- mTrackOutputAdapters = new SparseArray<>();
- }
-
- @Override
- public TrackOutput track(int id, int type) {
- TrackOutput trackOutput = mTrackOutputAdapters.get(id);
- if (trackOutput == null) {
- int trackIndex = mTrackOutputAdapters.size();
- trackOutput = new TrackOutputAdapter(trackIndex);
- mTrackOutputAdapters.put(id, trackOutput);
- if (mEagerlyExposeTrackType) {
- MediaFormat mediaFormat = new MediaFormat();
- mediaFormat.setString("track-type-string", toTypeString(type));
- mOutputConsumer.onTrackDataFound(
- trackIndex, new TrackData(mediaFormat, /* drmInitData= */ null));
- }
- }
- return trackOutput;
- }
-
- @Override
- public void endTracks() {
- mOutputConsumer.onTrackCountFound(mTrackOutputAdapters.size());
- }
-
- @Override
- public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
- long durationUs = exoplayerSeekMap.getDurationUs();
- if (durationUs != C.TIME_UNSET) {
- mDurationMillis = C.usToMs(durationUs);
- }
- if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) {
- ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap;
- MediaFormat mediaFormat = new MediaFormat();
- mediaFormat.setByteBuffer("chunk-index-int-sizes", toByteBuffer(chunkIndex.sizes));
- mediaFormat.setByteBuffer(
- "chunk-index-long-offsets", toByteBuffer(chunkIndex.offsets));
- mediaFormat.setByteBuffer(
- "chunk-index-long-us-durations", toByteBuffer(chunkIndex.durationsUs));
- mediaFormat.setByteBuffer(
- "chunk-index-long-us-times", toByteBuffer(chunkIndex.timesUs));
- mOutputConsumer.onTrackDataFound(
- /* trackIndex= */ 0, new TrackData(mediaFormat, /* drmInitData= */ null));
- }
- mOutputConsumer.onSeekMapFound(new SeekMap(exoplayerSeekMap));
- }
- }
-
- private class TrackOutputAdapter implements TrackOutput {
-
- private final int mTrackIndex;
-
- private CryptoInfo mLastOutputCryptoInfo;
- private CryptoInfo.Pattern mLastOutputEncryptionPattern;
- private CryptoData mLastReceivedCryptoData;
-
- @EncryptionDataReadState private int mEncryptionDataReadState;
- private int mEncryptionDataSizeToSubtractFromSampleDataSize;
- private int mEncryptionVectorSize;
- private byte[] mScratchIvSpace;
- private int mSubsampleEncryptionDataSize;
- private int[] mScratchSubsampleEncryptedBytesCount;
- private int[] mScratchSubsampleClearBytesCount;
- private boolean mHasSubsampleEncryptionData;
- private int mSkippedSupplementalDataBytes;
-
- private TrackOutputAdapter(int trackIndex) {
- mTrackIndex = trackIndex;
- mScratchIvSpace = new byte[16]; // Size documented in CryptoInfo.
- mScratchSubsampleEncryptedBytesCount = new int[32];
- mScratchSubsampleClearBytesCount = new int[32];
- mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
- mLastOutputEncryptionPattern =
- new CryptoInfo.Pattern(/* blocksToEncrypt= */ 0, /* blocksToSkip= */ 0);
- }
-
- @Override
- public void format(Format format) {
- mTrackFormats.put(mTrackIndex, format);
- mOutputConsumer.onTrackDataFound(
- mTrackIndex,
- new TrackData(
- toMediaFormat(format), toFrameworkDrmInitData(format.drmInitData)));
- }
-
- @Override
- public int sampleData(
- DataReader input,
- int length,
- boolean allowEndOfInput,
- @SampleDataPart int sampleDataPart)
- throws IOException {
- mScratchDataReaderAdapter.setDataReader(input, length);
- long positionBeforeReading = mScratchDataReaderAdapter.getPosition();
- mOutputConsumer.onSampleDataFound(mTrackIndex, mScratchDataReaderAdapter);
- return (int) (mScratchDataReaderAdapter.getPosition() - positionBeforeReading);
- }
-
- @Override
- public void sampleData(
- ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) {
- if (sampleDataPart == SAMPLE_DATA_PART_ENCRYPTION && !mInBandCryptoInfo) {
- while (length > 0) {
- switch (mEncryptionDataReadState) {
- case STATE_READING_SIGNAL_BYTE:
- int encryptionSignalByte = data.readUnsignedByte();
- length--;
- mHasSubsampleEncryptionData = ((encryptionSignalByte >> 7) & 1) != 0;
- mEncryptionVectorSize = encryptionSignalByte & 0x7F;
- mEncryptionDataSizeToSubtractFromSampleDataSize =
- mEncryptionVectorSize + 1; // Signal byte.
- mEncryptionDataReadState = STATE_READING_INIT_VECTOR;
- break;
- case STATE_READING_INIT_VECTOR:
- Arrays.fill(mScratchIvSpace, (byte) 0); // Ensure 0-padding.
- data.readBytes(mScratchIvSpace, /* offset= */ 0, mEncryptionVectorSize);
- length -= mEncryptionVectorSize;
- if (mHasSubsampleEncryptionData) {
- mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE;
- } else {
- mSubsampleEncryptionDataSize = 0;
- mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
- }
- break;
- case STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE:
- mSubsampleEncryptionDataSize = data.readUnsignedShort();
- if (mScratchSubsampleClearBytesCount.length
- < mSubsampleEncryptionDataSize) {
- mScratchSubsampleClearBytesCount =
- new int[mSubsampleEncryptionDataSize];
- mScratchSubsampleEncryptedBytesCount =
- new int[mSubsampleEncryptionDataSize];
- }
- length -= 2;
- mEncryptionDataSizeToSubtractFromSampleDataSize +=
- 2
- + mSubsampleEncryptionDataSize
- * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY;
- mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_DATA;
- break;
- case STATE_READING_SUBSAMPLE_ENCRYPTION_DATA:
- for (int i = 0; i < mSubsampleEncryptionDataSize; i++) {
- mScratchSubsampleClearBytesCount[i] = data.readUnsignedShort();
- mScratchSubsampleEncryptedBytesCount[i] = data.readInt();
- }
- length -=
- mSubsampleEncryptionDataSize
- * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY;
- mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
- if (length != 0) {
- throw new IllegalStateException();
- }
- break;
- default:
- // Never happens.
- throw new IllegalStateException();
- }
- }
- } else if (sampleDataPart == SAMPLE_DATA_PART_SUPPLEMENTAL
- && !mIncludeSupplementalData) {
- mSkippedSupplementalDataBytes += length;
- data.skipBytes(length);
- } else {
- outputSampleData(data, length);
- }
- }
-
- @Override
- public void sampleMetadata(
- long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData) {
- size -= mSkippedSupplementalDataBytes;
- mSkippedSupplementalDataBytes = 0;
- mOutputConsumer.onSampleCompleted(
- mTrackIndex,
- timeUs,
- getMediaParserFlags(flags),
- size - mEncryptionDataSizeToSubtractFromSampleDataSize,
- offset,
- getPopulatedCryptoInfo(cryptoData));
- mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
- mEncryptionDataSizeToSubtractFromSampleDataSize = 0;
- }
-
- @Nullable
- private CryptoInfo getPopulatedCryptoInfo(@Nullable CryptoData cryptoData) {
- if (cryptoData == null) {
- // The sample is not encrypted.
- return null;
- } else if (mInBandCryptoInfo) {
- if (cryptoData != mLastReceivedCryptoData) {
- mLastOutputCryptoInfo =
- createNewCryptoInfoAndPopulateWithCryptoData(cryptoData);
- // We are using in-band crypto info, so the IV will be ignored. But we prevent
- // it from being null because toString assumes it non-null.
- mLastOutputCryptoInfo.iv = EMPTY_BYTE_ARRAY;
- }
- } else /* We must populate the full CryptoInfo. */ {
- // CryptoInfo.pattern is not accessible to the user, so the user needs to feed
- // this CryptoInfo directly to MediaCodec. We need to create a new CryptoInfo per
- // sample because of per-sample initialization vector changes.
- CryptoInfo newCryptoInfo = createNewCryptoInfoAndPopulateWithCryptoData(cryptoData);
- newCryptoInfo.iv = Arrays.copyOf(mScratchIvSpace, mScratchIvSpace.length);
- boolean canReuseSubsampleInfo =
- mLastOutputCryptoInfo != null
- && mLastOutputCryptoInfo.numSubSamples
- == mSubsampleEncryptionDataSize;
- for (int i = 0; i < mSubsampleEncryptionDataSize && canReuseSubsampleInfo; i++) {
- canReuseSubsampleInfo =
- mLastOutputCryptoInfo.numBytesOfClearData[i]
- == mScratchSubsampleClearBytesCount[i]
- && mLastOutputCryptoInfo.numBytesOfEncryptedData[i]
- == mScratchSubsampleEncryptedBytesCount[i];
- }
- newCryptoInfo.numSubSamples = mSubsampleEncryptionDataSize;
- if (canReuseSubsampleInfo) {
- newCryptoInfo.numBytesOfClearData = mLastOutputCryptoInfo.numBytesOfClearData;
- newCryptoInfo.numBytesOfEncryptedData =
- mLastOutputCryptoInfo.numBytesOfEncryptedData;
- } else {
- newCryptoInfo.numBytesOfClearData =
- Arrays.copyOf(
- mScratchSubsampleClearBytesCount, mSubsampleEncryptionDataSize);
- newCryptoInfo.numBytesOfEncryptedData =
- Arrays.copyOf(
- mScratchSubsampleEncryptedBytesCount,
- mSubsampleEncryptionDataSize);
- }
- mLastOutputCryptoInfo = newCryptoInfo;
- }
- mLastReceivedCryptoData = cryptoData;
- return mLastOutputCryptoInfo;
- }
-
- private CryptoInfo createNewCryptoInfoAndPopulateWithCryptoData(CryptoData cryptoData) {
- CryptoInfo cryptoInfo = new CryptoInfo();
- cryptoInfo.key = cryptoData.encryptionKey;
- cryptoInfo.mode = cryptoData.cryptoMode;
- if (cryptoData.clearBlocks != mLastOutputEncryptionPattern.getSkipBlocks()
- || cryptoData.encryptedBlocks
- != mLastOutputEncryptionPattern.getEncryptBlocks()) {
- mLastOutputEncryptionPattern =
- new CryptoInfo.Pattern(cryptoData.encryptedBlocks, cryptoData.clearBlocks);
- }
- cryptoInfo.setPattern(mLastOutputEncryptionPattern);
- return cryptoInfo;
- }
-
- private void outputSampleData(ParsableByteArray data, int length) {
- mScratchParsableByteArrayAdapter.resetWithByteArray(data, length);
- try {
- // Read all bytes from data. ExoPlayer extractors expect all sample data to be
- // consumed by TrackOutput implementations when passing a ParsableByteArray.
- while (mScratchParsableByteArrayAdapter.getLength() > 0) {
- mOutputConsumer.onSampleDataFound(
- mTrackIndex, mScratchParsableByteArrayAdapter);
- }
- } catch (IOException e) {
- // Unexpected.
- throw new RuntimeException(e);
- }
- }
- }
-
- private static final class DataReaderAdapter implements InputReader {
-
- private DataReader mDataReader;
- private int mCurrentPosition;
- private long mLength;
-
- public void setDataReader(DataReader dataReader, long length) {
- mDataReader = dataReader;
- mCurrentPosition = 0;
- mLength = length;
- }
-
- // Input implementation.
-
- @Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
- int readBytes = 0;
- readBytes = mDataReader.read(buffer, offset, readLength);
- mCurrentPosition += readBytes;
- return readBytes;
- }
-
- @Override
- public long getPosition() {
- return mCurrentPosition;
- }
-
- @Override
- public long getLength() {
- return mLength - mCurrentPosition;
- }
- }
-
- private static final class ParsableByteArrayAdapter implements InputReader {
-
- private ParsableByteArray mByteArray;
- private long mLength;
- private int mCurrentPosition;
-
- public void resetWithByteArray(ParsableByteArray byteArray, long length) {
- mByteArray = byteArray;
- mCurrentPosition = 0;
- mLength = length;
- }
-
- // Input implementation.
-
- @Override
- public int read(byte[] buffer, int offset, int readLength) {
- mByteArray.readBytes(buffer, offset, readLength);
- mCurrentPosition += readLength;
- return readLength;
- }
-
- @Override
- public long getPosition() {
- return mCurrentPosition;
- }
-
- @Override
- public long getLength() {
- return mLength - mCurrentPosition;
- }
- }
-
- private static final class DummyExoPlayerSeekMap
- implements com.google.android.exoplayer2.extractor.SeekMap {
-
- @Override
- public boolean isSeekable() {
- return true;
- }
-
- @Override
- public long getDurationUs() {
- return C.TIME_UNSET;
- }
-
- @Override
- public SeekPoints getSeekPoints(long timeUs) {
- com.google.android.exoplayer2.extractor.SeekPoint seekPoint =
- new com.google.android.exoplayer2.extractor.SeekPoint(
- timeUs, /* position= */ 0);
- return new SeekPoints(seekPoint, seekPoint);
- }
- }
-
- /** Creates extractor instances. */
- private interface ExtractorFactory {
-
- /** Returns a new extractor instance. */
- Extractor createInstance();
- }
-
- // Private static methods.
-
- private static Format toExoPlayerCaptionFormat(MediaFormat mediaFormat) {
- Format.Builder formatBuilder =
- new Format.Builder().setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME));
- if (mediaFormat.containsKey(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)) {
- formatBuilder.setAccessibilityChannel(
- mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER));
- }
- return formatBuilder.build();
- }
-
- private static MediaFormat toMediaFormat(Format format) {
- MediaFormat result = new MediaFormat();
- setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
-
- ColorInfo colorInfo = format.colorInfo;
- if (colorInfo != null) {
- setOptionalMediaFormatInt(
- result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
-
- if (format.colorInfo.hdrStaticInfo != null) {
- result.setByteBuffer(
- MediaFormat.KEY_HDR_STATIC_INFO,
- ByteBuffer.wrap(format.colorInfo.hdrStaticInfo));
- }
- }
-
- setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
- setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
- if (format.frameRate != Format.NO_VALUE) {
- result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate);
- }
- setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
-
- List<byte[]> initData = format.initializationData;
- for (int i = 0; i < initData.size(); i++) {
- result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
- }
- setPcmEncoding(format, result);
- setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
- setOptionalMediaFormatInt(
- result, MediaFormat.KEY_CAPTION_SERVICE_NUMBER, format.accessibilityChannel);
-
- int selectionFlags = format.selectionFlags;
- result.setInteger(
- MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
- result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
- result.setInteger(
- MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
-
- setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
-
- if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) {
- int parWidth = 1;
- int parHeight = 1;
- if (format.pixelWidthHeightRatio < 1.0f) {
- parHeight = 1 << 30;
- parWidth = (int) (format.pixelWidthHeightRatio * parHeight);
- } else if (format.pixelWidthHeightRatio > 1.0f) {
- parWidth = 1 << 30;
- parHeight = (int) (parWidth / format.pixelWidthHeightRatio);
- }
- result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth);
- result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight);
- result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio);
- }
- if (format.drmInitData != null) {
- // The crypto mode is propagated along with sample metadata. We also include it in the
- // format for convenient use from ExoPlayer.
- result.setString("crypto-mode-fourcc", format.drmInitData.schemeType);
- }
- if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
- result.setLong("subsample-offset-us-long", format.subsampleOffsetUs);
- }
- // LACK OF SUPPORT FOR:
- // format.id;
- // format.metadata;
- // format.stereoMode;
- return result;
- }
-
- private static ByteBuffer toByteBuffer(long[] longArray) {
- ByteBuffer byteBuffer = ByteBuffer.allocateDirect(longArray.length * Long.BYTES);
- for (long element : longArray) {
- byteBuffer.putLong(element);
- }
- byteBuffer.flip();
- return byteBuffer;
- }
-
- private static ByteBuffer toByteBuffer(int[] intArray) {
- ByteBuffer byteBuffer = ByteBuffer.allocateDirect(intArray.length * Integer.BYTES);
- for (int element : intArray) {
- byteBuffer.putInt(element);
- }
- byteBuffer.flip();
- return byteBuffer;
- }
-
- private static String toTypeString(int type) {
- switch (type) {
- case C.TRACK_TYPE_VIDEO:
- return "video";
- case C.TRACK_TYPE_AUDIO:
- return "audio";
- case C.TRACK_TYPE_TEXT:
- return "text";
- case C.TRACK_TYPE_METADATA:
- return "metadata";
- default:
- return "unknown";
- }
- }
-
- private static void setPcmEncoding(Format format, MediaFormat result) {
- int exoPcmEncoding = format.pcmEncoding;
- setOptionalMediaFormatInt(result, "exo-pcm-encoding", format.pcmEncoding);
- int mediaFormatPcmEncoding;
- switch (exoPcmEncoding) {
- case C.ENCODING_PCM_8BIT:
- mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT;
- break;
- case C.ENCODING_PCM_16BIT:
- mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_16BIT;
- break;
- case C.ENCODING_PCM_FLOAT:
- mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT;
- break;
- default:
- // No matching value. Do nothing.
- return;
- }
- result.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding);
- }
-
- private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
- if (value != Format.NO_VALUE) {
- mediaFormat.setInteger(key, value);
- }
- }
-
- private static void setOptionalMediaFormatString(
- MediaFormat mediaFormat, String key, @Nullable String value) {
- if (value != null) {
- mediaFormat.setString(key, value);
- }
- }
-
- private DrmInitData toFrameworkDrmInitData(
- com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData) {
- try {
- return exoDrmInitData != null && mSchemeInitDataConstructor != null
- ? new MediaParserDrmInitData(exoDrmInitData)
- : null;
- } catch (Throwable e) {
- if (!mLoggedSchemeInitDataCreationException) {
- mLoggedSchemeInitDataCreationException = true;
- Log.e(TAG, "Unable to create SchemeInitData instance.");
- }
- return null;
- }
- }
-
- /** Returns a new {@link SeekPoint} equivalent to the given {@code exoPlayerSeekPoint}. */
- private static SeekPoint toSeekPoint(
- com.google.android.exoplayer2.extractor.SeekPoint exoPlayerSeekPoint) {
- return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position);
- }
-
- /**
- * Introduces random error to the given metric value in order to prevent the identification of
- * the parsed media.
- */
- private static long addDither(long value) {
- // Generate a random in [0, 1].
- double randomDither = ThreadLocalRandom.current().nextFloat();
- // Clamp the random number to [0, 2 * MEDIAMETRICS_DITHER].
- randomDither *= 2 * MEDIAMETRICS_DITHER;
- // Translate the random number to [1 - MEDIAMETRICS_DITHER, 1 + MEDIAMETRICS_DITHER].
- randomDither += 1 - MEDIAMETRICS_DITHER;
- return value != -1 ? (long) (value * randomDither) : -1;
- }
-
- private static void assertValidNames(@NonNull String[] names) {
- for (String name : names) {
- if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) {
- throw new IllegalArgumentException(
- "Invalid extractor name: "
- + name
- + ". Supported parsers are: "
- + TextUtils.join(", ", EXTRACTOR_FACTORIES_BY_NAME.keySet())
- + ".");
- }
- }
- }
-
- private int getMediaParserFlags(int flags) {
- @SampleFlags int result = 0;
- result |= (flags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? SAMPLE_FLAG_ENCRYPTED : 0;
- result |= (flags & C.BUFFER_FLAG_KEY_FRAME) != 0 ? SAMPLE_FLAG_KEY_FRAME : 0;
- result |= (flags & C.BUFFER_FLAG_DECODE_ONLY) != 0 ? SAMPLE_FLAG_DECODE_ONLY : 0;
- result |=
- (flags & C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA) != 0 && mIncludeSupplementalData
- ? SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA
- : 0;
- result |= (flags & C.BUFFER_FLAG_LAST_SAMPLE) != 0 ? SAMPLE_FLAG_LAST_SAMPLE : 0;
- return result;
- }
-
- @Nullable
- private static Constructor<DrmInitData.SchemeInitData> getSchemeInitDataConstructor() {
- // TODO: Use constructor statically when available.
- Constructor<DrmInitData.SchemeInitData> constructor;
- try {
- return DrmInitData.SchemeInitData.class.getConstructor(
- UUID.class, String.class, byte[].class);
- } catch (Throwable e) {
- Log.e(TAG, "Unable to get SchemeInitData constructor.");
- return null;
- }
- }
-
- // Native methods.
-
- private native void nativeSubmitMetrics(
- String logSessionId,
- String parserName,
- boolean createdByName,
- String parserPool,
- String lastObservedExceptionName,
- long resourceByteCount,
- long durationMillis,
- String trackMimeTypes,
- String trackCodecs,
- String alteredParameters,
- int videoWidth,
- int videoHeight);
-
- // Static initialization.
-
- static {
- System.loadLibrary(JNI_LIBRARY_NAME);
-
- // Using a LinkedHashMap to keep the insertion order when iterating over the keys.
- LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>();
- // Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering,
- // which in turn aims to minimize the chances of incorrect extractor selections.
- extractorFactoriesByName.put(PARSER_NAME_MATROSKA, MatroskaExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_FMP4, FragmentedMp4Extractor::new);
- extractorFactoriesByName.put(PARSER_NAME_MP4, Mp4Extractor::new);
- extractorFactoriesByName.put(PARSER_NAME_MP3, Mp3Extractor::new);
- extractorFactoriesByName.put(PARSER_NAME_ADTS, AdtsExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_AC3, Ac3Extractor::new);
- extractorFactoriesByName.put(PARSER_NAME_TS, TsExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_FLV, FlvExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_OGG, OggExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_PS, PsExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_WAV, WavExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_AMR, AmrExtractor::new);
- extractorFactoriesByName.put(PARSER_NAME_AC4, Ac4Extractor::new);
- extractorFactoriesByName.put(PARSER_NAME_FLAC, FlacExtractor::new);
- EXTRACTOR_FACTORIES_BY_NAME = Collections.unmodifiableMap(extractorFactoriesByName);
-
- HashMap<String, Class> expectedTypeByParameterName = new HashMap<>();
- expectedTypeByParameterName.put(PARAMETER_ADTS_ENABLE_CBR_SEEKING, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_AMR_ENABLE_CBR_SEEKING, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_FLAC_DISABLE_ID3, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_EDIT_LISTS, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_TFDT_BOX, Boolean.class);
- expectedTypeByParameterName.put(
- PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_MP3_DISABLE_ID3, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_CBR_SEEKING, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_INDEX_SEEKING, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_TS_MODE, String.class);
- expectedTypeByParameterName.put(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AAC_STREAM, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AVC_STREAM, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_TS_DETECT_ACCESS_UNITS, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_IN_BAND_CRYPTO_INFO, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class);
- expectedTypeByParameterName.put(
- PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class);
- expectedTypeByParameterName.put(
- PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class);
- expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class);
- // We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters
- // instead. Checking that the value is a List is insufficient to catch wrong parameter
- // value types.
- int sumOfParameterNameLengths =
- expectedTypeByParameterName.keySet().stream()
- .map(String::length)
- .reduce(0, Integer::sum);
- sumOfParameterNameLengths += PARAMETER_EXPOSE_CAPTION_FORMATS.length();
- // Add space for any required separators.
- MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH =
- sumOfParameterNameLengths + expectedTypeByParameterName.size();
-
- EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName);
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java
deleted file mode 100644
index 7d07eb3..0000000
--- a/apex/media/framework/java/android/media/MediaSession2.java
+++ /dev/null
@@ -1,932 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS;
-import static android.media.MediaConstants.KEY_CONNECTION_HINTS;
-import static android.media.MediaConstants.KEY_PACKAGE_NAME;
-import static android.media.MediaConstants.KEY_PID;
-import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
-import static android.media.MediaConstants.KEY_TOKEN_EXTRAS;
-import static android.media.Session2Command.Result.RESULT_ERROR_UNKNOWN_ERROR;
-import static android.media.Session2Command.Result.RESULT_INFO_SKIPPED;
-import static android.media.Session2Token.TYPE_SESSION;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
-import android.os.BadParcelableException;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Parcel;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.modules.utils.build.SdkLevel;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Allows a media app to expose its transport controls and playback information in a process to
- * other processes including the Android framework and other apps.
- */
-public class MediaSession2 implements AutoCloseable {
- static final String TAG = "MediaSession2";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- // Note: This checks the uniqueness of a session ID only in a single process.
- // When the framework becomes able to check the uniqueness, this logic should be removed.
- //@GuardedBy("MediaSession.class")
- private static final List<String> SESSION_ID_LIST = new ArrayList<>();
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Object mLock = new Object();
- //@GuardedBy("mLock")
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Map<Controller2Link, ControllerInfo> mConnectedControllers = new HashMap<>();
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Context mContext;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Executor mCallbackExecutor;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final SessionCallback mCallback;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Session2Link mSessionStub;
-
- private final String mSessionId;
- private final PendingIntent mSessionActivity;
- private final Session2Token mSessionToken;
- private final MediaSessionManager mMediaSessionManager;
- private final MediaCommunicationManager mCommunicationManager;
- private final Handler mResultHandler;
-
- //@GuardedBy("mLock")
- private boolean mClosed;
- //@GuardedBy("mLock")
- private boolean mPlaybackActive;
- //@GuardedBy("mLock")
- private ForegroundServiceEventCallback mForegroundServiceEventCallback;
-
- MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity,
- @NonNull Executor callbackExecutor, @NonNull SessionCallback callback,
- @NonNull Bundle tokenExtras) {
- synchronized (MediaSession2.class) {
- if (SESSION_ID_LIST.contains(id)) {
- throw new IllegalStateException("Session ID must be unique. ID=" + id);
- }
- SESSION_ID_LIST.add(id);
- }
-
- mContext = context;
- mSessionId = id;
- mSessionActivity = sessionActivity;
- mCallbackExecutor = callbackExecutor;
- mCallback = callback;
- mSessionStub = new Session2Link(this);
- mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(),
- mSessionStub, tokenExtras);
- if (SdkLevel.isAtLeastS()) {
- mCommunicationManager = mContext.getSystemService(MediaCommunicationManager.class);
- mMediaSessionManager = null;
- } else {
- mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
- mCommunicationManager = null;
- }
- // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked.
- mResultHandler = new Handler(context.getMainLooper());
- mClosed = false;
- }
-
- @Override
- public void close() {
- try {
- List<ControllerInfo> controllerInfos;
- ForegroundServiceEventCallback callback;
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
- mClosed = true;
- controllerInfos = getConnectedControllers();
- mConnectedControllers.clear();
- callback = mForegroundServiceEventCallback;
- mForegroundServiceEventCallback = null;
- }
- synchronized (MediaSession2.class) {
- SESSION_ID_LIST.remove(mSessionId);
- }
- if (callback != null) {
- callback.onSessionClosed(this);
- }
- for (ControllerInfo info : controllerInfos) {
- info.notifyDisconnected();
- }
- } catch (Exception e) {
- // Should not be here.
- }
- }
-
- /**
- * Returns the session ID
- */
- @NonNull
- public String getId() {
- return mSessionId;
- }
-
- /**
- * Returns the {@link Session2Token} for creating {@link MediaController2}.
- */
- @NonNull
- public Session2Token getToken() {
- return mSessionToken;
- }
-
- /**
- * Broadcasts a session command to all the connected controllers
- * <p>
- * @param command the session command
- * @param args optional arguments
- */
- public void broadcastSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- List<ControllerInfo> controllerInfos = getConnectedControllers();
- for (ControllerInfo controller : controllerInfos) {
- controller.sendSessionCommand(command, args, null);
- }
- }
-
- /**
- * Sends a session command to a specific controller
- * <p>
- * @param controller the controller to get the session command
- * @param command the session command
- * @param args optional arguments
- * @return a token which will be sent together in {@link SessionCallback#onCommandResult}
- * when its result is received.
- */
- @NonNull
- public Object sendSessionCommand(@NonNull ControllerInfo controller,
- @NonNull Session2Command command, @Nullable Bundle args) {
- if (controller == null) {
- throw new IllegalArgumentException("controller shouldn't be null");
- }
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- ResultReceiver resultReceiver = new ResultReceiver(mResultHandler) {
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- controller.receiveCommandResult(this);
- mCallbackExecutor.execute(() -> {
- mCallback.onCommandResult(MediaSession2.this, controller, this,
- command, new Session2Command.Result(resultCode, resultData));
- });
- }
- };
- controller.sendSessionCommand(command, args, resultReceiver);
- return resultReceiver;
- }
-
- /**
- * Cancels the session command previously sent.
- *
- * @param controller the controller to get the session command
- * @param token the token which is returned from {@link #sendSessionCommand}.
- */
- public void cancelSessionCommand(@NonNull ControllerInfo controller, @NonNull Object token) {
- if (controller == null) {
- throw new IllegalArgumentException("controller shouldn't be null");
- }
- if (token == null) {
- throw new IllegalArgumentException("token shouldn't be null");
- }
- controller.cancelSessionCommand(token);
- }
-
- /**
- * Sets whether the playback is active (i.e. playing something)
- *
- * @param playbackActive {@code true} if the playback active, {@code false} otherwise.
- **/
- public void setPlaybackActive(boolean playbackActive) {
- final ForegroundServiceEventCallback serviceCallback;
- synchronized (mLock) {
- if (mPlaybackActive == playbackActive) {
- return;
- }
- mPlaybackActive = playbackActive;
- serviceCallback = mForegroundServiceEventCallback;
- }
- if (serviceCallback != null) {
- serviceCallback.onPlaybackActiveChanged(this, playbackActive);
- }
- List<ControllerInfo> controllerInfos = getConnectedControllers();
- for (ControllerInfo controller : controllerInfos) {
- controller.notifyPlaybackActiveChanged(playbackActive);
- }
- }
-
- /**
- * Returns whether the playback is active (i.e. playing something)
- *
- * @return {@code true} if the playback active, {@code false} otherwise.
- */
- public boolean isPlaybackActive() {
- synchronized (mLock) {
- return mPlaybackActive;
- }
- }
-
- /**
- * Gets the list of the connected controllers
- *
- * @return list of the connected controllers.
- */
- @NonNull
- public List<ControllerInfo> getConnectedControllers() {
- List<ControllerInfo> controllers = new ArrayList<>();
- synchronized (mLock) {
- controllers.addAll(mConnectedControllers.values());
- }
- return controllers;
- }
-
- /**
- * Returns whether the given bundle includes non-framework Parcelables.
- */
- static boolean hasCustomParcelable(@Nullable Bundle bundle) {
- if (bundle == null) {
- return false;
- }
-
- // Try writing the bundle to parcel, and read it with framework classloader.
- Parcel parcel = null;
- try {
- parcel = Parcel.obtain();
- parcel.writeBundle(bundle);
- parcel.setDataPosition(0);
- Bundle out = parcel.readBundle(null);
-
- for (String key : out.keySet()) {
- out.get(key);
- }
- } catch (BadParcelableException e) {
- Log.d(TAG, "Custom parcelable in bundle.", e);
- return true;
- } finally {
- if (parcel != null) {
- parcel.recycle();
- }
- }
- return false;
- }
-
- boolean isClosed() {
- synchronized (mLock) {
- return mClosed;
- }
- }
-
- SessionCallback getCallback() {
- return mCallback;
- }
-
- boolean isTrustedForMediaControl(RemoteUserInfo remoteUserInfo) {
- if (SdkLevel.isAtLeastS()) {
- return mCommunicationManager.isTrustedForMediaControl(remoteUserInfo);
- } else {
- return mMediaSessionManager.isTrustedForMediaControl(remoteUserInfo);
- }
- }
-
- void setForegroundServiceEventCallback(ForegroundServiceEventCallback callback) {
- synchronized (mLock) {
- if (mForegroundServiceEventCallback == callback) {
- return;
- }
- if (mForegroundServiceEventCallback != null && callback != null) {
- throw new IllegalStateException("A session cannot be added to multiple services");
- }
- mForegroundServiceEventCallback = callback;
- }
- }
-
- // Called by Session2Link.onConnect and MediaSession2Service.MediaSession2ServiceStub.connect
- void onConnect(final Controller2Link controller, int callingPid, int callingUid, int seq,
- Bundle connectionRequest) {
- if (callingPid == 0) {
- // The pid here is from Binder.getCallingPid(), which can be 0 for an oneway call from
- // the remote process. If it's the case, use PID from the connectionRequest.
- callingPid = connectionRequest.getInt(KEY_PID);
- }
- String callingPkg = connectionRequest.getString(KEY_PACKAGE_NAME);
-
- RemoteUserInfo remoteUserInfo = new RemoteUserInfo(callingPkg, callingPid, callingUid);
-
- Bundle connectionHints = connectionRequest.getBundle(KEY_CONNECTION_HINTS);
- if (connectionHints == null) {
- Log.w(TAG, "connectionHints shouldn't be null.");
- connectionHints = Bundle.EMPTY;
- } else if (hasCustomParcelable(connectionHints)) {
- Log.w(TAG, "connectionHints contain custom parcelable. Ignoring.");
- connectionHints = Bundle.EMPTY;
- }
-
- final ControllerInfo controllerInfo = new ControllerInfo(
- remoteUserInfo,
- isTrustedForMediaControl(remoteUserInfo),
- controller,
- connectionHints);
- mCallbackExecutor.execute(() -> {
- boolean connected = false;
- try {
- if (isClosed()) {
- return;
- }
- controllerInfo.mAllowedCommands =
- mCallback.onConnect(MediaSession2.this, controllerInfo);
- // Don't reject connection for the request from trusted app.
- // Otherwise server will fail to retrieve session's information to dispatch
- // media keys to.
- if (controllerInfo.mAllowedCommands == null && !controllerInfo.isTrusted()) {
- return;
- }
- if (controllerInfo.mAllowedCommands == null) {
- // For trusted apps, send non-null allowed commands to keep
- // connection.
- controllerInfo.mAllowedCommands =
- new Session2CommandGroup.Builder().build();
- }
- if (DEBUG) {
- Log.d(TAG, "Accepting connection: " + controllerInfo);
- }
- // If connection is accepted, notify the current state to the controller.
- // It's needed because we cannot call synchronous calls between
- // session/controller.
- Bundle connectionResult = new Bundle();
- connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub);
- connectionResult.putParcelable(KEY_ALLOWED_COMMANDS,
- controllerInfo.mAllowedCommands);
- connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive());
- connectionResult.putBundle(KEY_TOKEN_EXTRAS, mSessionToken.getExtras());
-
- // Double check if session is still there, because close() can be called in
- // another thread.
- if (isClosed()) {
- return;
- }
- controllerInfo.notifyConnected(connectionResult);
- synchronized (mLock) {
- if (mConnectedControllers.containsKey(controller)) {
- Log.w(TAG, "Controller " + controllerInfo + " has sent connection"
- + " request multiple times");
- }
- mConnectedControllers.put(controller, controllerInfo);
- }
- mCallback.onPostConnect(MediaSession2.this, controllerInfo);
- connected = true;
- } finally {
- if (!connected || isClosed()) {
- if (DEBUG) {
- Log.d(TAG, "Rejecting connection or notifying that session is closed"
- + ", controllerInfo=" + controllerInfo);
- }
- synchronized (mLock) {
- mConnectedControllers.remove(controller);
- }
- controllerInfo.notifyDisconnected();
- }
- }
- });
- }
-
- // Called by Session2Link.onDisconnect
- void onDisconnect(@NonNull final Controller2Link controller, int seq) {
- final ControllerInfo controllerInfo;
- synchronized (mLock) {
- controllerInfo = mConnectedControllers.remove(controller);
- }
- if (controllerInfo == null) {
- return;
- }
- mCallbackExecutor.execute(() -> {
- mCallback.onDisconnected(MediaSession2.this, controllerInfo);
- });
- }
-
- // Called by Session2Link.onSessionCommand
- void onSessionCommand(@NonNull final Controller2Link controller, final int seq,
- final Session2Command command, final Bundle args,
- @Nullable ResultReceiver resultReceiver) {
- if (controller == null) {
- return;
- }
- final ControllerInfo controllerInfo;
- synchronized (mLock) {
- controllerInfo = mConnectedControllers.get(controller);
- }
- if (controllerInfo == null) {
- return;
- }
-
- // TODO: check allowed commands.
- synchronized (mLock) {
- controllerInfo.addRequestedCommandSeqNumber(seq);
- }
- mCallbackExecutor.execute(() -> {
- if (!controllerInfo.removeRequestedCommandSeqNumber(seq)) {
- if (resultReceiver != null) {
- resultReceiver.send(RESULT_INFO_SKIPPED, null);
- }
- return;
- }
- Session2Command.Result result = mCallback.onSessionCommand(
- MediaSession2.this, controllerInfo, command, args);
- if (resultReceiver != null) {
- if (result == null) {
- resultReceiver.send(RESULT_INFO_SKIPPED, null);
- } else {
- resultReceiver.send(result.getResultCode(), result.getResultData());
- }
- }
- });
- }
-
- // Called by Session2Link.onCancelCommand
- void onCancelCommand(@NonNull final Controller2Link controller, final int seq) {
- final ControllerInfo controllerInfo;
- synchronized (mLock) {
- controllerInfo = mConnectedControllers.get(controller);
- }
- if (controllerInfo == null) {
- return;
- }
- controllerInfo.removeRequestedCommandSeqNumber(seq);
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Builder for {@link MediaSession2}.
- * <p>
- * Any incoming event from the {@link MediaController2} will be handled on the callback
- * executor. If it's not set, {@link Context#getMainExecutor()} will be used by default.
- */
- public static final class Builder {
- private Context mContext;
- private String mId;
- private PendingIntent mSessionActivity;
- private Executor mCallbackExecutor;
- private SessionCallback mCallback;
- private Bundle mExtras;
-
- /**
- * Creates a builder for {@link MediaSession2}.
- *
- * @param context Context
- * @throws IllegalArgumentException if context is {@code null}.
- */
- public Builder(@NonNull Context context) {
- if (context == null) {
- throw new IllegalArgumentException("context shouldn't be null");
- }
- mContext = context;
- }
-
- /**
- * Set an intent for launching UI for this Session. This can be used as a
- * quick link to an ongoing media screen. The intent should be for an
- * activity that may be started using {@link Context#startActivity(Intent)}.
- *
- * @param pi The intent to launch to show UI for this session.
- * @return The Builder to allow chaining
- */
- @NonNull
- public Builder setSessionActivity(@Nullable PendingIntent pi) {
- mSessionActivity = pi;
- return this;
- }
-
- /**
- * Set ID of the session. If it's not set, an empty string will be used to create a session.
- * <p>
- * Use this if and only if your app supports multiple playback at the same time and also
- * wants to provide external apps to have finer controls of them.
- *
- * @param id id of the session. Must be unique per package.
- * @throws IllegalArgumentException if id is {@code null}.
- * @return The Builder to allow chaining
- */
- @NonNull
- public Builder setId(@NonNull String id) {
- if (id == null) {
- throw new IllegalArgumentException("id shouldn't be null");
- }
- mId = id;
- return this;
- }
-
- /**
- * Set callback for the session and its executor.
- *
- * @param executor callback executor
- * @param callback session callback.
- * @return The Builder to allow chaining
- */
- @NonNull
- public Builder setSessionCallback(@NonNull Executor executor,
- @NonNull SessionCallback callback) {
- mCallbackExecutor = executor;
- mCallback = callback;
- return this;
- }
-
- /**
- * Set extras for the session token. If null or not set, {@link Session2Token#getExtras()}
- * will return an empty {@link Bundle}. An {@link IllegalArgumentException} will be thrown
- * if the bundle contains any non-framework Parcelable objects.
- *
- * @return The Builder to allow chaining
- * @see Session2Token#getExtras()
- */
- @NonNull
- public Builder setExtras(@NonNull Bundle extras) {
- if (extras == null) {
- throw new NullPointerException("extras shouldn't be null");
- }
- if (hasCustomParcelable(extras)) {
- throw new IllegalArgumentException(
- "extras shouldn't contain any custom parcelables");
- }
- mExtras = new Bundle(extras);
- return this;
- }
-
- /**
- * Build {@link MediaSession2}.
- *
- * @return a new session
- * @throws IllegalStateException if the session with the same id is already exists for the
- * package.
- */
- @NonNull
- public MediaSession2 build() {
- if (mCallbackExecutor == null) {
- mCallbackExecutor = mContext.getMainExecutor();
- }
- if (mCallback == null) {
- mCallback = new SessionCallback() {};
- }
- if (mId == null) {
- mId = "";
- }
- if (mExtras == null) {
- mExtras = Bundle.EMPTY;
- }
- MediaSession2 session2 = new MediaSession2(mContext, mId, mSessionActivity,
- mCallbackExecutor, mCallback, mExtras);
-
- // Notify framework about the newly create session after the constructor is finished.
- // Otherwise, framework may access the session before the initialization is finished.
- try {
- if (SdkLevel.isAtLeastS()) {
- MediaCommunicationManager manager =
- mContext.getSystemService(MediaCommunicationManager.class);
- manager.notifySession2Created(session2.getToken());
- } else {
- MediaSessionManager manager =
- mContext.getSystemService(MediaSessionManager.class);
- manager.notifySession2Created(session2.getToken());
- }
- } catch (Exception e) {
- session2.close();
- throw e;
- }
-
- return session2;
- }
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Information of a controller.
- */
- public static final class ControllerInfo {
- private final RemoteUserInfo mRemoteUserInfo;
- private final boolean mIsTrusted;
- private final Controller2Link mControllerBinder;
- private final Bundle mConnectionHints;
- private final Object mLock = new Object();
- //@GuardedBy("mLock")
- private int mNextSeqNumber;
- //@GuardedBy("mLock")
- private ArrayMap<ResultReceiver, Integer> mPendingCommands;
- //@GuardedBy("mLock")
- private ArraySet<Integer> mRequestedCommandSeqNumbers;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- Session2CommandGroup mAllowedCommands;
-
- /**
- * @param remoteUserInfo remote user info
- * @param trusted {@code true} if trusted, {@code false} otherwise
- * @param controllerBinder Controller2Link for the connected controller.
- * @param connectionHints a session-specific argument sent from the controller for the
- * connection. The contents of this bundle may affect the
- * connection result.
- */
- ControllerInfo(@NonNull RemoteUserInfo remoteUserInfo, boolean trusted,
- @Nullable Controller2Link controllerBinder, @NonNull Bundle connectionHints) {
- mRemoteUserInfo = remoteUserInfo;
- mIsTrusted = trusted;
- mControllerBinder = controllerBinder;
- mConnectionHints = connectionHints;
- mPendingCommands = new ArrayMap<>();
- mRequestedCommandSeqNumbers = new ArraySet<>();
- }
-
- /**
- * @return remote user info of the controller.
- */
- @NonNull
- public RemoteUserInfo getRemoteUserInfo() {
- return mRemoteUserInfo;
- }
-
- /**
- * @return package name of the controller.
- */
- @NonNull
- public String getPackageName() {
- return mRemoteUserInfo.getPackageName();
- }
-
- /**
- * @return uid of the controller. Can be a negative value if the uid cannot be obtained.
- */
- public int getUid() {
- return mRemoteUserInfo.getUid();
- }
-
- /**
- * @return connection hints sent from controller.
- */
- @NonNull
- public Bundle getConnectionHints() {
- return new Bundle(mConnectionHints);
- }
-
- /**
- * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or
- * has a enabled notification listener so can be trusted to accept connection and incoming
- * command request.
- *
- * @return {@code true} if the controller is trusted.
- * @hide
- */
- public boolean isTrusted() {
- return mIsTrusted;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mControllerBinder, mRemoteUserInfo);
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (!(obj instanceof ControllerInfo)) return false;
- if (this == obj) return true;
-
- ControllerInfo other = (ControllerInfo) obj;
- if (mControllerBinder != null || other.mControllerBinder != null) {
- return Objects.equals(mControllerBinder, other.mControllerBinder);
- }
- return mRemoteUserInfo.equals(other.mRemoteUserInfo);
- }
-
- @Override
- @NonNull
- public String toString() {
- return "ControllerInfo {pkg=" + mRemoteUserInfo.getPackageName() + ", uid="
- + mRemoteUserInfo.getUid() + ", allowedCommands=" + mAllowedCommands + "})";
- }
-
- void notifyConnected(Bundle connectionResult) {
- if (mControllerBinder == null) return;
-
- try {
- mControllerBinder.notifyConnected(getNextSeqNumber(), connectionResult);
- } catch (RuntimeException e) {
- // Controller may be died prematurely.
- }
- }
-
- void notifyDisconnected() {
- if (mControllerBinder == null) return;
-
- try {
- mControllerBinder.notifyDisconnected(getNextSeqNumber());
- } catch (RuntimeException e) {
- // Controller may be died prematurely.
- }
- }
-
- void notifyPlaybackActiveChanged(boolean playbackActive) {
- if (mControllerBinder == null) return;
-
- try {
- mControllerBinder.notifyPlaybackActiveChanged(getNextSeqNumber(), playbackActive);
- } catch (RuntimeException e) {
- // Controller may be died prematurely.
- }
- }
-
- void sendSessionCommand(Session2Command command, Bundle args,
- ResultReceiver resultReceiver) {
- if (mControllerBinder == null) return;
-
- try {
- int seq = getNextSeqNumber();
- synchronized (mLock) {
- mPendingCommands.put(resultReceiver, seq);
- }
- mControllerBinder.sendSessionCommand(seq, command, args, resultReceiver);
- } catch (RuntimeException e) {
- // Controller may be died prematurely.
- synchronized (mLock) {
- mPendingCommands.remove(resultReceiver);
- }
- resultReceiver.send(RESULT_ERROR_UNKNOWN_ERROR, null);
- }
- }
-
- void cancelSessionCommand(@NonNull Object token) {
- if (mControllerBinder == null) return;
- Integer seq;
- synchronized (mLock) {
- seq = mPendingCommands.remove(token);
- }
- if (seq != null) {
- mControllerBinder.cancelSessionCommand(seq);
- }
- }
-
- void receiveCommandResult(ResultReceiver resultReceiver) {
- synchronized (mLock) {
- mPendingCommands.remove(resultReceiver);
- }
- }
-
- void addRequestedCommandSeqNumber(int seq) {
- synchronized (mLock) {
- mRequestedCommandSeqNumbers.add(seq);
- }
- }
-
- boolean removeRequestedCommandSeqNumber(int seq) {
- synchronized (mLock) {
- return mRequestedCommandSeqNumbers.remove(seq);
- }
- }
-
- private int getNextSeqNumber() {
- synchronized (mLock) {
- return mNextSeqNumber++;
- }
- }
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Callback to be called for all incoming commands from {@link MediaController2}s.
- */
- public abstract static class SessionCallback {
- /**
- * Called when a controller is created for this session. Return allowed commands for
- * controller. By default it returns {@code null}.
- * <p>
- * You can reject the connection by returning {@code null}. In that case, controller
- * receives {@link MediaController2.ControllerCallback#onDisconnected(MediaController2)}
- * and cannot be used.
- * <p>
- * The controller hasn't connected yet in this method, so calls to the controller
- * (e.g. {@link #sendSessionCommand}) would be ignored. Override {@link #onPostConnect} for
- * the custom initialization for the controller instead.
- *
- * @param session the session for this event
- * @param controller controller information.
- * @return allowed commands. Can be {@code null} to reject connection.
- */
- @Nullable
- public Session2CommandGroup onConnect(@NonNull MediaSession2 session,
- @NonNull ControllerInfo controller) {
- return null;
- }
-
- /**
- * Called immediately after a controller is connected. This is a convenient method to add
- * custom initialization between the session and a controller.
- * <p>
- * Note that calls to the controller (e.g. {@link #sendSessionCommand}) work here but don't
- * work in {@link #onConnect} because the controller hasn't connected yet in
- * {@link #onConnect}.
- *
- * @param session the session for this event
- * @param controller controller information.
- */
- public void onPostConnect(@NonNull MediaSession2 session,
- @NonNull ControllerInfo controller) {
- }
-
- /**
- * Called when a controller is disconnected
- *
- * @param session the session for this event
- * @param controller controller information
- */
- public void onDisconnected(@NonNull MediaSession2 session,
- @NonNull ControllerInfo controller) {}
-
- /**
- * Called when a controller sent a session command.
- *
- * @param session the session for this event
- * @param controller controller information
- * @param command the session command
- * @param args optional arguments
- * @return the result for the session command. If {@code null}, RESULT_INFO_SKIPPED
- * will be sent to the session.
- */
- @Nullable
- public Session2Command.Result onSessionCommand(@NonNull MediaSession2 session,
- @NonNull ControllerInfo controller, @NonNull Session2Command command,
- @Nullable Bundle args) {
- return null;
- }
-
- /**
- * Called when the command sent to the controller is finished.
- *
- * @param session the session for this event
- * @param controller controller information
- * @param token the token got from {@link MediaSession2#sendSessionCommand}
- * @param command the session command
- * @param result the result of the session command
- */
- public void onCommandResult(@NonNull MediaSession2 session,
- @NonNull ControllerInfo controller, @NonNull Object token,
- @NonNull Session2Command command, @NonNull Session2Command.Result result) {}
- }
-
- abstract static class ForegroundServiceEventCallback {
- public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {}
- public void onSessionClosed(MediaSession2 session) {}
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaSession2Service.java b/apex/media/framework/java/android/media/MediaSession2Service.java
deleted file mode 100644
index 9f80c43..0000000
--- a/apex/media/framework/java/android/media/MediaSession2Service.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright 2019 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.media;
-
-import static android.media.MediaConstants.KEY_CONNECTION_HINTS;
-import static android.media.MediaConstants.KEY_PACKAGE_NAME;
-import static android.media.MediaConstants.KEY_PID;
-
-import android.annotation.CallSuper;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Service containing {@link MediaSession2}.
- */
-public abstract class MediaSession2Service extends Service {
- /**
- * The {@link Intent} that must be declared as handled by the service.
- */
- public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
-
- private static final String TAG = "MediaSession2Service";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final MediaSession2.ForegroundServiceEventCallback mForegroundServiceEventCallback =
- new MediaSession2.ForegroundServiceEventCallback() {
- @Override
- public void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
- MediaSession2Service.this.onPlaybackActiveChanged(session, playbackActive);
- }
-
- @Override
- public void onSessionClosed(MediaSession2 session) {
- removeSession(session);
- }
- };
-
- private final Object mLock = new Object();
- //@GuardedBy("mLock")
- private NotificationManager mNotificationManager;
- //@GuardedBy("mLock")
- private MediaSessionManager mMediaSessionManager;
- //@GuardedBy("mLock")
- private Intent mStartSelfIntent;
- //@GuardedBy("mLock")
- private Map<String, MediaSession2> mSessions = new ArrayMap<>();
- //@GuardedBy("mLock")
- private Map<MediaSession2, MediaNotification> mNotifications = new ArrayMap<>();
- //@GuardedBy("mLock")
- private MediaSession2ServiceStub mStub;
-
- /**
- * Called by the system when the service is first created. Do not call this method directly.
- * <p>
- * Override this method if you need your own initialization. Derived classes MUST call through
- * to the super class's implementation of this method.
- */
- @CallSuper
- @Override
- public void onCreate() {
- super.onCreate();
- synchronized (mLock) {
- mStub = new MediaSession2ServiceStub(this);
- mStartSelfIntent = new Intent(this, this.getClass());
- mNotificationManager =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- mMediaSessionManager =
- (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
- }
- }
-
- @CallSuper
- @Override
- @Nullable
- public IBinder onBind(@NonNull Intent intent) {
- if (SERVICE_INTERFACE.equals(intent.getAction())) {
- synchronized (mLock) {
- return mStub;
- }
- }
- return null;
- }
-
- /**
- * Called by the system to notify that it is no longer used and is being removed. Do not call
- * this method directly.
- * <p>
- * Override this method if you need your own clean up. Derived classes MUST call through
- * to the super class's implementation of this method.
- */
- @CallSuper
- @Override
- public void onDestroy() {
- super.onDestroy();
- synchronized (mLock) {
- List<MediaSession2> sessions = getSessions();
- for (MediaSession2 session : sessions) {
- removeSession(session);
- }
- mSessions.clear();
- mNotifications.clear();
- }
- mStub.close();
- }
-
- /**
- * Called when a {@link MediaController2} is created with the this service's
- * {@link Session2Token}. Return the session for telling the controller which session to
- * connect. Return {@code null} to reject the connection from this controller.
- * <p>
- * Session returned here will be added to this service automatically. You don't need to call
- * {@link #addSession(MediaSession2)} for that.
- * <p>
- * This method is always called on the main thread.
- *
- * @param controllerInfo information of the controller which is trying to connect.
- * @return a {@link MediaSession2} instance for the controller to connect to, or {@code null}
- * to reject connection
- * @see MediaSession2.Builder
- * @see #getSessions()
- */
- @Nullable
- public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo);
-
- /**
- * Called to update the media notification when the playback state changes.
- * <p>
- * If playback is active and a notification is returned, the service uses it to become a
- * foreground service. If playback is not active then the notification is still posted, but the
- * service does not become a foreground service.
- * <p>
- * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission
- * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU}
- * or later, notifications will only be posted if the app has also been granted the
- * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission.
- *
- * @param session the session for which an updated media notification is required.
- * @return the {@link MediaNotification}. Can be {@code null}.
- */
- @Nullable
- public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
-
- /**
- * Adds a session to this service.
- * <p>
- * Added session will be removed automatically when it's closed, or removed when
- * {@link #removeSession} is called.
- *
- * @param session a session to be added.
- * @see #removeSession(MediaSession2)
- */
- public final void addSession(@NonNull MediaSession2 session) {
- if (session == null) {
- throw new IllegalArgumentException("session shouldn't be null");
- }
- if (session.isClosed()) {
- throw new IllegalArgumentException("session is already closed");
- }
- synchronized (mLock) {
- MediaSession2 previousSession = mSessions.get(session.getId());
- if (previousSession != null) {
- if (previousSession != session) {
- Log.w(TAG, "Session ID should be unique, ID=" + session.getId()
- + ", previous=" + previousSession + ", session=" + session);
- }
- return;
- }
- mSessions.put(session.getId(), session);
- session.setForegroundServiceEventCallback(mForegroundServiceEventCallback);
- }
- }
-
- /**
- * Removes a session from this service.
- *
- * @param session a session to be removed.
- * @see #addSession(MediaSession2)
- */
- public final void removeSession(@NonNull MediaSession2 session) {
- if (session == null) {
- throw new IllegalArgumentException("session shouldn't be null");
- }
- MediaNotification notification;
- synchronized (mLock) {
- if (mSessions.get(session.getId()) != session) {
- // Session isn't added or removed already.
- return;
- }
- mSessions.remove(session.getId());
- notification = mNotifications.remove(session);
- }
- session.setForegroundServiceEventCallback(null);
- if (notification != null) {
- mNotificationManager.cancel(notification.getNotificationId());
- }
- if (getSessions().isEmpty()) {
- stopForeground(false);
- }
- }
-
- /**
- * Gets the list of {@link MediaSession2}s that you've added to this service.
- *
- * @return sessions
- */
- public final @NonNull List<MediaSession2> getSessions() {
- List<MediaSession2> list = new ArrayList<>();
- synchronized (mLock) {
- list.addAll(mSessions.values());
- }
- return list;
- }
-
- /**
- * Returns the {@link MediaSessionManager}.
- */
- @NonNull
- MediaSessionManager getMediaSessionManager() {
- synchronized (mLock) {
- return mMediaSessionManager;
- }
- }
-
- /**
- * Called by registered {@link MediaSession2.ForegroundServiceEventCallback}
- *
- * @param session session with change
- * @param playbackActive {@code true} if playback is active.
- */
- void onPlaybackActiveChanged(MediaSession2 session, boolean playbackActive) {
- MediaNotification mediaNotification = onUpdateNotification(session);
- if (mediaNotification == null) {
- // The service implementation doesn't want to use the automatic start/stopForeground
- // feature.
- return;
- }
- synchronized (mLock) {
- mNotifications.put(session, mediaNotification);
- }
- int id = mediaNotification.getNotificationId();
- Notification notification = mediaNotification.getNotification();
- if (!playbackActive) {
- mNotificationManager.notify(id, notification);
- return;
- }
- // playbackActive == true
- startForegroundService(mStartSelfIntent);
- startForeground(id, notification);
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Returned by {@link #onUpdateNotification(MediaSession2)} for making session service
- * foreground service to keep playback running in the background. It's highly recommended to
- * show media style notification here.
- */
- public static class MediaNotification {
- private final int mNotificationId;
- private final Notification mNotification;
-
- /**
- * Default constructor
- *
- * @param notificationId notification id to be used for
- * {@link NotificationManager#notify(int, Notification)}.
- * @param notification a notification to make session service run in the foreground. Media
- * style notification is recommended here.
- */
- public MediaNotification(int notificationId, @NonNull Notification notification) {
- if (notification == null) {
- throw new IllegalArgumentException("notification shouldn't be null");
- }
- mNotificationId = notificationId;
- mNotification = notification;
- }
-
- /**
- * Gets the id of the notification.
- *
- * @return the notification id
- */
- public int getNotificationId() {
- return mNotificationId;
- }
-
- /**
- * Gets the notification.
- *
- * @return the notification
- */
- @NonNull
- public Notification getNotification() {
- return mNotification;
- }
- }
-
- private static final class MediaSession2ServiceStub extends IMediaSession2Service.Stub
- implements AutoCloseable {
- final WeakReference<MediaSession2Service> mService;
- final Handler mHandler;
-
- MediaSession2ServiceStub(MediaSession2Service service) {
- mService = new WeakReference<>(service);
- mHandler = new Handler(service.getMainLooper());
- }
-
- @Override
- public void connect(Controller2Link caller, int seq, Bundle connectionRequest) {
- if (mService.get() == null) {
- if (DEBUG) {
- Log.d(TAG, "Service is already destroyed");
- }
- return;
- }
- if (caller == null || connectionRequest == null) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring calls with illegal arguments, caller=" + caller
- + ", connectionRequest=" + connectionRequest);
- }
- return;
- }
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- mHandler.post(() -> {
- boolean shouldNotifyDisconnected = true;
- try {
- final MediaSession2Service service = mService.get();
- if (service == null) {
- if (DEBUG) {
- Log.d(TAG, "Service isn't available");
- }
- return;
- }
-
- String callingPkg = connectionRequest.getString(KEY_PACKAGE_NAME);
- // The Binder.getCallingPid() can be 0 for an oneway call from the
- // remote process. If it's the case, use PID from the connectionRequest.
- RemoteUserInfo remoteUserInfo = new RemoteUserInfo(
- callingPkg,
- pid == 0 ? connectionRequest.getInt(KEY_PID) : pid,
- uid);
-
- Bundle connectionHints = connectionRequest.getBundle(KEY_CONNECTION_HINTS);
- if (connectionHints == null) {
- Log.w(TAG, "connectionHints shouldn't be null.");
- connectionHints = Bundle.EMPTY;
- } else if (MediaSession2.hasCustomParcelable(connectionHints)) {
- Log.w(TAG, "connectionHints contain custom parcelable. Ignoring.");
- connectionHints = Bundle.EMPTY;
- }
-
- final ControllerInfo controllerInfo = new ControllerInfo(
- remoteUserInfo,
- service.getMediaSessionManager()
- .isTrustedForMediaControl(remoteUserInfo),
- caller,
- connectionHints);
-
- if (DEBUG) {
- Log.d(TAG, "Handling incoming connection request from the"
- + " controller=" + controllerInfo);
- }
-
- final MediaSession2 session;
- session = service.onGetSession(controllerInfo);
-
- if (session == null) {
- if (DEBUG) {
- Log.d(TAG, "Rejecting incoming connection request from the"
- + " controller=" + controllerInfo);
- }
- // Note: Trusted controllers also can be rejected according to the
- // service implementation.
- return;
- }
- service.addSession(session);
- shouldNotifyDisconnected = false;
- session.onConnect(caller, pid, uid, seq, connectionRequest);
- } catch (Exception e) {
- // Don't propagate exception in service to the controller.
- Log.w(TAG, "Failed to add a session to session service", e);
- } finally {
- // Trick to call onDisconnected() in one place.
- if (shouldNotifyDisconnected) {
- if (DEBUG) {
- Log.d(TAG, "Notifying the controller of its disconnection");
- }
- try {
- caller.notifyDisconnected(0);
- } catch (RuntimeException e) {
- // Controller may be died prematurely.
- // Not an issue because we'll ignore it anyway.
- }
- }
- }
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void close() {
- mHandler.removeCallbacksAndMessages(null);
- mService.clear();
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/MediaTranscodingManager.java b/apex/media/framework/java/android/media/MediaTranscodingManager.java
deleted file mode 100644
index aff3204..0000000
--- a/apex/media/framework/java/android/media/MediaTranscodingManager.java
+++ /dev/null
@@ -1,1749 +0,0 @@
-/*
- * Copyright (C) 2019 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.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.system.Os;
-import android.util.Log;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.annotation.MinSdk;
-import com.android.modules.utils.build.SdkLevel;
-
-import java.io.FileNotFoundException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- Android 12 introduces Compatible media transcoding feature. See
- <a href="https://developer.android.com/about/versions/12/features#compatible_media_transcoding">
- Compatible media transcoding</a>. MediaTranscodingManager provides an interface to the system's media
- transcoding service and can be used to transcode media files, e.g. transcoding a video from HEVC to
- AVC.
-
- <h3>Transcoding Types</h3>
- <h4>Video Transcoding</h4>
- When transcoding a video file, the video track will be transcoded based on the desired track format
- and the audio track will be pass through without any modification.
- <p class=note>
- Note that currently only support transcoding video file in mp4 format and with single video track.
-
- <h3>Transcoding Request</h3>
- <p>
- To transcode a media file, first create a {@link TranscodingRequest} through its builder class
- {@link VideoTranscodingRequest.Builder}. Transcode requests are then enqueue to the manager through
- {@link MediaTranscodingManager#enqueueRequest(
- TranscodingRequest, Executor, OnTranscodingFinishedListener)}
- TranscodeRequest are processed based on client process's priority and request priority. When a
- transcode operation is completed the caller is notified via its
- {@link OnTranscodingFinishedListener}.
- In the meantime the caller may use the returned TranscodingSession object to cancel or check the
- status of a specific transcode operation.
- <p>
- Here is an example where <code>Builder</code> is used to specify all parameters
-
- <pre class=prettyprint>
- VideoTranscodingRequest request =
- new VideoTranscodingRequest.Builder(srcUri, dstUri, videoFormat).build();
- }</pre>
- @hide
- */
-@MinSdk(Build.VERSION_CODES.S)
-@RequiresApi(Build.VERSION_CODES.S)
-@SystemApi
-public final class MediaTranscodingManager {
- private static final String TAG = "MediaTranscodingManager";
-
- /** Maximum number of retry to connect to the service. */
- private static final int CONNECT_SERVICE_RETRY_COUNT = 100;
-
- /** Interval between trying to reconnect to the service. */
- private static final int INTERVAL_CONNECT_SERVICE_RETRY_MS = 40;
-
- /** Default bpp(bits-per-pixel) to use for calculating default bitrate. */
- private static final float BPP = 0.25f;
-
- /**
- * Listener that gets notified when a transcoding operation has finished.
- * This listener gets notified regardless of how the operation finished. It is up to the
- * listener implementation to check the result and take appropriate action.
- */
- @FunctionalInterface
- public interface OnTranscodingFinishedListener {
- /**
- * Called when the transcoding operation has finished. The receiver may use the
- * TranscodingSession to check the result, i.e. whether the operation succeeded, was
- * canceled or if an error occurred.
- *
- * @param session The TranscodingSession instance for the finished transcoding operation.
- */
- void onTranscodingFinished(@NonNull TranscodingSession session);
- }
-
- private final Context mContext;
- private ContentResolver mContentResolver;
- private final String mPackageName;
- private final int mPid;
- private final int mUid;
- private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
- private final HashMap<Integer, TranscodingSession> mPendingTranscodingSessions = new HashMap();
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- @NonNull private ITranscodingClient mTranscodingClient = null;
- private static MediaTranscodingManager sMediaTranscodingManager;
-
- private void handleTranscodingFinished(int sessionId, TranscodingResultParcel result) {
- synchronized (mPendingTranscodingSessions) {
- // Gets the session associated with the sessionId and removes it from
- // mPendingTranscodingSessions.
- final TranscodingSession session = mPendingTranscodingSessions.remove(sessionId);
-
- if (session == null) {
- // This should not happen in reality.
- Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions");
- return;
- }
-
- // Updates the session status and result.
- session.updateStatusAndResult(TranscodingSession.STATUS_FINISHED,
- TranscodingSession.RESULT_SUCCESS,
- TranscodingSession.ERROR_NONE);
-
- // Notifies client the session is done.
- if (session.mListener != null && session.mListenerExecutor != null) {
- session.mListenerExecutor.execute(
- () -> session.mListener.onTranscodingFinished(session));
- }
- }
- }
-
- private void handleTranscodingFailed(int sessionId, int errorCode) {
- synchronized (mPendingTranscodingSessions) {
- // Gets the session associated with the sessionId and removes it from
- // mPendingTranscodingSessions.
- final TranscodingSession session = mPendingTranscodingSessions.remove(sessionId);
-
- if (session == null) {
- // This should not happen in reality.
- Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions");
- return;
- }
-
- // Updates the session status and result.
- session.updateStatusAndResult(TranscodingSession.STATUS_FINISHED,
- TranscodingSession.RESULT_ERROR, errorCode);
-
- // Notifies client the session failed.
- if (session.mListener != null && session.mListenerExecutor != null) {
- session.mListenerExecutor.execute(
- () -> session.mListener.onTranscodingFinished(session));
- }
- }
- }
-
- private void handleTranscodingProgressUpdate(int sessionId, int newProgress) {
- synchronized (mPendingTranscodingSessions) {
- // Gets the session associated with the sessionId.
- final TranscodingSession session = mPendingTranscodingSessions.get(sessionId);
-
- if (session == null) {
- // This should not happen in reality.
- Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions");
- return;
- }
-
- // Updates the session progress.
- session.updateProgress(newProgress);
-
- // Notifies client the progress update.
- if (session.mProgressUpdateExecutor != null
- && session.mProgressUpdateListener != null) {
- session.mProgressUpdateExecutor.execute(
- () -> session.mProgressUpdateListener.onProgressUpdate(session,
- newProgress));
- }
- }
- }
-
- private IMediaTranscodingService getService(boolean retry) {
- // Do not try to get the service on pre-S. The service is lazy-start and getting the
- // service could block.
- if (!SdkLevel.isAtLeastS()) {
- return null;
- }
-
- int retryCount = !retry ? 1 : CONNECT_SERVICE_RETRY_COUNT;
- Log.i(TAG, "get service with retry " + retryCount);
- for (int count = 1; count <= retryCount; count++) {
- Log.d(TAG, "Trying to connect to service. Try count: " + count);
- IMediaTranscodingService service = IMediaTranscodingService.Stub.asInterface(
- MediaFrameworkInitializer
- .getMediaServiceManager()
- .getMediaTranscodingServiceRegisterer()
- .get());
- if (service != null) {
- return service;
- }
- try {
- // Sleep a bit before retry.
- Thread.sleep(INTERVAL_CONNECT_SERVICE_RETRY_MS);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- Log.w(TAG, "Failed to get service");
- return null;
- }
-
- /*
- * Handle client binder died event.
- * Upon receiving a binder died event of the client, we will do the following:
- * 1) For the session that is running, notify the client that the session is failed with
- * error code, so client could choose to retry the session or not.
- * TODO(hkuang): Add a new error code to signal service died error.
- * 2) For the sessions that is still pending or paused, we will resubmit the session
- * once we successfully reconnect to the service and register a new client.
- * 3) When trying to connect to the service and register a new client. The service may need time
- * to reboot or never boot up again. So we will retry for a number of times. If we still
- * could not connect, we will notify client session failure for the pending and paused
- * sessions.
- */
- private void onClientDied() {
- synchronized (mLock) {
- mTranscodingClient = null;
- }
-
- // Delegates the session notification and retry to the executor as it may take some time.
- mExecutor.execute(() -> {
- // List to track the sessions that we want to retry.
- List<TranscodingSession> retrySessions = new ArrayList<TranscodingSession>();
-
- // First notify the client of session failure for all the running sessions.
- synchronized (mPendingTranscodingSessions) {
- for (Map.Entry<Integer, TranscodingSession> entry :
- mPendingTranscodingSessions.entrySet()) {
- TranscodingSession session = entry.getValue();
-
- if (session.getStatus() == TranscodingSession.STATUS_RUNNING) {
- session.updateStatusAndResult(TranscodingSession.STATUS_FINISHED,
- TranscodingSession.RESULT_ERROR,
- TranscodingSession.ERROR_SERVICE_DIED);
-
- // Remove the session from pending sessions.
- mPendingTranscodingSessions.remove(entry.getKey());
-
- if (session.mListener != null && session.mListenerExecutor != null) {
- Log.i(TAG, "Notify client session failed");
- session.mListenerExecutor.execute(
- () -> session.mListener.onTranscodingFinished(session));
- }
- } else if (session.getStatus() == TranscodingSession.STATUS_PENDING
- || session.getStatus() == TranscodingSession.STATUS_PAUSED) {
- // Add the session to retrySessions to handle them later.
- retrySessions.add(session);
- }
- }
- }
-
- // Try to register with the service once it boots up.
- IMediaTranscodingService service = getService(true /*retry*/);
- boolean haveTranscodingClient = false;
- if (service != null) {
- synchronized (mLock) {
- mTranscodingClient = registerClient(service);
- if (mTranscodingClient != null) {
- haveTranscodingClient = true;
- }
- }
- }
-
- for (TranscodingSession session : retrySessions) {
- // Notify the session failure if we fails to connect to the service or fail
- // to retry the session.
- if (!haveTranscodingClient) {
- // TODO(hkuang): Return correct error code to the client.
- handleTranscodingFailed(session.getSessionId(), 0 /*unused */);
- }
-
- try {
- // Do not set hasRetried for retry initiated by MediaTranscodingManager.
- session.retryInternal(false /*setHasRetried*/);
- } catch (Exception re) {
- // TODO(hkuang): Return correct error code to the client.
- handleTranscodingFailed(session.getSessionId(), 0 /*unused */);
- }
- }
- });
- }
-
- private void updateStatus(int sessionId, int status) {
- synchronized (mPendingTranscodingSessions) {
- final TranscodingSession session = mPendingTranscodingSessions.get(sessionId);
-
- if (session == null) {
- // This should not happen in reality.
- Log.e(TAG, "Session " + sessionId + " is not in Pendingsessions");
- return;
- }
-
- // Updates the session status.
- session.updateStatus(status);
- }
- }
-
- // Just forwards all the events to the event handler.
- private ITranscodingClientCallback mTranscodingClientCallback =
- new ITranscodingClientCallback.Stub() {
- // TODO(hkuang): Add more unit test to test difference file open mode.
- @Override
- public ParcelFileDescriptor openFileDescriptor(String fileUri, String mode)
- throws RemoteException {
- if (!mode.equals("r") && !mode.equals("w") && !mode.equals("rw")) {
- Log.e(TAG, "Unsupport mode: " + mode);
- return null;
- }
-
- Uri uri = Uri.parse(fileUri);
- try {
- AssetFileDescriptor afd = mContentResolver.openAssetFileDescriptor(uri,
- mode);
- if (afd != null) {
- return afd.getParcelFileDescriptor();
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Cannot find content uri: " + uri, e);
- } catch (SecurityException e) {
- Log.w(TAG, "Cannot open content uri: " + uri, e);
- } catch (Exception e) {
- Log.w(TAG, "Unknown content uri: " + uri, e);
- }
- return null;
- }
-
- @Override
- public void onTranscodingStarted(int sessionId) throws RemoteException {
- updateStatus(sessionId, TranscodingSession.STATUS_RUNNING);
- }
-
- @Override
- public void onTranscodingPaused(int sessionId) throws RemoteException {
- updateStatus(sessionId, TranscodingSession.STATUS_PAUSED);
- }
-
- @Override
- public void onTranscodingResumed(int sessionId) throws RemoteException {
- updateStatus(sessionId, TranscodingSession.STATUS_RUNNING);
- }
-
- @Override
- public void onTranscodingFinished(int sessionId, TranscodingResultParcel result)
- throws RemoteException {
- handleTranscodingFinished(sessionId, result);
- }
-
- @Override
- public void onTranscodingFailed(int sessionId, int errorCode)
- throws RemoteException {
- handleTranscodingFailed(sessionId, errorCode);
- }
-
- @Override
- public void onAwaitNumberOfSessionsChanged(int sessionId, int oldAwaitNumber,
- int newAwaitNumber) throws RemoteException {
- //TODO(hkuang): Implement this.
- }
-
- @Override
- public void onProgressUpdate(int sessionId, int newProgress)
- throws RemoteException {
- handleTranscodingProgressUpdate(sessionId, newProgress);
- }
- };
-
- private ITranscodingClient registerClient(IMediaTranscodingService service) {
- synchronized (mLock) {
- try {
- // Registers the client with MediaTranscoding service.
- mTranscodingClient = service.registerClient(
- mTranscodingClientCallback,
- mPackageName,
- mPackageName);
-
- if (mTranscodingClient != null) {
- mTranscodingClient.asBinder().linkToDeath(() -> onClientDied(), /* flags */ 0);
- }
- } catch (Exception ex) {
- Log.e(TAG, "Failed to register new client due to exception " + ex);
- mTranscodingClient = null;
- }
- }
- return mTranscodingClient;
- }
-
- /**
- * @hide
- */
- public MediaTranscodingManager(@NonNull Context context) {
- mContext = context;
- mContentResolver = mContext.getContentResolver();
- mPackageName = mContext.getPackageName();
- mUid = Os.getuid();
- mPid = Os.getpid();
- }
-
- /**
- * Abstract base class for all the TranscodingRequest.
- * <p> TranscodingRequest encapsulates the desired configuration for the transcoding.
- */
- public abstract static class TranscodingRequest {
- /**
- *
- * Default transcoding type.
- * @hide
- */
- public static final int TRANSCODING_TYPE_UNKNOWN = 0;
-
- /**
- * TRANSCODING_TYPE_VIDEO indicates that client wants to perform transcoding on a video.
- * <p>Note that currently only support transcoding video file in mp4 format.
- * @hide
- */
- public static final int TRANSCODING_TYPE_VIDEO = 1;
-
- /**
- * TRANSCODING_TYPE_IMAGE indicates that client wants to perform transcoding on an image.
- * @hide
- */
- public static final int TRANSCODING_TYPE_IMAGE = 2;
-
- /** @hide */
- @IntDef(prefix = {"TRANSCODING_TYPE_"}, value = {
- TRANSCODING_TYPE_UNKNOWN,
- TRANSCODING_TYPE_VIDEO,
- TRANSCODING_TYPE_IMAGE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TranscodingType {}
-
- /**
- * Default value.
- *
- * @hide
- */
- public static final int PRIORITY_UNKNOWN = 0;
- /**
- * PRIORITY_REALTIME indicates that the transcoding request is time-critical and that the
- * client wants the transcoding result as soon as possible.
- * <p> Set PRIORITY_REALTIME only if the transcoding is time-critical as it will involve
- * performance penalty due to resource reallocation to prioritize the sessions with higher
- * priority.
- *
- * @hide
- */
- public static final int PRIORITY_REALTIME = 1;
-
- /**
- * PRIORITY_OFFLINE indicates the transcoding is not time-critical and the client does not
- * need the transcoding result as soon as possible.
- * <p>Sessions with PRIORITY_OFFLINE will be scheduled behind PRIORITY_REALTIME. Always set
- * to
- * PRIORITY_OFFLINE if client does not need the result as soon as possible and could accept
- * delay of the transcoding result.
- *
- * @hide
- *
- */
- public static final int PRIORITY_OFFLINE = 2;
-
- /** @hide */
- @IntDef(prefix = {"PRIORITY_"}, value = {
- PRIORITY_UNKNOWN,
- PRIORITY_REALTIME,
- PRIORITY_OFFLINE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TranscodingPriority {}
-
- /** Uri of the source media file. */
- private @NonNull Uri mSourceUri;
-
- /** Uri of the destination media file. */
- private @NonNull Uri mDestinationUri;
-
- /** FileDescriptor of the source media file. */
- private @Nullable ParcelFileDescriptor mSourceFileDescriptor;
-
- /** FileDescriptor of the destination media file. */
- private @Nullable ParcelFileDescriptor mDestinationFileDescriptor;
-
- /**
- * The UID of the client that the TranscodingRequest is for. Only privileged caller could
- * set this Uid as only they could do the transcoding on behalf of the client.
- * -1 means not available.
- */
- private int mClientUid = -1;
-
- /**
- * The Pid of the client that the TranscodingRequest is for. Only privileged caller could
- * set this Uid as only they could do the transcoding on behalf of the client.
- * -1 means not available.
- */
- private int mClientPid = -1;
-
- /** Type of the transcoding. */
- private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN;
-
- /** Priority of the transcoding. */
- private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
-
- /**
- * Desired image format for the destination file.
- * <p> If this is null, source file's image track will be passed through and copied to the
- * destination file.
- * @hide
- */
- private @Nullable MediaFormat mImageFormat = null;
-
- @VisibleForTesting
- private TranscodingTestConfig mTestConfig = null;
-
- /**
- * Prevent public constructor access.
- */
- /* package private */ TranscodingRequest() {
- }
-
- private TranscodingRequest(Builder b) {
- mSourceUri = b.mSourceUri;
- mSourceFileDescriptor = b.mSourceFileDescriptor;
- mDestinationUri = b.mDestinationUri;
- mDestinationFileDescriptor = b.mDestinationFileDescriptor;
- mClientUid = b.mClientUid;
- mClientPid = b.mClientPid;
- mPriority = b.mPriority;
- mType = b.mType;
- mTestConfig = b.mTestConfig;
- }
-
- /**
- * Return the type of the transcoding.
- * @hide
- */
- @TranscodingType
- public int getType() {
- return mType;
- }
-
- /** Return source uri of the transcoding. */
- @NonNull
- public Uri getSourceUri() {
- return mSourceUri;
- }
-
- /**
- * Return source file descriptor of the transcoding.
- * This will be null if client has not provided it.
- */
- @Nullable
- public ParcelFileDescriptor getSourceFileDescriptor() {
- return mSourceFileDescriptor;
- }
-
- /** Return the UID of the client that this request is for. -1 means not available. */
- public int getClientUid() {
- return mClientUid;
- }
-
- /** Return the PID of the client that this request is for. -1 means not available. */
- public int getClientPid() {
- return mClientPid;
- }
-
- /** Return destination uri of the transcoding. */
- @NonNull
- public Uri getDestinationUri() {
- return mDestinationUri;
- }
-
- /**
- * Return destination file descriptor of the transcoding.
- * This will be null if client has not provided it.
- */
- @Nullable
- public ParcelFileDescriptor getDestinationFileDescriptor() {
- return mDestinationFileDescriptor;
- }
-
- /**
- * Return priority of the transcoding.
- * @hide
- */
- @TranscodingPriority
- public int getPriority() {
- return mPriority;
- }
-
- /**
- * Return TestConfig of the transcoding.
- * @hide
- */
- @Nullable
- public TranscodingTestConfig getTestConfig() {
- return mTestConfig;
- }
-
- abstract void writeFormatToParcel(TranscodingRequestParcel parcel);
-
- /* Writes the TranscodingRequest to a parcel. */
- private TranscodingRequestParcel writeToParcel(@NonNull Context context) {
- TranscodingRequestParcel parcel = new TranscodingRequestParcel();
- switch (mPriority) {
- case PRIORITY_OFFLINE:
- parcel.priority = TranscodingSessionPriority.kUnspecified;
- break;
- case PRIORITY_REALTIME:
- case PRIORITY_UNKNOWN:
- default:
- parcel.priority = TranscodingSessionPriority.kNormal;
- break;
- }
- parcel.transcodingType = mType;
- parcel.sourceFilePath = mSourceUri.toString();
- parcel.sourceFd = mSourceFileDescriptor;
- parcel.destinationFilePath = mDestinationUri.toString();
- parcel.destinationFd = mDestinationFileDescriptor;
- parcel.clientUid = mClientUid;
- parcel.clientPid = mClientPid;
- if (mClientUid < 0) {
- parcel.clientPackageName = context.getPackageName();
- } else {
- String packageName = context.getPackageManager().getNameForUid(mClientUid);
- // PackageName is optional as some uid does not have package name. Set to
- // "Unavailable" string in this case.
- if (packageName == null) {
- Log.w(TAG, "Failed to find package for uid: " + mClientUid);
- packageName = "Unavailable";
- }
- parcel.clientPackageName = packageName;
- }
- writeFormatToParcel(parcel);
- if (mTestConfig != null) {
- parcel.isForTesting = true;
- parcel.testConfig = mTestConfig;
- }
- return parcel;
- }
-
- /**
- * Builder to build a {@link TranscodingRequest} object.
- *
- * @param <T> The subclass to be built.
- */
- abstract static class Builder<T extends Builder<T>> {
- private @NonNull Uri mSourceUri;
- private @NonNull Uri mDestinationUri;
- private @Nullable ParcelFileDescriptor mSourceFileDescriptor = null;
- private @Nullable ParcelFileDescriptor mDestinationFileDescriptor = null;
- private int mClientUid = -1;
- private int mClientPid = -1;
- private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN;
- private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
- private TranscodingTestConfig mTestConfig;
-
- abstract T self();
-
- /**
- * Creates a builder for building {@link TranscodingRequest}s.
- *
- * Client must set the source Uri. If client also provides the source fileDescriptor
- * through is provided by {@link #setSourceFileDescriptor(ParcelFileDescriptor)},
- * TranscodingSession will use the fd instead of calling back to the client to open the
- * sourceUri.
- *
- *
- * @param type The transcoding type.
- * @param sourceUri Content uri for the source media file.
- * @param destinationUri Content uri for the destination media file.
- *
- */
- private Builder(@TranscodingType int type, @NonNull Uri sourceUri,
- @NonNull Uri destinationUri) {
- mType = type;
-
- if (sourceUri == null || Uri.EMPTY.equals(sourceUri)) {
- throw new IllegalArgumentException(
- "You must specify a non-empty source Uri.");
- }
- mSourceUri = sourceUri;
-
- if (destinationUri == null || Uri.EMPTY.equals(destinationUri)) {
- throw new IllegalArgumentException(
- "You must specify a non-empty destination Uri.");
- }
- mDestinationUri = destinationUri;
- }
-
- /**
- * Specifies the fileDescriptor opened from the source media file.
- *
- * This call is optional. If the source fileDescriptor is provided, TranscodingSession
- * will use it directly instead of opening the uri from {@link #Builder(int, Uri, Uri)}.
- * It is client's responsibility to make sure the fileDescriptor is opened from the
- * source uri.
- * @param fileDescriptor a {@link ParcelFileDescriptor} opened from source media file.
- * @return The same builder instance.
- * @throws IllegalArgumentException if fileDescriptor is invalid.
- */
- @NonNull
- public T setSourceFileDescriptor(@NonNull ParcelFileDescriptor fileDescriptor) {
- if (fileDescriptor == null || fileDescriptor.getFd() < 0) {
- throw new IllegalArgumentException(
- "Invalid source descriptor.");
- }
- mSourceFileDescriptor = fileDescriptor;
- return self();
- }
-
- /**
- * Specifies the fileDescriptor opened from the destination media file.
- *
- * This call is optional. If the destination fileDescriptor is provided,
- * TranscodingSession will use it directly instead of opening the source uri from
- * {@link #Builder(int, Uri, Uri)} upon transcoding starts. It is client's
- * responsibility to make sure the fileDescriptor is opened from the destination uri.
- * @param fileDescriptor a {@link ParcelFileDescriptor} opened from destination media
- * file.
- * @return The same builder instance.
- * @throws IllegalArgumentException if fileDescriptor is invalid.
- */
- @NonNull
- public T setDestinationFileDescriptor(
- @NonNull ParcelFileDescriptor fileDescriptor) {
- if (fileDescriptor == null || fileDescriptor.getFd() < 0) {
- throw new IllegalArgumentException(
- "Invalid destination descriptor.");
- }
- mDestinationFileDescriptor = fileDescriptor;
- return self();
- }
-
- /**
- * Specify the UID of the client that this request is for.
- * <p>
- * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the
- * pid. Note that the permission check happens on the service side upon starting the
- * transcoding. If the client does not have the permission, the transcoding will fail.
- *
- * @param uid client Uid.
- * @return The same builder instance.
- * @throws IllegalArgumentException if uid is invalid.
- */
- @NonNull
- public T setClientUid(int uid) {
- if (uid < 0) {
- throw new IllegalArgumentException("Invalid Uid");
- }
- mClientUid = uid;
- return self();
- }
-
- /**
- * Specify the pid of the client that this request is for.
- * <p>
- * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could forward the
- * pid. Note that the permission check happens on the service side upon starting the
- * transcoding. If the client does not have the permission, the transcoding will fail.
- *
- * @param pid client Pid.
- * @return The same builder instance.
- * @throws IllegalArgumentException if pid is invalid.
- */
- @NonNull
- public T setClientPid(int pid) {
- if (pid < 0) {
- throw new IllegalArgumentException("Invalid pid");
- }
- mClientPid = pid;
- return self();
- }
-
- /**
- * Specifies the priority of the transcoding.
- *
- * @param priority Must be one of the {@code PRIORITY_*}
- * @return The same builder instance.
- * @throws IllegalArgumentException if flags is invalid.
- * @hide
- */
- @NonNull
- public T setPriority(@TranscodingPriority int priority) {
- if (priority != PRIORITY_OFFLINE && priority != PRIORITY_REALTIME) {
- throw new IllegalArgumentException("Invalid priority: " + priority);
- }
- mPriority = priority;
- return self();
- }
-
- /**
- * Sets the delay in processing this request.
- * @param config test config.
- * @return The same builder instance.
- * @hide
- */
- @VisibleForTesting
- @NonNull
- public T setTestConfig(@NonNull TranscodingTestConfig config) {
- mTestConfig = config;
- return self();
- }
- }
-
- /**
- * Abstract base class for all the format resolvers.
- */
- abstract static class MediaFormatResolver {
- private @NonNull ApplicationMediaCapabilities mClientCaps;
-
- /**
- * Prevents public constructor access.
- */
- /* package private */ MediaFormatResolver() {
- }
-
- /**
- * Constructs MediaFormatResolver object.
- *
- * @param clientCaps An ApplicationMediaCapabilities object containing the client's
- * capabilities.
- */
- MediaFormatResolver(@NonNull ApplicationMediaCapabilities clientCaps) {
- if (clientCaps == null) {
- throw new IllegalArgumentException("Client capabilities must not be null");
- }
- mClientCaps = clientCaps;
- }
-
- /**
- * Returns the client capabilities.
- */
- @NonNull
- /* package */ ApplicationMediaCapabilities getClientCapabilities() {
- return mClientCaps;
- }
-
- abstract boolean shouldTranscode();
- }
-
- /**
- * VideoFormatResolver for deciding if video transcoding is needed, and if so, the track
- * formats to use.
- */
- public static class VideoFormatResolver extends MediaFormatResolver {
- private static final int BIT_RATE = 20000000; // 20Mbps
-
- private MediaFormat mSrcVideoFormatHint;
- private MediaFormat mSrcAudioFormatHint;
-
- /**
- * Constructs a new VideoFormatResolver object.
- *
- * @param clientCaps An ApplicationMediaCapabilities object containing the client's
- * capabilities.
- * @param srcVideoFormatHint A MediaFormat object containing information about the
- * source's video track format that could affect the
- * transcoding decision. Such information could include video
- * codec types, color spaces, whether special format info (eg.
- * slow-motion markers) are present, etc.. If a particular
- * information is not present, it will not be used to make the
- * decision.
- */
- public VideoFormatResolver(@NonNull ApplicationMediaCapabilities clientCaps,
- @NonNull MediaFormat srcVideoFormatHint) {
- super(clientCaps);
- mSrcVideoFormatHint = srcVideoFormatHint;
- }
-
- /**
- * Constructs a new VideoFormatResolver object.
- *
- * @param clientCaps An ApplicationMediaCapabilities object containing the client's
- * capabilities.
- * @param srcVideoFormatHint A MediaFormat object containing information about the
- * source's video track format that could affect the
- * transcoding decision. Such information could include video
- * codec types, color spaces, whether special format info (eg.
- * slow-motion markers) are present, etc.. If a particular
- * information is not present, it will not be used to make the
- * decision.
- * @param srcAudioFormatHint A MediaFormat object containing information about the
- * source's audio track format that could affect the
- * transcoding decision.
- * @hide
- */
- VideoFormatResolver(@NonNull ApplicationMediaCapabilities clientCaps,
- @NonNull MediaFormat srcVideoFormatHint,
- @NonNull MediaFormat srcAudioFormatHint) {
- super(clientCaps);
- mSrcVideoFormatHint = srcVideoFormatHint;
- mSrcAudioFormatHint = srcAudioFormatHint;
- }
-
- /**
- * Returns whether the source content should be transcoded.
- *
- * @return true if the source should be transcoded.
- */
- public boolean shouldTranscode() {
- boolean supportHevc = getClientCapabilities().isVideoMimeTypeSupported(
- MediaFormat.MIMETYPE_VIDEO_HEVC);
- if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals(
- mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) {
- return true;
- }
- // TODO: add more checks as needed below.
- return false;
- }
-
- /**
- * Retrieves the video track format to be used on
- * {@link VideoTranscodingRequest.Builder#setVideoTrackFormat(MediaFormat)} for this
- * configuration.
- *
- * @return the video track format to be used if transcoding should be performed,
- * and null otherwise.
- * @throws IllegalArgumentException if the hinted source video format contains invalid
- * parameters.
- */
- @Nullable
- public MediaFormat resolveVideoFormat() {
- if (!shouldTranscode()) {
- return null;
- }
-
- MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint);
- videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
-
- int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH, -1);
- int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT, -1);
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException(
- "Source Width and height must be larger than 0");
- }
-
- float frameRate =
- mSrcVideoFormatHint.getNumber(MediaFormat.KEY_FRAME_RATE, 30.0)
- .floatValue();
- if (frameRate <= 0) {
- throw new IllegalArgumentException(
- "frameRate must be larger than 0");
- }
-
- int bitrate = getAVCBitrate(width, height, frameRate);
- videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
- return videoTrackFormat;
- }
-
- /**
- * Generate a default bitrate with the fixed bpp(bits-per-pixel) 0.25.
- * This maps to:
- * 1080P@30fps -> 16Mbps
- * 1080P@60fps-> 32Mbps
- * 4K@30fps -> 62Mbps
- */
- private static int getDefaultBitrate(int width, int height, float frameRate) {
- return (int) (width * height * frameRate * BPP);
- }
-
- /**
- * Query the bitrate from CamcorderProfile. If there are two profiles that match the
- * width/height/framerate, we will use the higher one to get better quality.
- * Return default bitrate if could not find any match profile.
- */
- private static int getAVCBitrate(int width, int height, float frameRate) {
- int bitrate = -1;
- int[] cameraIds = {0, 1};
-
- // Profiles ordered in decreasing order of preference.
- int[] preferQualities = {
- CamcorderProfile.QUALITY_2160P,
- CamcorderProfile.QUALITY_1080P,
- CamcorderProfile.QUALITY_720P,
- CamcorderProfile.QUALITY_480P,
- CamcorderProfile.QUALITY_LOW,
- };
-
- for (int cameraId : cameraIds) {
- for (int quality : preferQualities) {
- // Check if camera id has profile for the quality level.
- if (!CamcorderProfile.hasProfile(cameraId, quality)) {
- continue;
- }
- CamcorderProfile profile = CamcorderProfile.get(cameraId, quality);
- // Check the width/height/framerate/codec, also consider portrait case.
- if (((width == profile.videoFrameWidth
- && height == profile.videoFrameHeight)
- || (height == profile.videoFrameWidth
- && width == profile.videoFrameHeight))
- && (int) frameRate == profile.videoFrameRate
- && profile.videoCodec == MediaRecorder.VideoEncoder.H264) {
- if (bitrate < profile.videoBitRate) {
- bitrate = profile.videoBitRate;
- }
- break;
- }
- }
- }
-
- if (bitrate == -1) {
- Log.w(TAG, "Failed to find CamcorderProfile for w: " + width + "h: " + height
- + " fps: "
- + frameRate);
- bitrate = getDefaultBitrate(width, height, frameRate);
- }
- Log.d(TAG, "Using bitrate " + bitrate + " for " + width + " " + height + " "
- + frameRate);
- return bitrate;
- }
-
- /**
- * Retrieves the audio track format to be used for transcoding.
- *
- * @return the audio track format to be used if transcoding should be performed, and
- * null otherwise.
- * @hide
- */
- @Nullable
- public MediaFormat resolveAudioFormat() {
- if (!shouldTranscode()) {
- return null;
- }
- // Audio transcoding is not supported yet, always return null.
- return null;
- }
- }
- }
-
- /**
- * VideoTranscodingRequest encapsulates the configuration for transcoding a video.
- */
- public static final class VideoTranscodingRequest extends TranscodingRequest {
- /**
- * Desired output video format of the destination file.
- * <p> If this is null, source file's video track will be passed through and copied to the
- * destination file.
- */
- private @Nullable MediaFormat mVideoTrackFormat = null;
-
- /**
- * Desired output audio format of the destination file.
- * <p> If this is null, source file's audio track will be passed through and copied to the
- * destination file.
- */
- private @Nullable MediaFormat mAudioTrackFormat = null;
-
- private VideoTranscodingRequest(VideoTranscodingRequest.Builder builder) {
- super(builder);
- mVideoTrackFormat = builder.mVideoTrackFormat;
- mAudioTrackFormat = builder.mAudioTrackFormat;
- }
-
- /**
- * Return the video track format of the transcoding.
- * This will be null if client has not specified the video track format.
- */
- @NonNull
- public MediaFormat getVideoTrackFormat() {
- return mVideoTrackFormat;
- }
-
- @Override
- void writeFormatToParcel(TranscodingRequestParcel parcel) {
- parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
- }
-
- /* Converts the MediaFormat to TranscodingVideoTrackFormat. */
- private static TranscodingVideoTrackFormat convertToVideoTrackFormat(MediaFormat format) {
- if (format == null) {
- throw new IllegalArgumentException("Invalid MediaFormat");
- }
-
- TranscodingVideoTrackFormat trackFormat = new TranscodingVideoTrackFormat();
-
- if (format.containsKey(MediaFormat.KEY_MIME)) {
- String mime = format.getString(MediaFormat.KEY_MIME);
- if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
- trackFormat.codecType = TranscodingVideoCodecType.kAvc;
- } else if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
- trackFormat.codecType = TranscodingVideoCodecType.kHevc;
- } else {
- throw new UnsupportedOperationException("Only support transcode to avc/hevc");
- }
- }
-
- if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
- int bitrateBps = format.getInteger(MediaFormat.KEY_BIT_RATE);
- if (bitrateBps <= 0) {
- throw new IllegalArgumentException("Bitrate must be larger than 0");
- }
- trackFormat.bitrateBps = bitrateBps;
- }
-
- if (format.containsKey(MediaFormat.KEY_WIDTH) && format.containsKey(
- MediaFormat.KEY_HEIGHT)) {
- int width = format.getInteger(MediaFormat.KEY_WIDTH);
- int height = format.getInteger(MediaFormat.KEY_HEIGHT);
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("Width and height must be larger than 0");
- }
- // TODO: Validate the aspect ratio after adding scaling.
- trackFormat.width = width;
- trackFormat.height = height;
- }
-
- if (format.containsKey(MediaFormat.KEY_PROFILE)) {
- int profile = format.getInteger(MediaFormat.KEY_PROFILE);
- if (profile <= 0) {
- throw new IllegalArgumentException("Invalid codec profile");
- }
- // TODO: Validate the profile according to codec type.
- trackFormat.profile = profile;
- }
-
- if (format.containsKey(MediaFormat.KEY_LEVEL)) {
- int level = format.getInteger(MediaFormat.KEY_LEVEL);
- if (level <= 0) {
- throw new IllegalArgumentException("Invalid codec level");
- }
- // TODO: Validate the level according to codec type.
- trackFormat.level = level;
- }
-
- return trackFormat;
- }
-
- /**
- * Builder class for {@link VideoTranscodingRequest}.
- */
- public static final class Builder extends
- TranscodingRequest.Builder<VideoTranscodingRequest.Builder> {
- /**
- * Desired output video format of the destination file.
- * <p> If this is null, source file's video track will be passed through and
- * copied to the destination file.
- */
- private @Nullable MediaFormat mVideoTrackFormat = null;
-
- /**
- * Desired output audio format of the destination file.
- * <p> If this is null, source file's audio track will be passed through and copied
- * to the destination file.
- */
- private @Nullable MediaFormat mAudioTrackFormat = null;
-
- /**
- * Creates a builder for building {@link VideoTranscodingRequest}s.
- *
- * <p> Client could only specify the settings that matters to them, e.g. codec format or
- * bitrate. And by default, transcoding will preserve the original video's settings
- * (bitrate, framerate, resolution) if not provided.
- * <p>Note that some settings may silently fail to apply if the device does not support
- * them.
- * @param sourceUri Content uri for the source media file.
- * @param destinationUri Content uri for the destination media file.
- * @param videoFormat MediaFormat containing the settings that client wants override in
- * the original video's video track.
- * @throws IllegalArgumentException if videoFormat is invalid.
- */
- public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri,
- @NonNull MediaFormat videoFormat) {
- super(TRANSCODING_TYPE_VIDEO, sourceUri, destinationUri);
- setVideoTrackFormat(videoFormat);
- }
-
- @Override
- @NonNull
- public Builder setClientUid(int uid) {
- super.setClientUid(uid);
- return self();
- }
-
- @Override
- @NonNull
- public Builder setClientPid(int pid) {
- super.setClientPid(pid);
- return self();
- }
-
- @Override
- @NonNull
- public Builder setSourceFileDescriptor(@NonNull ParcelFileDescriptor fd) {
- super.setSourceFileDescriptor(fd);
- return self();
- }
-
- @Override
- @NonNull
- public Builder setDestinationFileDescriptor(@NonNull ParcelFileDescriptor fd) {
- super.setDestinationFileDescriptor(fd);
- return self();
- }
-
- private void setVideoTrackFormat(@NonNull MediaFormat videoFormat) {
- if (videoFormat == null) {
- throw new IllegalArgumentException("videoFormat must not be null");
- }
-
- // Check if the MediaFormat is for video by looking at the MIME type.
- String mime = videoFormat.containsKey(MediaFormat.KEY_MIME)
- ? videoFormat.getString(MediaFormat.KEY_MIME) : null;
- if (mime == null || !mime.startsWith("video/")) {
- throw new IllegalArgumentException("Invalid video format: wrong mime type");
- }
-
- mVideoTrackFormat = videoFormat;
- }
-
- /**
- * @return a new {@link TranscodingRequest} instance successfully initialized
- * with all the parameters set on this <code>Builder</code>.
- * @throws UnsupportedOperationException if the parameters set on the
- * <code>Builder</code> were incompatible, or
- * if they are not supported by the
- * device.
- */
- @NonNull
- public VideoTranscodingRequest build() {
- return new VideoTranscodingRequest(this);
- }
-
- @Override
- VideoTranscodingRequest.Builder self() {
- return this;
- }
- }
- }
-
- /**
- * Handle to an enqueued transcoding operation. An instance of this class represents a single
- * enqueued transcoding operation. The caller can use that instance to query the status or
- * progress, and to get the result once the operation has completed.
- */
- public static final class TranscodingSession {
- /** The session is enqueued but not yet running. */
- public static final int STATUS_PENDING = 1;
- /** The session is currently running. */
- public static final int STATUS_RUNNING = 2;
- /** The session is finished. */
- public static final int STATUS_FINISHED = 3;
- /** The session is paused. */
- public static final int STATUS_PAUSED = 4;
-
- /** @hide */
- @IntDef(prefix = { "STATUS_" }, value = {
- STATUS_PENDING,
- STATUS_RUNNING,
- STATUS_FINISHED,
- STATUS_PAUSED,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Status {}
-
- /** The session does not have a result yet. */
- public static final int RESULT_NONE = 1;
- /** The session completed successfully. */
- public static final int RESULT_SUCCESS = 2;
- /** The session encountered an error while running. */
- public static final int RESULT_ERROR = 3;
- /** The session was canceled by the caller. */
- public static final int RESULT_CANCELED = 4;
-
- /** @hide */
- @IntDef(prefix = { "RESULT_" }, value = {
- RESULT_NONE,
- RESULT_SUCCESS,
- RESULT_ERROR,
- RESULT_CANCELED,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Result {}
-
-
- // The error code exposed here should be in sync with:
- // frameworks/av/media/libmediatranscoding/aidl/android/media/TranscodingErrorCode.aidl
- /** @hide */
- @IntDef(prefix = { "TRANSCODING_SESSION_ERROR_" }, value = {
- ERROR_NONE,
- ERROR_DROPPED_BY_SERVICE,
- ERROR_SERVICE_DIED})
- @Retention(RetentionPolicy.SOURCE)
- public @interface TranscodingSessionErrorCode{}
- /**
- * Constant indicating that no error occurred.
- */
- public static final int ERROR_NONE = 0;
-
- /**
- * Constant indicating that the session is dropped by Transcoding service due to hitting
- * the limit, e.g. too many back to back transcoding happen in a short time frame.
- */
- public static final int ERROR_DROPPED_BY_SERVICE = 1;
-
- /**
- * Constant indicating the backing transcoding service is died. Client should enqueue the
- * the request again.
- */
- public static final int ERROR_SERVICE_DIED = 2;
-
- /** Listener that gets notified when the progress changes. */
- @FunctionalInterface
- public interface OnProgressUpdateListener {
- /**
- * Called when the progress changes. The progress is in percentage between 0 and 1,
- * where 0 means the session has not yet started and 100 means that it has finished.
- *
- * @param session The session associated with the progress.
- * @param progress The new progress ranging from 0 ~ 100 inclusive.
- */
- void onProgressUpdate(@NonNull TranscodingSession session,
- @IntRange(from = 0, to = 100) int progress);
- }
-
- private final MediaTranscodingManager mManager;
- private Executor mListenerExecutor;
- private OnTranscodingFinishedListener mListener;
- private int mSessionId = -1;
- // Lock for internal state.
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private Executor mProgressUpdateExecutor = null;
- @GuardedBy("mLock")
- private OnProgressUpdateListener mProgressUpdateListener = null;
- @GuardedBy("mLock")
- private int mProgress = 0;
- @GuardedBy("mLock")
- private int mProgressUpdateInterval = 0;
- @GuardedBy("mLock")
- private @Status int mStatus = STATUS_PENDING;
- @GuardedBy("mLock")
- private @Result int mResult = RESULT_NONE;
- @GuardedBy("mLock")
- private @TranscodingSessionErrorCode int mErrorCode = ERROR_NONE;
- @GuardedBy("mLock")
- private boolean mHasRetried = false;
- // The original request that associated with this session.
- private final TranscodingRequest mRequest;
-
- private TranscodingSession(
- @NonNull MediaTranscodingManager manager,
- @NonNull TranscodingRequest request,
- @NonNull TranscodingSessionParcel parcel,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnTranscodingFinishedListener listener) {
- Objects.requireNonNull(manager, "manager must not be null");
- Objects.requireNonNull(parcel, "parcel must not be null");
- Objects.requireNonNull(executor, "listenerExecutor must not be null");
- Objects.requireNonNull(listener, "listener must not be null");
- mManager = manager;
- mSessionId = parcel.sessionId;
- mListenerExecutor = executor;
- mListener = listener;
- mRequest = request;
- }
-
- /**
- * Set a progress listener.
- * @param executor The executor on which listener will be invoked.
- * @param listener The progress listener.
- */
- public void setOnProgressUpdateListener(
- @NonNull @CallbackExecutor Executor executor,
- @Nullable OnProgressUpdateListener listener) {
- synchronized (mLock) {
- Objects.requireNonNull(executor, "listenerExecutor must not be null");
- Objects.requireNonNull(listener, "listener must not be null");
- mProgressUpdateExecutor = executor;
- mProgressUpdateListener = listener;
- }
- }
-
- private void updateStatusAndResult(@Status int sessionStatus,
- @Result int sessionResult, @TranscodingSessionErrorCode int errorCode) {
- synchronized (mLock) {
- mStatus = sessionStatus;
- mResult = sessionResult;
- mErrorCode = errorCode;
- }
- }
-
- /**
- * Retrieve the error code associated with the RESULT_ERROR.
- */
- public @TranscodingSessionErrorCode int getErrorCode() {
- synchronized (mLock) {
- return mErrorCode;
- }
- }
-
- /**
- * Resubmit the transcoding session to the service.
- * Note that only the session that fails or gets cancelled could be retried and each session
- * could be retried only once. After that, Client need to enqueue a new request if they want
- * to try again.
- *
- * @return true if successfully resubmit the job to service. False otherwise.
- * @throws UnsupportedOperationException if the retry could not be fulfilled.
- * @hide
- */
- public boolean retry() {
- return retryInternal(true /*setHasRetried*/);
- }
-
- // TODO(hkuang): Add more test for it.
- private boolean retryInternal(boolean setHasRetried) {
- synchronized (mLock) {
- if (mStatus == STATUS_PENDING || mStatus == STATUS_RUNNING) {
- throw new UnsupportedOperationException(
- "Failed to retry as session is in processing");
- }
-
- if (mHasRetried) {
- throw new UnsupportedOperationException("Session has been retried already");
- }
-
- // Get the client interface.
- ITranscodingClient client = mManager.getTranscodingClient();
- if (client == null) {
- Log.e(TAG, "Service rebooting. Try again later");
- return false;
- }
-
- synchronized (mManager.mPendingTranscodingSessions) {
- try {
- // Submits the request to MediaTranscoding service.
- TranscodingSessionParcel sessionParcel = new TranscodingSessionParcel();
- if (!client.submitRequest(mRequest.writeToParcel(mManager.mContext),
- sessionParcel)) {
- mHasRetried = true;
- throw new UnsupportedOperationException("Failed to enqueue request");
- }
-
- // Replace the old session id wit the new one.
- mSessionId = sessionParcel.sessionId;
- // Adds the new session back into pending sessions.
- mManager.mPendingTranscodingSessions.put(mSessionId, this);
- } catch (RemoteException re) {
- return false;
- }
- mStatus = STATUS_PENDING;
- mHasRetried = setHasRetried ? true : false;
- }
- }
- return true;
- }
-
- /**
- * Cancels the transcoding session and notify the listener.
- * If the session happened to finish before being canceled this call is effectively a no-op
- * and will not update the result in that case.
- */
- public void cancel() {
- synchronized (mLock) {
- // Check if the session is finished already.
- if (mStatus != STATUS_FINISHED) {
- try {
- ITranscodingClient client = mManager.getTranscodingClient();
- // The client may be gone.
- if (client != null) {
- client.cancelSession(mSessionId);
- }
- } catch (RemoteException re) {
- //TODO(hkuang): Find out what to do if failing to cancel the session.
- Log.e(TAG, "Failed to cancel the session due to exception: " + re);
- }
- mStatus = STATUS_FINISHED;
- mResult = RESULT_CANCELED;
-
- // Notifies client the session is canceled.
- mListenerExecutor.execute(() -> mListener.onTranscodingFinished(this));
- }
- }
- }
-
- /**
- * Gets the progress of the transcoding session. The progress is between 0 and 100, where 0
- * means that the session has not yet started and 100 means that it is finished. For the
- * cancelled session, the progress will be the last updated progress before it is cancelled.
- * @return The progress.
- */
- @IntRange(from = 0, to = 100)
- public int getProgress() {
- synchronized (mLock) {
- return mProgress;
- }
- }
-
- /**
- * Gets the status of the transcoding session.
- * @return The status.
- */
- public @Status int getStatus() {
- synchronized (mLock) {
- return mStatus;
- }
- }
-
- /**
- * Adds a client uid that is also waiting for this transcoding session.
- * <p>
- * Only privilege caller with android.permission.WRITE_MEDIA_STORAGE could add the
- * uid. Note that the permission check happens on the service side upon starting the
- * transcoding. If the client does not have the permission, the transcoding will fail.
- * @param uid the additional client uid to be added.
- * @return true if successfully added, false otherwise.
- */
- public boolean addClientUid(int uid) {
- if (uid < 0) {
- throw new IllegalArgumentException("Invalid Uid");
- }
-
- // Get the client interface.
- ITranscodingClient client = mManager.getTranscodingClient();
- if (client == null) {
- Log.e(TAG, "Service is dead...");
- return false;
- }
-
- try {
- if (!client.addClientUid(mSessionId, uid)) {
- Log.e(TAG, "Failed to add client uid");
- return false;
- }
- } catch (Exception ex) {
- Log.e(TAG, "Failed to get client uids due to " + ex);
- return false;
- }
- return true;
- }
-
- /**
- * Query all the client that waiting for this transcoding session
- * @return a list containing all the client uids.
- */
- @NonNull
- public List<Integer> getClientUids() {
- List<Integer> uidList = new ArrayList<Integer>();
-
- // Get the client interface.
- ITranscodingClient client = mManager.getTranscodingClient();
- if (client == null) {
- Log.e(TAG, "Service is dead...");
- return uidList;
- }
-
- try {
- int[] clientUids = client.getClientUids(mSessionId);
- for (int i : clientUids) {
- uidList.add(i);
- }
- } catch (Exception ex) {
- Log.e(TAG, "Failed to get client uids due to " + ex);
- }
-
- return uidList;
- }
-
- /**
- * Gets sessionId of the transcoding session.
- * @return session id.
- */
- public int getSessionId() {
- return mSessionId;
- }
-
- /**
- * Gets the result of the transcoding session.
- * @return The result.
- */
- public @Result int getResult() {
- synchronized (mLock) {
- return mResult;
- }
- }
-
- @Override
- public String toString() {
- String result;
- String status;
-
- switch (mResult) {
- case RESULT_NONE:
- result = "RESULT_NONE";
- break;
- case RESULT_SUCCESS:
- result = "RESULT_SUCCESS";
- break;
- case RESULT_ERROR:
- result = "RESULT_ERROR(" + mErrorCode + ")";
- break;
- case RESULT_CANCELED:
- result = "RESULT_CANCELED";
- break;
- default:
- result = String.valueOf(mResult);
- break;
- }
-
- switch (mStatus) {
- case STATUS_PENDING:
- status = "STATUS_PENDING";
- break;
- case STATUS_PAUSED:
- status = "STATUS_PAUSED";
- break;
- case STATUS_RUNNING:
- status = "STATUS_RUNNING";
- break;
- case STATUS_FINISHED:
- status = "STATUS_FINISHED";
- break;
- default:
- status = String.valueOf(mStatus);
- break;
- }
- return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}",
- mSessionId, status, result, mProgress);
- }
-
- private void updateProgress(int newProgress) {
- synchronized (mLock) {
- mProgress = newProgress;
- }
- }
-
- private void updateStatus(int newStatus) {
- synchronized (mLock) {
- mStatus = newStatus;
- }
- }
- }
-
- private ITranscodingClient getTranscodingClient() {
- synchronized (mLock) {
- return mTranscodingClient;
- }
- }
-
- /**
- * Enqueues a TranscodingRequest for execution.
- * <p> Upon successfully accepting the request, MediaTranscodingManager will return a
- * {@link TranscodingSession} to the client. Client should use {@link TranscodingSession} to
- * track the progress and get the result.
- * <p> MediaTranscodingManager will return null if fails to accept the request due to service
- * rebooting. Client could retry again after receiving null.
- *
- * @param transcodingRequest The TranscodingRequest to enqueue.
- * @param listenerExecutor Executor on which the listener is notified.
- * @param listener Listener to get notified when the transcoding session is finished.
- * @return A TranscodingSession for this operation.
- * @throws UnsupportedOperationException if the request could not be fulfilled.
- */
- @Nullable
- public TranscodingSession enqueueRequest(
- @NonNull TranscodingRequest transcodingRequest,
- @NonNull @CallbackExecutor Executor listenerExecutor,
- @NonNull OnTranscodingFinishedListener listener) {
- Log.i(TAG, "enqueueRequest called.");
- Objects.requireNonNull(transcodingRequest, "transcodingRequest must not be null");
- Objects.requireNonNull(listenerExecutor, "listenerExecutor must not be null");
- Objects.requireNonNull(listener, "listener must not be null");
-
- // Converts the request to TranscodingRequestParcel.
- TranscodingRequestParcel requestParcel = transcodingRequest.writeToParcel(mContext);
-
- Log.i(TAG, "Getting transcoding request " + transcodingRequest.getSourceUri());
-
- // Submits the request to MediaTranscoding service.
- try {
- TranscodingSessionParcel sessionParcel = new TranscodingSessionParcel();
- // Synchronizes the access to mPendingTranscodingSessions to make sure the session Id is
- // inserted in the mPendingTranscodingSessions in the callback handler.
- synchronized (mPendingTranscodingSessions) {
- synchronized (mLock) {
- if (mTranscodingClient == null) {
- // Try to register with the service again.
- IMediaTranscodingService service = getService(false /*retry*/);
- if (service == null) {
- Log.w(TAG, "Service rebooting. Try again later");
- return null;
- }
- mTranscodingClient = registerClient(service);
- // If still fails, throws an exception to tell client to try later.
- if (mTranscodingClient == null) {
- Log.w(TAG, "Service rebooting. Try again later");
- return null;
- }
- }
-
- if (!mTranscodingClient.submitRequest(requestParcel, sessionParcel)) {
- throw new UnsupportedOperationException("Failed to enqueue request");
- }
- }
-
- // Wraps the TranscodingSessionParcel into a TranscodingSession and returns it to
- // client for tracking.
- TranscodingSession session = new TranscodingSession(this, transcodingRequest,
- sessionParcel,
- listenerExecutor,
- listener);
-
- // Adds the new session into pending sessions.
- mPendingTranscodingSessions.put(session.getSessionId(), session);
- return session;
- }
- } catch (RemoteException ex) {
- Log.w(TAG, "Service rebooting. Try again later");
- return null;
- } catch (ServiceSpecificException ex) {
- throw new UnsupportedOperationException(
- "Failed to submit request to Transcoding service. Error: " + ex);
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/ProxyDataSourceCallback.java b/apex/media/framework/java/android/media/ProxyDataSourceCallback.java
deleted file mode 100644
index 14d3ce8..0000000
--- a/apex/media/framework/java/android/media/ProxyDataSourceCallback.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2019 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.media;
-
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * A DataSourceCallback that is backed by a ParcelFileDescriptor.
- */
-class ProxyDataSourceCallback extends DataSourceCallback {
- private static final String TAG = "TestDataSourceCallback";
-
- ParcelFileDescriptor mPFD;
- FileDescriptor mFD;
-
- ProxyDataSourceCallback(ParcelFileDescriptor pfd) throws IOException {
- mPFD = pfd.dup();
- mFD = mPFD.getFileDescriptor();
- }
-
- @Override
- public synchronized int readAt(long position, byte[] buffer, int offset, int size)
- throws IOException {
- try {
- Os.lseek(mFD, position, OsConstants.SEEK_SET);
- int ret = Os.read(mFD, buffer, offset, size);
- return (ret == 0) ? END_OF_STREAM : ret;
- } catch (ErrnoException e) {
- throw new IOException(e);
- }
- }
-
- @Override
- public synchronized long getSize() throws IOException {
- return mPFD.getStatSize();
- }
-
- @Override
- public synchronized void close() {
- try {
- mPFD.close();
- } catch (IOException e) {
- Log.e(TAG, "Failed to close the PFD.", e);
- }
- }
-}
-
diff --git a/apex/media/framework/java/android/media/RoutingDelegate.java b/apex/media/framework/java/android/media/RoutingDelegate.java
deleted file mode 100644
index 2359813..0000000
--- a/apex/media/framework/java/android/media/RoutingDelegate.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2018 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.media;
-
-import android.os.Handler;
-
-class RoutingDelegate implements AudioRouting.OnRoutingChangedListener {
- private AudioRouting mAudioRouting;
- private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
- private Handler mHandler;
-
- RoutingDelegate(final AudioRouting audioRouting,
- final AudioRouting.OnRoutingChangedListener listener,
- Handler handler) {
- mAudioRouting = audioRouting;
- mOnRoutingChangedListener = listener;
- mHandler = handler;
- }
-
- public AudioRouting.OnRoutingChangedListener getListener() {
- return mOnRoutingChangedListener;
- }
-
- public Handler getHandler() {
- return mHandler;
- }
-
- @Override
- public void onRoutingChanged(AudioRouting router) {
- if (mOnRoutingChangedListener != null) {
- mOnRoutingChangedListener.onRoutingChanged(mAudioRouting);
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/Session2Command.java b/apex/media/framework/java/android/media/Session2Command.java
deleted file mode 100644
index 7e71591..0000000
--- a/apex/media/framework/java/android/media/Session2Command.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.Objects;
-
-/**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
- * <p>
- * If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command.
- * If {@link #getCommandCode()} is {@link #COMMAND_CODE_CUSTOM}), it's custom command and
- * {@link #getCustomAction()} shouldn't be {@code null}.
- * <p>
- * Refer to the <a href="{@docRoot}reference/androidx/media2/session/SessionCommand.html">
- * AndroidX SessionCommand</a> class for the list of valid commands.
- */
-public final class Session2Command implements Parcelable {
- /**
- * Command code for the custom command which can be defined by string action in the
- * {@link Session2Command}.
- */
- public static final int COMMAND_CODE_CUSTOM = 0;
-
- public static final @android.annotation.NonNull Parcelable.Creator<Session2Command> CREATOR =
- new Parcelable.Creator<Session2Command>() {
- @Override
- public Session2Command createFromParcel(Parcel in) {
- return new Session2Command(in);
- }
-
- @Override
- public Session2Command[] newArray(int size) {
- return new Session2Command[size];
- }
- };
-
- private final int mCommandCode;
- // Nonnull if it's custom command
- private final String mCustomAction;
- private final Bundle mCustomExtras;
-
- /**
- * Constructor for creating a command predefined in AndroidX media2.
- *
- * @param commandCode A command code for a command predefined in AndroidX media2.
- */
- public Session2Command(int commandCode) {
- if (commandCode == COMMAND_CODE_CUSTOM) {
- throw new IllegalArgumentException("commandCode shouldn't be COMMAND_CODE_CUSTOM");
- }
- mCommandCode = commandCode;
- mCustomAction = null;
- mCustomExtras = null;
- }
-
- /**
- * Constructor for creating a custom command.
- *
- * @param action The action of this custom command.
- * @param extras An extra bundle for this custom command.
- */
- public Session2Command(@NonNull String action, @Nullable Bundle extras) {
- if (action == null) {
- throw new IllegalArgumentException("action shouldn't be null");
- }
- mCommandCode = COMMAND_CODE_CUSTOM;
- mCustomAction = action;
- mCustomExtras = extras;
- }
-
- /**
- * Used by parcelable creator.
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- Session2Command(Parcel in) {
- mCommandCode = in.readInt();
- mCustomAction = in.readString();
- mCustomExtras = in.readBundle();
- }
-
- /**
- * Gets the command code of a predefined command.
- * This will return {@link #COMMAND_CODE_CUSTOM} for a custom command.
- */
- public int getCommandCode() {
- return mCommandCode;
- }
-
- /**
- * Gets the action of a custom command.
- * This will return {@code null} for a predefined command.
- */
- @Nullable
- public String getCustomAction() {
- return mCustomAction;
- }
-
- /**
- * Gets the extra bundle of a custom command.
- * This will return {@code null} for a predefined command.
- */
- @Nullable
- public Bundle getCustomExtras() {
- return mCustomExtras;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- if (dest == null) {
- throw new IllegalArgumentException("parcel shouldn't be null");
- }
- dest.writeInt(mCommandCode);
- dest.writeString(mCustomAction);
- dest.writeBundle(mCustomExtras);
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- if (!(obj instanceof Session2Command)) {
- return false;
- }
- Session2Command other = (Session2Command) obj;
- return mCommandCode == other.mCommandCode
- && TextUtils.equals(mCustomAction, other.mCustomAction);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mCustomAction, mCommandCode);
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Contains the result of {@link Session2Command}.
- */
- public static final class Result {
- private final int mResultCode;
- private final Bundle mResultData;
-
- /**
- * Result code representing that the command is skipped or canceled. For an example, a seek
- * command can be skipped if it is followed by another seek command.
- */
- public static final int RESULT_INFO_SKIPPED = 1;
-
- /**
- * Result code representing that the command is successfully completed.
- */
- public static final int RESULT_SUCCESS = 0;
-
- /**
- * Result code represents that call is ended with an unknown error.
- */
- public static final int RESULT_ERROR_UNKNOWN_ERROR = -1;
-
- /**
- * Constructor of {@link Result}.
- *
- * @param resultCode result code
- * @param resultData result data
- */
- public Result(int resultCode, @Nullable Bundle resultData) {
- mResultCode = resultCode;
- mResultData = resultData;
- }
-
- /**
- * Returns the result code.
- */
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Returns the result data.
- */
- @Nullable
- public Bundle getResultData() {
- return mResultData;
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/Session2CommandGroup.java b/apex/media/framework/java/android/media/Session2CommandGroup.java
deleted file mode 100644
index af8184a..0000000
--- a/apex/media/framework/java/android/media/Session2CommandGroup.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import static android.media.Session2Command.COMMAND_CODE_CUSTOM;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * A set of {@link Session2Command} which represents a command group.
- */
-public final class Session2CommandGroup implements Parcelable {
- private static final String TAG = "Session2CommandGroup";
-
- public static final @android.annotation.NonNull Parcelable.Creator<Session2CommandGroup>
- CREATOR = new Parcelable.Creator<Session2CommandGroup>() {
- @Override
- public Session2CommandGroup createFromParcel(Parcel in) {
- return new Session2CommandGroup(in);
- }
-
- @Override
- public Session2CommandGroup[] newArray(int size) {
- return new Session2CommandGroup[size];
- }
- };
-
- Set<Session2Command> mCommands = new HashSet<>();
-
- /**
- * Creates a new Session2CommandGroup with commands copied from another object.
- *
- * @param commands The collection of commands to copy.
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- Session2CommandGroup(@Nullable Collection<Session2Command> commands) {
- if (commands != null) {
- mCommands.addAll(commands);
- }
- }
-
- /**
- * Used by parcelable creator.
- */
- @SuppressWarnings({"WeakerAccess", "UnsafeParcelApi"}) /* synthetic access */
- Session2CommandGroup(Parcel in) {
- Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader());
- if (commands != null) {
- for (Parcelable command : commands) {
- mCommands.add((Session2Command) command);
- }
- }
- }
-
- /**
- * Checks whether this command group has a command that matches given {@code command}.
- *
- * @param command A command to find. Shouldn't be {@code null}.
- */
- public boolean hasCommand(@NonNull Session2Command command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- return mCommands.contains(command);
- }
-
- /**
- * Checks whether this command group has a command that matches given {@code commandCode}.
- *
- * @param commandCode A command code to find.
- * Shouldn't be {@link Session2Command#COMMAND_CODE_CUSTOM}.
- */
- public boolean hasCommand(int commandCode) {
- if (commandCode == COMMAND_CODE_CUSTOM) {
- throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
- }
- for (Session2Command command : mCommands) {
- if (command.getCommandCode() == commandCode) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Gets all commands of this command group.
- */
- @NonNull
- public Set<Session2Command> getCommands() {
- return new HashSet<>(mCommands);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- if (dest == null) {
- throw new IllegalArgumentException("parcel shouldn't be null");
- }
- dest.writeParcelableArray(mCommands.toArray(new Session2Command[0]), 0);
- }
-
- /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Builds a {@link Session2CommandGroup} object.
- */
- public static final class Builder {
- private Set<Session2Command> mCommands;
-
- public Builder() {
- mCommands = new HashSet<>();
- }
-
- /**
- * Creates a new builder for {@link Session2CommandGroup} with commands copied from another
- * {@link Session2CommandGroup} object.
- * @param commandGroup
- */
- public Builder(@NonNull Session2CommandGroup commandGroup) {
- if (commandGroup == null) {
- throw new IllegalArgumentException("command group shouldn't be null");
- }
- mCommands = commandGroup.getCommands();
- }
-
- /**
- * Adds a command to this command group.
- *
- * @param command A command to add. Shouldn't be {@code null}.
- */
- @NonNull
- public Builder addCommand(@NonNull Session2Command command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- mCommands.add(command);
- return this;
- }
-
- /**
- * Removes a command from this group which matches given {@code command}.
- *
- * @param command A command to find. Shouldn't be {@code null}.
- */
- @NonNull
- public Builder removeCommand(@NonNull Session2Command command) {
- if (command == null) {
- throw new IllegalArgumentException("command shouldn't be null");
- }
- mCommands.remove(command);
- return this;
- }
-
- /**
- * Builds {@link Session2CommandGroup}.
- *
- * @return a new {@link Session2CommandGroup}.
- */
- @NonNull
- public Session2CommandGroup build() {
- return new Session2CommandGroup(mCommands);
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/Session2Link.java b/apex/media/framework/java/android/media/Session2Link.java
deleted file mode 100644
index 6e550e8..0000000
--- a/apex/media/framework/java/android/media/Session2Link.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.annotation.NonNull;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import java.util.Objects;
-
-/**
- * Handles incoming commands from {@link MediaController2} to {@link MediaSession2}.
- * @hide
- */
-// @SystemApi
-public final class Session2Link implements Parcelable {
- private static final String TAG = "Session2Link";
- private static final boolean DEBUG = MediaSession2.DEBUG;
-
- public static final @android.annotation.NonNull Parcelable.Creator<Session2Link> CREATOR =
- new Parcelable.Creator<Session2Link>() {
- @Override
- public Session2Link createFromParcel(Parcel in) {
- return new Session2Link(in);
- }
-
- @Override
- public Session2Link[] newArray(int size) {
- return new Session2Link[size];
- }
- };
-
- private final MediaSession2 mSession;
- private final IMediaSession2 mISession;
-
- public Session2Link(MediaSession2 session) {
- mSession = session;
- mISession = new Session2Stub();
- }
-
- Session2Link(Parcel in) {
- mSession = null;
- mISession = IMediaSession2.Stub.asInterface(in.readStrongBinder());
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mISession.asBinder());
- }
-
- @Override
- public int hashCode() {
- return mISession.asBinder().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Session2Link)) {
- return false;
- }
- Session2Link other = (Session2Link) obj;
- return Objects.equals(mISession.asBinder(), other.mISession.asBinder());
- }
-
- /** Link to death with mISession */
- public void linkToDeath(@NonNull IBinder.DeathRecipient recipient, int flags) {
- if (mISession != null) {
- try {
- mISession.asBinder().linkToDeath(recipient, flags);
- } catch (RemoteException e) {
- if (DEBUG) {
- Log.d(TAG, "Session died too early.", e);
- }
- }
- }
- }
-
- /** Unlink to death with mISession */
- public boolean unlinkToDeath(@NonNull IBinder.DeathRecipient recipient, int flags) {
- if (mISession != null) {
- return mISession.asBinder().unlinkToDeath(recipient, flags);
- }
- return true;
- }
-
- /** Interface method for IMediaSession2.connect */
- public void connect(final Controller2Link caller, int seq, Bundle connectionRequest) {
- try {
- mISession.connect(caller, seq, connectionRequest);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaSession2.disconnect */
- public void disconnect(final Controller2Link caller, int seq) {
- try {
- mISession.disconnect(caller, seq);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaSession2.sendSessionCommand */
- public void sendSessionCommand(final Controller2Link caller, final int seq,
- final Session2Command command, final Bundle args, ResultReceiver resultReceiver) {
- try {
- mISession.sendSessionCommand(caller, seq, command, args, resultReceiver);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Interface method for IMediaSession2.sendSessionCommand */
- public void cancelSessionCommand(final Controller2Link caller, final int seq) {
- try {
- mISession.cancelSessionCommand(caller, seq);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /** Stub implementation for IMediaSession2.connect */
- public void onConnect(final Controller2Link caller, int pid, int uid, int seq,
- Bundle connectionRequest) {
- mSession.onConnect(caller, pid, uid, seq, connectionRequest);
- }
-
- /** Stub implementation for IMediaSession2.disconnect */
- public void onDisconnect(final Controller2Link caller, int seq) {
- mSession.onDisconnect(caller, seq);
- }
-
- /** Stub implementation for IMediaSession2.sendSessionCommand */
- public void onSessionCommand(final Controller2Link caller, final int seq,
- final Session2Command command, final Bundle args, ResultReceiver resultReceiver) {
- mSession.onSessionCommand(caller, seq, command, args, resultReceiver);
- }
-
- /** Stub implementation for IMediaSession2.cancelSessionCommand */
- public void onCancelCommand(final Controller2Link caller, final int seq) {
- mSession.onCancelCommand(caller, seq);
- }
-
- private class Session2Stub extends IMediaSession2.Stub {
- @Override
- public void connect(final Controller2Link caller, int seq, Bundle connectionRequest) {
- if (caller == null || connectionRequest == null) {
- return;
- }
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- Session2Link.this.onConnect(caller, pid, uid, seq, connectionRequest);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void disconnect(final Controller2Link caller, int seq) {
- if (caller == null) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- Session2Link.this.onDisconnect(caller, seq);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void sendSessionCommand(final Controller2Link caller, final int seq,
- final Session2Command command, final Bundle args, ResultReceiver resultReceiver) {
- if (caller == null) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- Session2Link.this.onSessionCommand(caller, seq, command, args, resultReceiver);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void cancelSessionCommand(final Controller2Link caller, final int seq) {
- if (caller == null) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- Session2Link.this.onCancelCommand(caller, seq);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-}
diff --git a/apex/media/framework/java/android/media/Session2Token.java b/apex/media/framework/java/android/media/Session2Token.java
deleted file mode 100644
index aae2e1b..0000000
--- a/apex/media/framework/java/android/media/Session2Token.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright 2018 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.media;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/session/package-summary.html">Media2 session
- * Library</a> for consistent behavior across all devices.
- * <p>
- * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
- * If it's representing a session service, it may not be ongoing.
- * <p>
- * This may be passed to apps by the session owner to allow them to create a
- * {@link MediaController2} to communicate with the session.
- * <p>
- * It can be also obtained by {@link android.media.session.MediaSessionManager}.
- */
-public final class Session2Token implements Parcelable {
- private static final String TAG = "Session2Token";
-
- public static final @android.annotation.NonNull Creator<Session2Token> CREATOR =
- new Creator<Session2Token>() {
- @Override
- public Session2Token createFromParcel(Parcel p) {
- return new Session2Token(p);
- }
-
- @Override
- public Session2Token[] newArray(int size) {
- return new Session2Token[size];
- }
- };
-
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "TYPE_", value = {TYPE_SESSION, TYPE_SESSION_SERVICE})
- public @interface TokenType {
- }
-
- /**
- * Type for {@link MediaSession2}.
- */
- public static final int TYPE_SESSION = 0;
-
- /**
- * Type for {@link MediaSession2Service}.
- */
- public static final int TYPE_SESSION_SERVICE = 1;
-
- private final int mUid;
- @TokenType
- private final int mType;
- private final String mPackageName;
- private final String mServiceName;
- private final Session2Link mSessionLink;
- private final ComponentName mComponentName;
- private final Bundle mExtras;
-
- /**
- * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}.
- *
- * @param context The context.
- * @param serviceComponent The component name of the service.
- */
- public Session2Token(@NonNull Context context, @NonNull ComponentName serviceComponent) {
- if (context == null) {
- throw new IllegalArgumentException("context shouldn't be null");
- }
- if (serviceComponent == null) {
- throw new IllegalArgumentException("serviceComponent shouldn't be null");
- }
-
- final PackageManager manager = context.getPackageManager();
- final int uid = getUid(manager, serviceComponent.getPackageName());
-
- if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE,
- serviceComponent)) {
- Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service.");
- }
- mComponentName = serviceComponent;
- mPackageName = serviceComponent.getPackageName();
- mServiceName = serviceComponent.getClassName();
- mUid = uid;
- mType = TYPE_SESSION_SERVICE;
- mSessionLink = null;
- mExtras = Bundle.EMPTY;
- }
-
- Session2Token(int uid, int type, String packageName, Session2Link sessionLink,
- @NonNull Bundle tokenExtras) {
- mUid = uid;
- mType = type;
- mPackageName = packageName;
- mServiceName = null;
- mComponentName = null;
- mSessionLink = sessionLink;
- mExtras = tokenExtras;
- }
-
- Session2Token(Parcel in) {
- mUid = in.readInt();
- mType = in.readInt();
- mPackageName = in.readString();
- mServiceName = in.readString();
- mSessionLink = in.readParcelable(null);
- mComponentName = ComponentName.unflattenFromString(in.readString());
-
- Bundle extras = in.readBundle();
- if (extras == null) {
- Log.w(TAG, "extras shouldn't be null.");
- extras = Bundle.EMPTY;
- } else if (MediaSession2.hasCustomParcelable(extras)) {
- Log.w(TAG, "extras contain custom parcelable. Ignoring.");
- extras = Bundle.EMPTY;
- }
- mExtras = extras;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mUid);
- dest.writeInt(mType);
- dest.writeString(mPackageName);
- dest.writeString(mServiceName);
- dest.writeParcelable(mSessionLink, flags);
- dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
- dest.writeBundle(mExtras);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Session2Token)) {
- return false;
- }
- Session2Token other = (Session2Token) obj;
- return mUid == other.mUid
- && TextUtils.equals(mPackageName, other.mPackageName)
- && TextUtils.equals(mServiceName, other.mServiceName)
- && mType == other.mType
- && Objects.equals(mSessionLink, other.mSessionLink);
- }
-
- @Override
- public String toString() {
- return "Session2Token {pkg=" + mPackageName + " type=" + mType
- + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}";
- }
-
- /**
- * @return uid of the session
- */
- public int getUid() {
- return mUid;
- }
-
- /**
- * @return package name of the session
- */
- @NonNull
- public String getPackageName() {
- return mPackageName;
- }
-
- /**
- * @return service name of the session. Can be {@code null} for {@link #TYPE_SESSION}.
- */
- @Nullable
- public String getServiceName() {
- return mServiceName;
- }
-
- /**
- * @return type of the token
- * @see #TYPE_SESSION
- * @see #TYPE_SESSION_SERVICE
- */
- public @TokenType int getType() {
- return mType;
- }
-
- /**
- * @return extras of the token
- * @see MediaSession2.Builder#setExtras(Bundle)
- */
- @NonNull
- public Bundle getExtras() {
- return new Bundle(mExtras);
- }
-
- Session2Link getSessionLink() {
- return mSessionLink;
- }
-
- private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,
- ComponentName serviceComponent) {
- Intent serviceIntent = new Intent(serviceInterface);
- // Use queryIntentServices to find services with MediaSession2Service.SERVICE_INTERFACE.
- // We cannot use resolveService with intent specified class name, because resolveService
- // ignores actions if Intent.setClassName() is specified.
- serviceIntent.setPackage(serviceComponent.getPackageName());
-
- List<ResolveInfo> list = manager.queryIntentServices(
- serviceIntent, PackageManager.GET_META_DATA);
- if (list != null) {
- for (int i = 0; i < list.size(); i++) {
- ResolveInfo resolveInfo = list.get(i);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- continue;
- }
- if (TextUtils.equals(
- resolveInfo.serviceInfo.name, serviceComponent.getClassName())) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static int getUid(PackageManager manager, String packageName) {
- try {
- return manager.getApplicationInfo(packageName, 0).uid;
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Cannot find package " + packageName);
- }
- }
-}
diff --git a/apex/media/framework/jni/android_media_MediaParserJNI.cpp b/apex/media/framework/jni/android_media_MediaParserJNI.cpp
deleted file mode 100644
index c81152c..0000000
--- a/apex/media/framework/jni/android_media_MediaParserJNI.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#include <jni.h>
-#include <media/MediaMetrics.h>
-
-#define JNI_FUNCTION(RETURN_TYPE, NAME, ...) \
- extern "C" { \
- JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
- ##__VA_ARGS__); \
- } \
- JNIEXPORT RETURN_TYPE Java_android_media_MediaParser_##NAME(JNIEnv* env, jobject thiz, \
- ##__VA_ARGS__)
-
-namespace {
-
-constexpr char kMediaMetricsKey[] = "mediaparser";
-
-constexpr char kAttributeLogSessionId[] = "android.media.mediaparser.logSessionId";
-constexpr char kAttributeParserName[] = "android.media.mediaparser.parserName";
-constexpr char kAttributeCreatedByName[] = "android.media.mediaparser.createdByName";
-constexpr char kAttributeParserPool[] = "android.media.mediaparser.parserPool";
-constexpr char kAttributeLastException[] = "android.media.mediaparser.lastException";
-constexpr char kAttributeResourceByteCount[] = "android.media.mediaparser.resourceByteCount";
-constexpr char kAttributeDurationMillis[] = "android.media.mediaparser.durationMillis";
-constexpr char kAttributeTrackMimeTypes[] = "android.media.mediaparser.trackMimeTypes";
-constexpr char kAttributeTrackCodecs[] = "android.media.mediaparser.trackCodecs";
-constexpr char kAttributeAlteredParameters[] = "android.media.mediaparser.alteredParameters";
-constexpr char kAttributeVideoWidth[] = "android.media.mediaparser.videoWidth";
-constexpr char kAttributeVideoHeight[] = "android.media.mediaparser.videoHeight";
-
-// Util class to handle string resource management.
-class JstringHandle {
-public:
- JstringHandle(JNIEnv* env, jstring value) : mEnv(env), mJstringValue(value) {
- mCstringValue = env->GetStringUTFChars(value, /* isCopy= */ nullptr);
- }
-
- ~JstringHandle() {
- if (mCstringValue != nullptr) {
- mEnv->ReleaseStringUTFChars(mJstringValue, mCstringValue);
- }
- }
-
- [[nodiscard]] const char* value() const {
- return mCstringValue != nullptr ? mCstringValue : "";
- }
-
- JNIEnv* mEnv;
- jstring mJstringValue;
- const char* mCstringValue;
-};
-
-} // namespace
-
-JNI_FUNCTION(void, nativeSubmitMetrics, jstring logSessionIdJstring, jstring parserNameJstring,
- jboolean createdByName, jstring parserPoolJstring, jstring lastExceptionJstring,
- jlong resourceByteCount, jlong durationMillis, jstring trackMimeTypesJstring,
- jstring trackCodecsJstring, jstring alteredParameters, jint videoWidth,
- jint videoHeight) {
- mediametrics_handle_t item(mediametrics_create(kMediaMetricsKey));
- mediametrics_setCString(item, kAttributeLogSessionId,
- JstringHandle(env, logSessionIdJstring).value());
- mediametrics_setCString(item, kAttributeParserName,
- JstringHandle(env, parserNameJstring).value());
- mediametrics_setInt32(item, kAttributeCreatedByName, createdByName ? 1 : 0);
- mediametrics_setCString(item, kAttributeParserPool,
- JstringHandle(env, parserPoolJstring).value());
- mediametrics_setCString(item, kAttributeLastException,
- JstringHandle(env, lastExceptionJstring).value());
- mediametrics_setInt64(item, kAttributeResourceByteCount, resourceByteCount);
- mediametrics_setInt64(item, kAttributeDurationMillis, durationMillis);
- mediametrics_setCString(item, kAttributeTrackMimeTypes,
- JstringHandle(env, trackMimeTypesJstring).value());
- mediametrics_setCString(item, kAttributeTrackCodecs,
- JstringHandle(env, trackCodecsJstring).value());
- mediametrics_setCString(item, kAttributeAlteredParameters,
- JstringHandle(env, alteredParameters).value());
- mediametrics_setInt32(item, kAttributeVideoWidth, videoWidth);
- mediametrics_setInt32(item, kAttributeVideoHeight, videoHeight);
- mediametrics_selfRecord(item);
- mediametrics_delete(item);
-}
diff --git a/apex/media/framework/lint-baseline.xml b/apex/media/framework/lint-baseline.xml
deleted file mode 100644
index 95eea45..0000000
--- a/apex/media/framework/lint-baseline.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
-
- <issue
- id="DefaultLocale"
- message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
- errorLine1=" if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java"
- line="121"
- column="57"/>
- </issue>
-
- <issue
- id="DefaultLocale"
- message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead"
- errorLine1=" return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}","
- errorLine2=" ^">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java"
- line="1651"
- column="20"/>
- </issue>
-
- <issue
- id="ParcelClassLoader"
- message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
- errorLine1=" Bundle out = parcel.readBundle(null);"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java"
- line="303"
- column="33"/>
- </issue>
-
- <issue
- id="ParcelClassLoader"
- message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
- errorLine1=" mCustomExtras = in.readBundle();"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java"
- line="104"
- column="28"/>
- </issue>
-
- <issue
- id="ParcelClassLoader"
- message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead."
- errorLine1=" mSessionLink = in.readParcelable(null);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
- line="141"
- column="27"/>
- </issue>
-
- <issue
- id="ParcelClassLoader"
- message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
- errorLine1=" Bundle extras = in.readBundle();"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java"
- line="144"
- column="28"/>
- </issue>
-
-</issues>
diff --git a/apex/media/framework/updatable-media-proguard.flags b/apex/media/framework/updatable-media-proguard.flags
deleted file mode 100644
index 4e7d842..0000000
--- a/apex/media/framework/updatable-media-proguard.flags
+++ /dev/null
@@ -1,2 +0,0 @@
-# Keep all symbols in android.media.
--keep class android.media.* {*;}
diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp
deleted file mode 100644
index 834e5cb..0000000
--- a/apex/media/service/Android.bp
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
- name: "service-media-s-sources",
- srcs: [
- "java/**/*.java",
- ],
- path: "java",
- visibility: ["//visibility:private"],
-}
-
-java_sdk_library {
- name: "service-media-s",
- permitted_packages: [
- "com.android.server.media",
- ],
- defaults: ["framework-system-server-module-defaults"],
- srcs: [
- ":service-media-s-sources",
- ],
- libs: [
- "androidx.annotation_annotation",
- "updatable-media",
- "modules-annotation-minsdk",
- "modules-utils-build",
- ],
- jarjar_rules: "jarjar_rules.txt",
- sdk_version: "system_server_current",
- min_sdk_version: "29", // TODO: We may need to bump this at some point.
- lint: {
- strict_updatability_linting: true,
- },
- apex_available: [
- "com.android.media",
- ],
-}
diff --git a/apex/media/service/api/current.txt b/apex/media/service/api/current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/service/api/current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/service/api/removed.txt b/apex/media/service/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/service/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/service/api/system-server-current.txt b/apex/media/service/api/system-server-current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/service/api/system-server-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/service/api/system-server-removed.txt b/apex/media/service/api/system-server-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/media/service/api/system-server-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/media/service/jarjar_rules.txt b/apex/media/service/jarjar_rules.txt
deleted file mode 100644
index 7e37c2b..0000000
--- a/apex/media/service/jarjar_rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule com.android.modules.** android.media.internal.@1
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
deleted file mode 100644
index 4223fa6..0000000
--- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright 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.media;
-
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.os.UserHandle.ALL;
-import static android.os.UserHandle.getUserHandleForUid;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.IMediaCommunicationService;
-import android.media.IMediaCommunicationServiceCallback;
-import android.media.MediaController2;
-import android.media.MediaParceledListSlice;
-import android.media.Session2CommandGroup;
-import android.media.Session2Token;
-import android.media.session.MediaSessionManager;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.KeyEvent;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.modules.annotation.MinSdk;
-import com.android.server.SystemService;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * A system service that manages {@link android.media.MediaSession2} creations
- * and their ongoing media playback state.
- * @hide
- */
-@MinSdk(Build.VERSION_CODES.S)
-@RequiresApi(Build.VERSION_CODES.S)
-public class MediaCommunicationService extends SystemService {
- private static final String TAG = "MediaCommunicationSrv";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- final Context mContext;
-
- final Object mLock = new Object();
- final Handler mHandler = new Handler(Looper.getMainLooper());
-
- @GuardedBy("mLock")
- private final SparseIntArray mFullUserIds = new SparseIntArray();
- @GuardedBy("mLock")
- private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<>();
-
- final Executor mRecordExecutor = Executors.newSingleThreadExecutor();
- @GuardedBy("mLock")
- final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>();
- final NotificationManager mNotificationManager;
- MediaSessionManager mSessionManager;
-
- public MediaCommunicationService(Context context) {
- super(context);
- mContext = context;
- mNotificationManager = context.getSystemService(NotificationManager.class);
- }
-
- @Override
- public void onStart() {
- publishBinderService(Context.MEDIA_COMMUNICATION_SERVICE, new Stub());
- updateUser();
- }
-
- @Override
- public void onBootPhase(int phase) {
- super.onBootPhase(phase);
- switch (phase) {
- // This ensures MediaSessionService is started
- case PHASE_BOOT_COMPLETED:
- mSessionManager = mContext.getSystemService(MediaSessionManager.class);
- break;
- }
- }
-
- @Override
- public void onUserStarting(@NonNull TargetUser user) {
- if (DEBUG) Log.d(TAG, "onUserStarting: " + user);
- updateUser();
- }
-
- @Override
- public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
- if (DEBUG) Log.d(TAG, "onUserSwitching: " + to);
- updateUser();
- }
-
- @Override
- public void onUserStopped(@NonNull TargetUser targetUser) {
- int userId = targetUser.getUserHandle().getIdentifier();
-
- if (DEBUG) Log.d(TAG, "onUserStopped: " + userId);
- synchronized (mLock) {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user != null) {
- if (user.getFullUserId() == userId) {
- user.destroyAllSessions();
- mUserRecords.remove(userId);
- } else {
- user.destroySessionsForUser(userId);
- }
- }
- }
- updateUser();
- }
-
- @Nullable
- CallbackRecord findCallbackRecordLocked(@Nullable IMediaCommunicationServiceCallback callback) {
- if (callback == null) {
- return null;
- }
- for (CallbackRecord record : mCallbackRecords) {
- if (Objects.equals(callback.asBinder(), record.mCallback.asBinder())) {
- return record;
- }
- }
- return null;
- }
-
- ArrayList<Session2Token> getSession2TokensLocked(int userId) {
- ArrayList<Session2Token> list = new ArrayList<>();
- if (userId == ALL.getIdentifier()) {
- int size = mUserRecords.size();
- for (int i = 0; i < size; i++) {
- list.addAll(mUserRecords.valueAt(i).getAllSession2Tokens());
- }
- } else {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user != null) {
- list.addAll(user.getSession2Tokens(userId));
- }
- }
- return list;
- }
-
- private FullUserRecord getFullUserRecordLocked(int userId) {
- int fullUserId = mFullUserIds.get(userId, -1);
- if (fullUserId < 0) {
- return null;
- }
- return mUserRecords.get(fullUserId);
- }
-
- private boolean hasMediaControlPermission(int pid, int uid) {
- // Check if it's system server or has MEDIA_CONTENT_CONTROL.
- // Note that system server doesn't have MEDIA_CONTENT_CONTROL, so we need extra
- // check here.
- if (uid == Process.SYSTEM_UID || mContext.checkPermission(
- android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- } else if (DEBUG) {
- Log.d(TAG, "uid(" + uid + ") hasn't granted MEDIA_CONTENT_CONTROL");
- }
- return false;
- }
-
- private void updateUser() {
- UserManager manager = mContext.getSystemService(UserManager.class);
- List<UserHandle> allUsers = manager.getUserHandles(/*excludeDying=*/false);
-
- synchronized (mLock) {
- mFullUserIds.clear();
- if (allUsers != null) {
- for (UserHandle user : allUsers) {
- UserHandle parent = manager.getProfileParent(user);
- if (parent != null) {
- mFullUserIds.put(user.getIdentifier(), parent.getIdentifier());
- } else {
- mFullUserIds.put(user.getIdentifier(), user.getIdentifier());
- if (mUserRecords.get(user.getIdentifier()) == null) {
- mUserRecords.put(user.getIdentifier(),
- new FullUserRecord(user.getIdentifier()));
- }
- }
- }
- }
- // Ensure that the current full user exists.
- int currentFullUserId = ActivityManager.getCurrentUser();
- FullUserRecord currentFullUserRecord = mUserRecords.get(currentFullUserId);
- if (currentFullUserRecord == null) {
- Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
- currentFullUserRecord = new FullUserRecord(currentFullUserId);
- mUserRecords.put(currentFullUserId, currentFullUserRecord);
- }
- mFullUserIds.put(currentFullUserId, currentFullUserId);
- }
- }
-
- void dispatchSession2Created(Session2Token token) {
- synchronized (mLock) {
- for (CallbackRecord record : mCallbackRecords) {
- if (record.mUserId != ALL.getIdentifier()
- && record.mUserId != getUserHandleForUid(token.getUid()).getIdentifier()) {
- continue;
- }
- try {
- record.mCallback.onSession2Created(token);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify session2 token created " + record);
- }
- }
- }
- }
-
- void dispatchSession2Changed(int userId) {
- ArrayList<Session2Token> allSession2Tokens;
- ArrayList<Session2Token> userSession2Tokens;
-
- synchronized (mLock) {
- allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier());
- userSession2Tokens = getSession2TokensLocked(userId);
-
- for (CallbackRecord record : mCallbackRecords) {
- if (record.mUserId == ALL.getIdentifier()) {
- try {
- MediaParceledListSlice<Session2Token> toSend =
- new MediaParceledListSlice<>(allSession2Tokens);
- toSend.setInlineCountLimit(0);
- record.mCallback.onSession2Changed(toSend);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify session2 tokens changed " + record);
- }
- } else if (record.mUserId == userId) {
- try {
- MediaParceledListSlice<Session2Token> toSend =
- new MediaParceledListSlice<>(userSession2Tokens);
- toSend.setInlineCountLimit(0);
- record.mCallback.onSession2Changed(toSend);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify session2 tokens changed " + record);
- }
- }
- }
- }
- }
-
- void onSessionDied(Session2Record session) {
- if (DEBUG) {
- Log.d(TAG, "Destroying " + session);
- }
- if (session.isClosed()) {
- Log.w(TAG, "Destroying already destroyed session. Ignoring.");
- return;
- }
-
- FullUserRecord user = session.getFullUser();
- if (user != null) {
- user.removeSession(session);
- }
- session.close();
- }
-
- void onSessionPlaybackStateChanged(Session2Record session, boolean promotePriority) {
- FullUserRecord user = session.getFullUser();
- if (user == null || !user.containsSession(session)) {
- Log.d(TAG, "Unknown session changed playback state. Ignoring.");
- return;
- }
- user.onPlaybackStateChanged(session, promotePriority);
- }
-
-
- static boolean isMediaSessionKey(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_MUTE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- case KeyEvent.KEYCODE_MEDIA_STOP:
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
- case KeyEvent.KEYCODE_MEDIA_REWIND:
- case KeyEvent.KEYCODE_MEDIA_RECORD:
- case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
- return true;
- }
- return false;
- }
-
- private class Stub extends IMediaCommunicationService.Stub {
- @Override
- public void notifySession2Created(Session2Token sessionToken) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- if (DEBUG) {
- Log.d(TAG, "Session2 is created " + sessionToken);
- }
- if (uid != sessionToken.getUid()) {
- throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
- + " but actually=" + sessionToken.getUid());
- }
- FullUserRecord user;
- int userId = getUserHandleForUid(sessionToken.getUid()).getIdentifier();
- synchronized (mLock) {
- user = getFullUserRecordLocked(userId);
- }
- if (user == null) {
- Log.w(TAG, "notifySession2Created: Ignore session of an unknown user");
- return;
- }
- user.addSession(new Session2Record(MediaCommunicationService.this,
- user, sessionToken, mRecordExecutor));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Returns if the controller's package is trusted (i.e. has either MEDIA_CONTENT_CONTROL
- * permission or an enabled notification listener)
- *
- * @param controllerPackageName package name of the controller app
- * @param controllerPid pid of the controller app
- * @param controllerUid uid of the controller app
- */
- @Override
- public boolean isTrusted(String controllerPackageName, int controllerPid,
- int controllerUid) {
- final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- final long token = Binder.clearCallingIdentity();
- try {
- // Don't perform check between controllerPackageName and controllerUid.
- // When an (activity|service) runs on the another apps process by specifying
- // android:process in the AndroidManifest.xml, then PID and UID would have the
- // running process' information instead of the (activity|service) that has created
- // MediaController.
- // Note that we can use Context#getOpPackageName() instead of
- // Context#getPackageName() for getting package name that matches with the PID/UID,
- // but it doesn't tell which package has created the MediaController, so useless.
- return hasMediaControlPermission(controllerPid, controllerUid)
- || hasEnabledNotificationListener(
- userId, controllerPackageName, controllerUid);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public MediaParceledListSlice getSession2Tokens(int userId) {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- // Check that they can make calls on behalf of the user and get the final user id
- int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
- ArrayList<Session2Token> result;
- synchronized (mLock) {
- result = getSession2TokensLocked(resolvedUserId);
- }
- MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result);
- parceledListSlice.setInlineCountLimit(1);
- return parceledListSlice;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void dispatchMediaKeyEvent(String packageName, KeyEvent keyEvent,
- boolean asSystemService) {
- if (keyEvent == null || !isMediaSessionKey(keyEvent.getKeyCode())) {
- Log.w(TAG, "Attempted to dispatch null or non-media key event.");
- return;
- }
-
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- try {
- //TODO: Dispatch key event to media session 2 if required
- mSessionManager.dispatchMediaKeyEvent(keyEvent, asSystemService);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
- public void registerCallback(IMediaCommunicationServiceCallback callback,
- String packageName) throws RemoteException {
- Objects.requireNonNull(callback, "callback should not be null");
- Objects.requireNonNull(packageName, "packageName should not be null");
-
- synchronized (mLock) {
- if (findCallbackRecordLocked(callback) == null) {
-
- CallbackRecord record = new CallbackRecord(callback, packageName,
- Binder.getCallingUid(), Binder.getCallingPid());
- mCallbackRecords.add(record);
- try {
- callback.asBinder().linkToDeath(record, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to register callback", e);
- mCallbackRecords.remove(record);
- }
- } else {
- Log.e(TAG, "registerCallback is called with already registered callback. "
- + "packageName=" + packageName);
- }
- }
- }
-
- @Override
- public void unregisterCallback(IMediaCommunicationServiceCallback callback)
- throws RemoteException {
- synchronized (mLock) {
- CallbackRecord existingRecord = findCallbackRecordLocked(callback);
- if (existingRecord != null) {
- mCallbackRecords.remove(existingRecord);
- callback.asBinder().unlinkToDeath(existingRecord, 0);
- } else {
- Log.e(TAG, "unregisterCallback is called with unregistered callback.");
- }
- }
- }
-
- private boolean hasEnabledNotificationListener(int callingUserId,
- String controllerPackageName, int controllerUid) {
- int controllerUserId = UserHandle.getUserHandleForUid(controllerUid).getIdentifier();
- if (callingUserId != controllerUserId) {
- // Enabled notification listener only works within the same user.
- return false;
- }
-
- if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName,
- UserHandle.getUserHandleForUid(controllerUid))) {
- return true;
- }
- if (DEBUG) {
- Log.d(TAG, controllerPackageName + " (uid=" + controllerUid
- + ") doesn't have an enabled notification listener");
- }
- return false;
- }
-
- // Handles incoming user by checking whether the caller has permission to access the
- // given user id's information or not. Permission is not necessary if the given user id is
- // equal to the caller's user id, but if not, the caller needs to have the
- // INTERACT_ACROSS_USERS_FULL permission. Otherwise, a security exception will be thrown.
- // The return value will be the given user id, unless the given user id is
- // UserHandle.CURRENT, which will return the ActivityManager.getCurrentUser() value instead.
- private int handleIncomingUser(int pid, int uid, int userId, String packageName) {
- int callingUserId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- if (userId == callingUserId) {
- return userId;
- }
-
- boolean canInteractAcrossUsersFull = mContext.checkPermission(
- INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED;
- if (canInteractAcrossUsersFull) {
- if (userId == UserHandle.CURRENT.getIdentifier()) {
- return ActivityManager.getCurrentUser();
- }
- return userId;
- }
-
- throw new SecurityException("Permission denied while calling from " + packageName
- + " with user id: " + userId + "; Need to run as either the calling user id ("
- + callingUserId + "), or with " + INTERACT_ACROSS_USERS_FULL + " permission");
- }
- }
-
- final class CallbackRecord implements IBinder.DeathRecipient {
- private final IMediaCommunicationServiceCallback mCallback;
- private final String mPackageName;
- private final int mUid;
- private int mPid;
- private final int mUserId;
-
- CallbackRecord(IMediaCommunicationServiceCallback callback,
- String packageName, int uid, int pid) {
- mCallback = callback;
- mPackageName = packageName;
- mUid = uid;
- mPid = pid;
- mUserId = (mContext.checkPermission(
- INTERACT_ACROSS_USERS_FULL, pid, uid) == PackageManager.PERMISSION_GRANTED)
- ? ALL.getIdentifier() : UserHandle.getUserHandleForUid(mUid).getIdentifier();
- }
-
- @Override
- public String toString() {
- return "CallbackRecord[callback=" + mCallback + ", pkg=" + mPackageName
- + ", uid=" + mUid + ", pid=" + mPid + "]";
- }
-
- @Override
- public void binderDied() {
- synchronized (mLock) {
- mCallbackRecords.remove(this);
- }
- }
- }
-
- final class FullUserRecord {
- private final int mFullUserId;
- private final SessionPriorityList mSessionPriorityList = new SessionPriorityList();
-
- FullUserRecord(int fullUserId) {
- mFullUserId = fullUserId;
- }
-
- public void addSession(Session2Record record) {
- mSessionPriorityList.addSession(record);
- mHandler.post(() -> dispatchSession2Created(record.mSessionToken));
- mHandler.post(() -> dispatchSession2Changed(mFullUserId));
- }
-
- private void removeSession(Session2Record record) {
- mSessionPriorityList.removeSession(record);
- mHandler.post(() -> dispatchSession2Changed(mFullUserId));
- //TODO: Handle if the removed session was the media button session.
- }
-
- public int getFullUserId() {
- return mFullUserId;
- }
-
- public List<Session2Token> getAllSession2Tokens() {
- return mSessionPriorityList.getAllTokens();
- }
-
- public List<Session2Token> getSession2Tokens(int userId) {
- return mSessionPriorityList.getTokensByUserId(userId);
- }
-
- public void destroyAllSessions() {
- mSessionPriorityList.destroyAllSessions();
- mHandler.post(() -> dispatchSession2Changed(mFullUserId));
- }
-
- public void destroySessionsForUser(int userId) {
- if (mSessionPriorityList.destroySessionsByUserId(userId)) {
- mHandler.post(() -> dispatchSession2Changed(mFullUserId));
- }
- }
-
- public boolean containsSession(Session2Record session) {
- return mSessionPriorityList.contains(session);
- }
-
- public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) {
- mSessionPriorityList.onPlaybackStateChanged(session, promotePriority);
- }
- }
-
- static final class Session2Record {
- final Session2Token mSessionToken;
- final Object mSession2RecordLock = new Object();
- final WeakReference<MediaCommunicationService> mServiceRef;
- final WeakReference<FullUserRecord> mFullUserRef;
- @GuardedBy("mSession2RecordLock")
- private final MediaController2 mController;
-
- @GuardedBy("mSession2RecordLock")
- boolean mIsConnected;
- @GuardedBy("mSession2RecordLock")
- private boolean mIsClosed;
-
- //TODO: introduce policy (See MediaSessionPolicyProvider)
- Session2Record(MediaCommunicationService service, FullUserRecord fullUser,
- Session2Token token, Executor controllerExecutor) {
- mServiceRef = new WeakReference<>(service);
- mFullUserRef = new WeakReference<>(fullUser);
- mSessionToken = token;
- mController = new MediaController2.Builder(service.getContext(), token)
- .setControllerCallback(controllerExecutor, new Controller2Callback())
- .build();
- }
-
- public int getUserId() {
- return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
- }
-
- public FullUserRecord getFullUser() {
- return mFullUserRef.get();
- }
-
- public boolean isClosed() {
- synchronized (mSession2RecordLock) {
- return mIsClosed;
- }
- }
-
- public void close() {
- synchronized (mSession2RecordLock) {
- mIsClosed = true;
- mController.close();
- }
- }
-
- public Session2Token getSessionToken() {
- return mSessionToken;
- }
-
- public boolean checkPlaybackActiveState(boolean expected) {
- synchronized (mSession2RecordLock) {
- return mIsConnected && mController.isPlaybackActive() == expected;
- }
- }
-
- private class Controller2Callback extends MediaController2.ControllerCallback {
- @Override
- public void onConnected(MediaController2 controller,
- Session2CommandGroup allowedCommands) {
- if (DEBUG) {
- Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
- }
- synchronized (mSession2RecordLock) {
- mIsConnected = true;
- }
- }
-
- @Override
- public void onDisconnected(MediaController2 controller) {
- if (DEBUG) {
- Log.d(TAG, "disconnected from " + mSessionToken);
- }
- synchronized (mSession2RecordLock) {
- mIsConnected = false;
- }
- MediaCommunicationService service = mServiceRef.get();
- if (service != null) {
- service.onSessionDied(Session2Record.this);
- }
- }
-
- @Override
- public void onPlaybackActiveChanged(
- @NonNull MediaController2 controller,
- boolean playbackActive) {
- if (DEBUG) {
- Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
- + playbackActive);
- }
- MediaCommunicationService service = mServiceRef.get();
- if (service != null) {
- service.onSessionPlaybackStateChanged(Session2Record.this, playbackActive);
- }
- }
- }
- }
-}
diff --git a/apex/media/service/java/com/android/server/media/SessionPriorityList.java b/apex/media/service/java/com/android/server/media/SessionPriorityList.java
deleted file mode 100644
index 8145861..0000000
--- a/apex/media/service/java/com/android/server/media/SessionPriorityList.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.annotation.Nullable;
-import android.media.Session2Token;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.modules.annotation.MinSdk;
-import com.android.server.media.MediaCommunicationService.Session2Record;
-
-import java.util.ArrayList;
-import java.util.List;
-
-//TODO: Define the priority specifically.
-/**
- * Keeps track of media sessions and their priority for notifications, media
- * button dispatch, etc.
- * Higher priority session has more chance to be selected as media button session,
- * which receives the media button events.
- */
-@MinSdk(Build.VERSION_CODES.S)
-@RequiresApi(Build.VERSION_CODES.S)
-class SessionPriorityList {
- private static final String TAG = "SessionPriorityList";
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private final List<Session2Record> mSessions = new ArrayList<>();
-
- @Nullable
- private Session2Record mMediaButtonSession;
- @Nullable
- private Session2Record mCachedVolumeSession;
-
- //TODO: integrate AudioPlayerStateMonitor
-
- public void addSession(Session2Record record) {
- synchronized (mLock) {
- mSessions.add(record);
- }
- }
-
- public void removeSession(Session2Record record) {
- synchronized (mLock) {
- mSessions.remove(record);
- }
- if (record == mMediaButtonSession) {
- updateMediaButtonSession(null);
- }
- }
-
- public void destroyAllSessions() {
- synchronized (mLock) {
- for (Session2Record session : mSessions) {
- session.close();
- }
- mSessions.clear();
- }
- }
-
- public boolean destroySessionsByUserId(int userId) {
- boolean changed = false;
- synchronized (mLock) {
- for (int i = mSessions.size() - 1; i >= 0; i--) {
- Session2Record session = mSessions.get(i);
- if (session.getUserId() == userId) {
- mSessions.remove(i);
- session.close();
- changed = true;
- }
- }
- }
- return changed;
- }
-
- public List<Session2Token> getAllTokens() {
- List<Session2Token> sessions = new ArrayList<>();
- synchronized (mLock) {
- for (Session2Record session : mSessions) {
- sessions.add(session.getSessionToken());
- }
- }
- return sessions;
- }
-
- public List<Session2Token> getTokensByUserId(int userId) {
- List<Session2Token> sessions = new ArrayList<>();
- synchronized (mLock) {
- for (Session2Record session : mSessions) {
- if (session.getUserId() == userId) {
- sessions.add(session.getSessionToken());
- }
- }
- }
- return sessions;
- }
-
- /** Gets the media button session which receives the media button events. */
- @Nullable
- public Session2Record getMediaButtonSession() {
- return mMediaButtonSession;
- }
-
- /** Gets the media volume session which receives the volume key events. */
- @Nullable
- public Session2Record getMediaVolumeSession() {
- //TODO: if null, calculate it.
- return mCachedVolumeSession;
- }
-
- public boolean contains(Session2Record session) {
- synchronized (mLock) {
- return mSessions.contains(session);
- }
- }
-
- public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) {
- if (promotePriority) {
- synchronized (mLock) {
- if (mSessions.remove(session)) {
- mSessions.add(0, session);
- } else {
- Log.w(TAG, "onPlaybackStateChanged: Ignoring unknown session");
- return;
- }
- }
- } else if (session.checkPlaybackActiveState(false)) {
- // Just clear the cached volume session when a state goes inactive
- mCachedVolumeSession = null;
- }
-
- // In most cases, playback state isn't needed for finding the media button session,
- // but we only use it as a hint if an app has multiple local media sessions.
- // In that case, we pick the media session whose PlaybackState matches
- // the audio playback configuration.
- if (mMediaButtonSession != null
- && mMediaButtonSession.getSessionToken().getUid()
- == session.getSessionToken().getUid()) {
- Session2Record newMediaButtonSession =
- findMediaButtonSession(mMediaButtonSession.getSessionToken().getUid());
- if (newMediaButtonSession != mMediaButtonSession) {
- // Check if the policy states that this session should not be updated as a media
- // button session.
- updateMediaButtonSession(newMediaButtonSession);
- }
- }
- }
-
- private void updateMediaButtonSession(@Nullable Session2Record newSession) {
- mMediaButtonSession = newSession;
- //TODO: invoke callbacks for media button session changed listeners
- }
-
- /**
- * Finds the media button session with the given {@param uid}.
- * If the app has multiple media sessions, the media session whose playback state is not null
- * and matches the audio playback state becomes the media button session. Otherwise the top
- * priority session becomes the media button session.
- *
- * @return The media button session. Returns {@code null} if the app doesn't have a media
- * session.
- */
- @Nullable
- private Session2Record findMediaButtonSession(int uid) {
- Session2Record mediaButtonSession = null;
- synchronized (mLock) {
- for (Session2Record session : mSessions) {
- if (uid != session.getSessionToken().getUid()) {
- continue;
- }
- // TODO: check audio player state monitor
- if (mediaButtonSession == null) {
- // Pick the top priority session as a default.
- mediaButtonSession = session;
- }
- }
- }
- return mediaButtonSession;
- }
-}
diff --git a/apex/media/service/lint-baseline.xml b/apex/media/service/lint-baseline.xml
deleted file mode 100644
index def6baf..0000000
--- a/apex/media/service/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev">
-
-</issues>
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 9564dde..c5410a0 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -189,6 +189,8 @@
instrument.abi = nextArgRequired();
} else if (opt.equals("--no-restart")) {
instrument.noRestart = true;
+ } else if (opt.equals("--always-check-signature")) {
+ instrument.alwaysCheckSignature = true;
} else {
System.err.println("Error: Unknown option: " + opt);
return;
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index 0b439df..a0562d9 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -16,6 +16,7 @@
package com.android.commands.am;
+import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
@@ -95,6 +96,7 @@
public Bundle args = new Bundle();
// Required
public String componentNameArg;
+ public boolean alwaysCheckSignature = false;
/**
* Construct the instrument command runner.
@@ -519,6 +521,9 @@
if (noRestart) {
flags |= INSTR_FLAG_NO_RESTART;
}
+ if (alwaysCheckSignature) {
+ flags |= INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
+ }
if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId,
abi)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
diff --git a/core/api/current.txt b/core/api/current.txt
index 51c9ad7..139ff3e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1453,6 +1453,7 @@
field public static final int supportsAssist = 16844016; // 0x10104f0
field public static final int supportsBatteryGameMode;
field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
+ field public static final int supportsInlineSuggestionsWithTouchExploration;
field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
field public static final int supportsLocalInteraction = 16844047; // 0x101050f
field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
@@ -4275,6 +4276,7 @@
method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle);
method public final void setMediaController(android.media.session.MediaController);
method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams);
+ method public void setPreferDockBigOverlays(boolean);
method @Deprecated public final void setProgress(int);
method @Deprecated public final void setProgressBarIndeterminate(boolean);
method @Deprecated public final void setProgressBarIndeterminateVisibility(boolean);
@@ -4548,6 +4550,7 @@
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int);
+ method @NonNull public static android.app.ActivityOptions makeLaunchIntoPip(@NonNull android.app.PictureInPictureParams);
method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
method @java.lang.SafeVarargs public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View,java.lang.String>...);
@@ -6633,13 +6636,17 @@
public static class PictureInPictureParams.Builder {
ctor public PictureInPictureParams.Builder();
+ ctor public PictureInPictureParams.Builder(@NonNull android.app.PictureInPictureParams);
method public android.app.PictureInPictureParams build();
method public android.app.PictureInPictureParams.Builder setActions(java.util.List<android.app.RemoteAction>);
method public android.app.PictureInPictureParams.Builder setAspectRatio(android.util.Rational);
method @NonNull public android.app.PictureInPictureParams.Builder setAutoEnterEnabled(boolean);
+ method @NonNull public android.app.PictureInPictureParams.Builder setCloseAction(@Nullable android.app.RemoteAction);
method @NonNull public android.app.PictureInPictureParams.Builder setExpandedAspectRatio(@Nullable android.util.Rational);
method @NonNull public android.app.PictureInPictureParams.Builder setSeamlessResizeEnabled(boolean);
method public android.app.PictureInPictureParams.Builder setSourceRectHint(android.graphics.Rect);
+ method @NonNull public android.app.PictureInPictureParams.Builder setSubtitle(@Nullable CharSequence);
+ method @NonNull public android.app.PictureInPictureParams.Builder setTitle(@Nullable CharSequence);
}
public final class PictureInPictureUiState implements android.os.Parcelable {
@@ -16446,6 +16453,7 @@
public class MeasuredText {
method public void getBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Rect);
method @FloatRange(from=0.0f) @Px public float getCharWidthAt(@IntRange(from=0) int);
+ method public void getFontMetricsInt(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Paint.FontMetricsInt);
method @FloatRange(from=0.0) @Px public float getWidth(@IntRange(from=0) int, @IntRange(from=0) int);
}
@@ -16989,6 +16997,7 @@
public class SensorEvent {
field public int accuracy;
+ field public boolean firstEventAfterDiscontinuity;
field public android.hardware.Sensor sensor;
field public long timestamp;
field public final float[] values;
@@ -17000,7 +17009,6 @@
method public void onFlushCompleted(android.hardware.Sensor);
method public void onSensorAdditionalInfo(android.hardware.SensorAdditionalInfo);
method public void onSensorChanged(android.hardware.SensorEvent);
- method public void onSensorDiscontinuity(@NonNull android.hardware.Sensor);
}
public interface SensorEventListener {
@@ -17118,6 +17126,9 @@
public final class SensorPrivacyManager {
method public boolean supportsSensorToggle(int);
+ method public boolean supportsSensorToggle(int, int);
+ field public static final int TOGGLE_TYPE_HARDWARE = 2; // 0x2
+ field public static final int TOGGLE_TYPE_SOFTWARE = 1; // 0x1
}
public static class SensorPrivacyManager.Sensors {
@@ -39505,7 +39516,7 @@
method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback);
method protected abstract void onStartListening(android.content.Intent, android.speech.RecognitionService.Callback);
method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
- method public void triggerModelDownload(@NonNull android.content.Intent);
+ method public void onTriggerModelDownload(@NonNull android.content.Intent);
field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
field public static final String SERVICE_META_DATA = "android.speech";
}
@@ -45008,6 +45019,7 @@
method public char charAt(int);
method public static android.text.PrecomputedText create(@NonNull CharSequence, @NonNull android.text.PrecomputedText.Params);
method public void getBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Rect);
+ method public void getFontMetricsInt(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.Paint.FontMetricsInt);
method @IntRange(from=0) public int getParagraphCount();
method @IntRange(from=0) public int getParagraphEnd(@IntRange(from=0) int);
method @IntRange(from=0) public int getParagraphStart(@IntRange(from=0) int);
@@ -47677,15 +47689,11 @@
public final class Choreographer {
method public static android.view.Choreographer getInstance();
- method public void postExtendedFrameCallback(@NonNull android.view.Choreographer.ExtendedFrameCallback);
method public void postFrameCallback(android.view.Choreographer.FrameCallback);
method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long);
- method public void removeExtendedFrameCallback(@Nullable android.view.Choreographer.ExtendedFrameCallback);
+ method public void postVsyncCallback(@NonNull android.view.Choreographer.VsyncCallback);
method public void removeFrameCallback(android.view.Choreographer.FrameCallback);
- }
-
- public static interface Choreographer.ExtendedFrameCallback {
- method public void onVsync(@NonNull android.view.Choreographer.FrameData);
+ method public void removeVsyncCallback(@Nullable android.view.Choreographer.VsyncCallback);
}
public static interface Choreographer.FrameCallback {
@@ -47700,10 +47708,14 @@
public static class Choreographer.FrameTimeline {
method public long getDeadlineNanos();
- method public long getExpectedPresentTimeNanos();
+ method public long getExpectedPresentationTimeNanos();
method public long getVsyncId();
}
+ public static interface Choreographer.VsyncCallback {
+ method public void onVsync(@NonNull android.view.Choreographer.FrameData);
+ }
+
public interface CollapsibleActionView {
method public void onActionViewCollapsed();
method public void onActionViewExpanded();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index df08721..904aa7b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1656,8 +1656,8 @@
method @NonNull public android.os.Bundle getSearchConstraints();
method @NonNull public String getSource();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "IS_PRESUBMIT_SUGGESTION";
- field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "SEARCH_PROVIDER_FILTER";
+ field public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "android.app.cloudsearch.IS_PRESUBMIT_SUGGESTION";
+ field public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "android.app.cloudsearch.SEARCH_PROVIDER_FILTER";
field @NonNull public static final android.os.Parcelable.Creator<android.app.cloudsearch.SearchRequest> CREATOR;
}
@@ -1699,23 +1699,23 @@
method @NonNull public String getTitle();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.cloudsearch.SearchResult> CREATOR;
- field public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "ACTION_BUTTON_IMAGE";
- field public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "ACTION_BUTTON_TEXT";
- field public static final String EXTRAINFO_APP_BADGES = "APP_BADGES";
- field public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER = "APP_CONTAINS_ADS_DISCLAIMER";
- field public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER = "APP_CONTAINS_IAP_DISCLAIMER";
- field public static final String EXTRAINFO_APP_DEVELOPER_NAME = "APP_DEVELOPER_NAME";
- field public static final String EXTRAINFO_APP_DOMAIN_URL = "APP_DOMAIN_URL";
- field public static final String EXTRAINFO_APP_IARC = "APP_IARC";
- field public static final String EXTRAINFO_APP_ICON = "APP_ICON";
- field public static final String EXTRAINFO_APP_REVIEW_COUNT = "APP_REVIEW_COUNT";
- field public static final String EXTRAINFO_APP_SIZE_BYTES = "APP_SIZE_BYTES";
- field public static final String EXTRAINFO_APP_STAR_RATING = "APP_STAR_RATING";
- field public static final String EXTRAINFO_LONG_DESCRIPTION = "LONG_DESCRIPTION";
- field public static final String EXTRAINFO_SCREENSHOTS = "SCREENSHOTS";
- field public static final String EXTRAINFO_SHORT_DESCRIPTION = "SHORT_DESCRIPTION";
- field public static final String EXTRAINFO_WEB_ICON = "WEB_ICON";
- field public static final String EXTRAINFO_WEB_URL = "WEB_URL";
+ field public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "android.app.cloudsearch.ACTION_BUTTON_IMAGE";
+ field public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "android.app.cloudsearch.ACTION_BUTTON_TEXT";
+ field public static final String EXTRAINFO_APP_BADGES = "android.app.cloudsearch.APP_BADGES";
+ field public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER = "android.app.cloudsearch.APP_CONTAINS_ADS_DISCLAIMER";
+ field public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER = "android.app.cloudsearch.APP_CONTAINS_IAP_DISCLAIMER";
+ field public static final String EXTRAINFO_APP_DEVELOPER_NAME = "android.app.cloudsearch.APP_DEVELOPER_NAME";
+ field public static final String EXTRAINFO_APP_DOMAIN_URL = "android.app.cloudsearch.APP_DOMAIN_URL";
+ field public static final String EXTRAINFO_APP_IARC = "android.app.cloudsearch.APP_IARC";
+ field public static final String EXTRAINFO_APP_ICON = "android.app.cloudsearch.APP_ICON";
+ field public static final String EXTRAINFO_APP_REVIEW_COUNT = "android.app.cloudsearch.APP_REVIEW_COUNT";
+ field public static final String EXTRAINFO_APP_SIZE_BYTES = "android.app.cloudsearch.APP_SIZE_BYTES";
+ field public static final String EXTRAINFO_APP_STAR_RATING = "android.app.cloudsearch.APP_STAR_RATING";
+ field public static final String EXTRAINFO_LONG_DESCRIPTION = "android.app.cloudsearch.LONG_DESCRIPTION";
+ field public static final String EXTRAINFO_SCREENSHOTS = "android.app.cloudsearch.SCREENSHOTS";
+ field public static final String EXTRAINFO_SHORT_DESCRIPTION = "android.app.cloudsearch.SHORT_DESCRIPTION";
+ field public static final String EXTRAINFO_WEB_ICON = "android.app.cloudsearch.WEB_ICON";
+ field public static final String EXTRAINFO_WEB_URL = "android.app.cloudsearch.WEB_URL";
}
public static final class SearchResult.Builder {
@@ -2254,13 +2254,20 @@
public class BaseTemplateData implements android.os.Parcelable {
method public int describeContents();
+ method public int getLayoutWeight();
+ method @Nullable public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo getPrimaryLoggingInfo();
method @Nullable public android.app.smartspace.uitemplatedata.TapAction getPrimaryTapAction();
method @Nullable public android.app.smartspace.uitemplatedata.Icon getSubtitleIcon();
method @Nullable public android.app.smartspace.uitemplatedata.Text getSubtitleText();
method @Nullable public android.app.smartspace.uitemplatedata.Text getSupplementalAlarmText();
+ method @Nullable public android.app.smartspace.uitemplatedata.Icon getSupplementalIcon();
+ method @Nullable public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo getSupplementalLoggingInfo();
method @Nullable public android.app.smartspace.uitemplatedata.Icon getSupplementalSubtitleIcon();
+ method @Nullable public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo getSupplementalSubtitleLoggingInfo();
method @Nullable public android.app.smartspace.uitemplatedata.TapAction getSupplementalSubtitleTapAction();
method @Nullable public android.app.smartspace.uitemplatedata.Text getSupplementalSubtitleText();
+ method @Nullable public android.app.smartspace.uitemplatedata.TapAction getSupplementalTapAction();
+ method @Nullable public android.app.smartspace.uitemplatedata.Text getSupplementalText();
method public int getTemplateType();
method @Nullable public android.app.smartspace.uitemplatedata.Icon getTitleIcon();
method @Nullable public android.app.smartspace.uitemplatedata.Text getTitleText();
@@ -2271,17 +2278,37 @@
public static class BaseTemplateData.Builder {
ctor public BaseTemplateData.Builder(int);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData build();
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setLayoutWeight(int);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setPrimaryLoggingInfo(@NonNull android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setPrimaryTapAction(@NonNull android.app.smartspace.uitemplatedata.TapAction);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.Icon);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSubtitleText(@NonNull android.app.smartspace.uitemplatedata.Text);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalAlarmText(@NonNull android.app.smartspace.uitemplatedata.Text);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalIcon(@NonNull android.app.smartspace.uitemplatedata.Icon);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalLoggingInfo(@NonNull android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.Icon);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleLoggingInfo(@NonNull android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleTapAction(@NonNull android.app.smartspace.uitemplatedata.TapAction);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalSubtitleText(@NonNull android.app.smartspace.uitemplatedata.Text);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalTapAction(@NonNull android.app.smartspace.uitemplatedata.TapAction);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setSupplementalText(@NonNull android.app.smartspace.uitemplatedata.Text);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setTitleIcon(@NonNull android.app.smartspace.uitemplatedata.Icon);
method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.Builder setTitleText(@NonNull android.app.smartspace.uitemplatedata.Text);
}
+ public static final class BaseTemplateData.SubItemLoggingInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getFeatureType();
+ method public int getInstanceId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo> CREATOR;
+ }
+
+ public static final class BaseTemplateData.SubItemLoggingInfo.Builder {
+ ctor public BaseTemplateData.SubItemLoggingInfo.Builder(int, int);
+ method @NonNull public android.app.smartspace.uitemplatedata.BaseTemplateData.SubItemLoggingInfo build();
+ }
+
public final class CarouselTemplateData extends android.app.smartspace.uitemplatedata.BaseTemplateData {
method @Nullable public android.app.smartspace.uitemplatedata.TapAction getCarouselAction();
method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.CarouselTemplateData.CarouselItem> getCarouselItems();
@@ -2408,6 +2435,7 @@
method @Nullable public android.content.Intent getIntent();
method @Nullable public android.app.PendingIntent getPendingIntent();
method @Nullable public android.os.UserHandle getUserHandle();
+ method public boolean shouldShowOnLockscreen();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.TapAction> CREATOR;
}
@@ -2418,6 +2446,7 @@
method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setIntent(@NonNull android.content.Intent);
method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setPendingIntent(@NonNull android.app.PendingIntent);
+ method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setShouldShowOnLockscreen(@NonNull boolean);
method @NonNull public android.app.smartspace.uitemplatedata.TapAction.Builder setUserHandle(@Nullable android.os.UserHandle);
}
@@ -2432,7 +2461,6 @@
public static final class Text.Builder {
ctor public Text.Builder(@NonNull CharSequence);
- ctor public Text.Builder(@NonNull CharSequence, @NonNull android.text.TextUtils.TruncateAt);
method @NonNull public android.app.smartspace.uitemplatedata.Text build();
method @NonNull public android.app.smartspace.uitemplatedata.Text.Builder setMaxLines(int);
method @NonNull public android.app.smartspace.uitemplatedata.Text.Builder setTruncateAtType(@NonNull android.text.TextUtils.TruncateAt);
@@ -2507,9 +2535,10 @@
package android.app.usage {
public final class BroadcastResponseStats implements android.os.Parcelable {
- ctor public BroadcastResponseStats(@NonNull String);
+ ctor public BroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
method public int describeContents();
method @IntRange(from=0) public int getBroadcastsDispatchedCount();
+ method @IntRange(from=1) public long getId();
method @IntRange(from=0) public int getNotificationsCancelledCount();
method @IntRange(from=0) public int getNotificationsPostedCount();
method @IntRange(from=0) public int getNotificationsUpdatedCount();
@@ -2566,13 +2595,13 @@
}
public final class UsageStatsManager {
- method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
+ method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void clearBroadcastResponseStats(@Nullable String, @IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
method @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long getLastTimeAnyComponentUsed(@NonNull String);
method public int getUsageSource();
method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged();
- method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.BroadcastResponseStats queryBroadcastResponseStats(@NonNull String, @IntRange(from=1) long);
+ method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.List<android.app.usage.BroadcastResponseStats> queryBroadcastResponseStats(@Nullable String, @IntRange(from=0) long);
method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
@@ -3730,13 +3759,25 @@
public final class SensorPrivacyManager {
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
- method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int, int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean);
}
public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener {
- method public void onSensorPrivacyChanged(int, boolean);
+ method public default void onSensorPrivacyChanged(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams);
+ method @Deprecated public void onSensorPrivacyChanged(int, boolean);
+ }
+
+ public static class SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams {
+ method public int getSensor();
+ method public int getToggleType();
+ method public boolean isEnabled();
}
}
@@ -5849,8 +5890,8 @@
method @IntRange(from=0, to=1023) public int getIssueOfDataClock();
method @IntRange(from=0, to=255) public int getIssueOfDataEphemeris();
method @Nullable public android.location.SatellitePvt.PositionEcef getPositionEcef();
- method @IntRange(from=0, to=604784) public int getTimeOfClock();
- method @IntRange(from=0, to=604784) public int getTimeOfEphemeris();
+ method @IntRange(from=0) public long getTimeOfClock();
+ method @IntRange(from=0) public long getTimeOfEphemeris();
method @FloatRange public double getTropoDelayMeters();
method @Nullable public android.location.SatellitePvt.VelocityEcef getVelocityEcef();
method public boolean hasIono();
@@ -5877,8 +5918,8 @@
method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataClock(@IntRange(from=0, to=1023) int);
method @NonNull public android.location.SatellitePvt.Builder setIssueOfDataEphemeris(@IntRange(from=0, to=255) int);
method @NonNull public android.location.SatellitePvt.Builder setPositionEcef(@NonNull android.location.SatellitePvt.PositionEcef);
- method @NonNull public android.location.SatellitePvt.Builder setTimeOfClock(@IntRange(from=0, to=604784) int);
- method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemeris(@IntRange(from=0, to=604784) int);
+ method @NonNull public android.location.SatellitePvt.Builder setTimeOfClock(@IntRange(from=0) long);
+ method @NonNull public android.location.SatellitePvt.Builder setTimeOfEphemeris(@IntRange(from=0) int);
method @NonNull public android.location.SatellitePvt.Builder setTropoDelayMeters(@FloatRange(from=0.0f, to=100.0f) double);
method @NonNull public android.location.SatellitePvt.Builder setVelocityEcef(@NonNull android.location.SatellitePvt.VelocityEcef);
}
@@ -6075,7 +6116,7 @@
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
- method public static boolean isUltrasoundSupported();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public boolean isUltrasoundSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
@@ -10003,6 +10044,21 @@
field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
}
+ public final class PermissionGroupUsage implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public CharSequence getAttributionLabel();
+ method @Nullable public CharSequence getAttributionTag();
+ method public long getLastAccessTimeMillis();
+ method @NonNull public String getPackageName();
+ method @NonNull public String getPermissionGroupName();
+ method @Nullable public CharSequence getProxyLabel();
+ method public int getUid();
+ method public boolean isActive();
+ method public boolean isPhoneCall();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.permission.PermissionGroupUsage> CREATOR;
+ }
+
public final class PermissionManager {
method public int checkDeviceIdentifierAccess(@Nullable String, @Nullable String, @Nullable String, int, int);
method public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
@@ -10019,6 +10075,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, long, int, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void stopOneTimePermissionSession(@NonNull String);
field @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) public static final String ACTION_REVIEW_PERMISSION_DECISIONS = "android.permission.action.REVIEW_PERMISSION_DECISIONS";
+ field public static final String EXTRA_PERMISSION_USAGES = "android.permission.extra.PERMISSION_USAGES";
field public static final int PERMISSION_GRANTED = 0; // 0x0
field public static final int PERMISSION_HARD_DENIED = 2; // 0x2
field public static final int PERMISSION_SOFT_DENIED = 1; // 0x1
@@ -10847,7 +10904,7 @@
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback);
method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback);
- method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback);
+ method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityUpdateCallback);
method public void onStopProximityUpdates();
field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6
field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3
@@ -10865,7 +10922,7 @@
method public void onSuccess(int, long);
}
- public static final class AttentionService.ProximityCallback {
+ public static final class AttentionService.ProximityUpdateCallback {
method public void onProximityUpdate(double);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 239bba5..29b8248 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -272,9 +272,12 @@
}
public class DreamManager {
+ method public boolean areDreamsSupported();
method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isDreaming();
+ method public boolean isScreensaverEnabled();
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@Nullable android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setDreamOverlay(@Nullable android.content.ComponentName);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setScreensaverEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
}
@@ -354,8 +357,11 @@
public final class PictureInPictureParams implements android.os.Parcelable {
method public java.util.List<android.app.RemoteAction> getActions();
method public float getAspectRatio();
+ method @Nullable public android.app.RemoteAction getCloseAction();
method public float getExpandedAspectRatio();
method public android.graphics.Rect getSourceRectHint();
+ method @Nullable public CharSequence getSubtitle();
+ method @Nullable public CharSequence getTitle();
method public boolean isSeamlessResizeEnabled();
}
@@ -413,6 +419,7 @@
method @NonNull public android.content.res.Configuration getConfiguration();
method public int getParentTaskId();
method @Nullable public android.app.PictureInPictureParams getPictureInPictureParams();
+ method public boolean getPreferDockBigOverlays();
method @NonNull public android.window.WindowContainerToken getToken();
method public boolean hasParentTask();
}
@@ -2051,17 +2058,6 @@
package android.permission {
- public final class PermGroupUsage {
- ctor public PermGroupUsage(@NonNull String, int, @NonNull String, long, boolean, boolean, @Nullable CharSequence);
- method @Nullable public CharSequence getAttribution();
- method public long getLastAccess();
- method @NonNull public String getPackageName();
- method @NonNull public String getPermGroupName();
- method public int getUid();
- method public boolean isActive();
- method public boolean isPhoneCall();
- }
-
public final class PermissionControllerManager {
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
@@ -2077,8 +2073,8 @@
}
public final class PermissionManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
- method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getIndicatorAppOpUsageData();
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getIndicatorAppOpUsageData(boolean);
method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
method @RequiresPermission(android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL) public void revokePostNotificationPermissionWithoutKillForTest(@NonNull String, int);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2b2f08f..11663a5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2965,6 +2965,27 @@
return false;
}
+ /**
+ * Specifies a preference to dock big overlays like the expanded picture-in-picture on TV
+ * (see {@link PictureInPictureParams.Builder#setExpandedAspectRatio}). Docking puts the
+ * big overlay side-by-side next to this activity, so that both windows are fully visible to
+ * the user.
+ *
+ * <p> If unspecified, whether the overlay window will be docked or not, will be defined
+ * by the system.
+ *
+ * <p> If specified, the system will try to respect the preference, but it may be
+ * overridden by a user preference.
+ *
+ * @param preferDockBigOverlays indicates that the activity prefers big overlays to be
+ * docked next to it instead of overlaying its content
+ *
+ * @see PictureInPictureParams.Builder#setExpandedAspectRatio
+ */
+ public void setPreferDockBigOverlays(boolean preferDockBigOverlays) {
+ ActivityClient.getInstance().setPreferDockBigOverlays(mToken, preferDockBigOverlays);
+ }
+
void dispatchMovedToDisplay(int displayId, Configuration config) {
updateDisplay(displayId);
onMovedToDisplay(displayId, config);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 4715e0f..cf8480c 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -324,6 +324,14 @@
}
}
+ void setPreferDockBigOverlays(IBinder token, boolean preferDockBigOverlays) {
+ try {
+ getActivityClientController().setPreferDockBigOverlays(token, preferDockBigOverlays);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
void toggleFreeformWindowingMode(IBinder token) {
try {
getActivityClientController().toggleFreeformWindowingMode(token);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 89dd9ef..9f15105 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -179,6 +179,12 @@
* @hide
*/
public static final int INSTR_FLAG_NO_RESTART = 1 << 3;
+ /**
+ * Force the check that instrumentation and the target package are signed with the same
+ * certificate even if {@link Build#IS_DEBUGGABLE} is {@code true}.
+ * @hide
+ */
+ public static final int INSTR_FLAG_ALWAYS_CHECK_SIGNATURE = 1 << 4;
static final class UidObserver extends IUidObserver.Stub {
final OnUidImportanceListener mListener;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 5012121..5ddaa80 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -350,6 +350,10 @@
/** See {@link #setTransientLaunch()}. */
private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
+ /** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */
+ private static final String KEY_LAUNCH_INTO_PIP_PARAMS =
+ "android.activity.launchIntoPipParams";
+
/**
* @see #setLaunchCookie
* @hide
@@ -444,6 +448,7 @@
private boolean mRemoveWithTaskOrganizer;
private boolean mLaunchedFromBubble;
private boolean mTransientLaunch;
+ private PictureInPictureParams mLaunchIntoPipParams;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1106,6 +1111,24 @@
return opts;
}
+ /**
+ * Creates an {@link ActivityOptions} instance that launch into picture-in-picture.
+ * This is normally used by a Host activity to start another activity that will directly enter
+ * picture-in-picture upon its creation.
+ * @param pictureInPictureParams {@link PictureInPictureParams} for launching the Activity to
+ * picture-in-picture mode.
+ */
+ @NonNull
+ public static ActivityOptions makeLaunchIntoPip(
+ @NonNull PictureInPictureParams pictureInPictureParams) {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mLaunchIntoPipParams = new PictureInPictureParams.Builder(pictureInPictureParams)
+ .setIsLaunchIntoPip(true)
+ .build();
+ opts.mLaunchBounds = new Rect(pictureInPictureParams.getSourceRectHint());
+ return opts;
+ }
+
/** @hide */
public boolean getLaunchTaskBehind() {
return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
@@ -1219,6 +1242,7 @@
mLaunchedFromBubble = opts.getBoolean(KEY_LAUNCHED_FROM_BUBBLE);
mTransientLaunch = opts.getBoolean(KEY_TRANSIENT_LAUNCH);
mSplashScreenStyle = opts.getInt(KEY_SPLASH_SCREEN_STYLE);
+ mLaunchIntoPipParams = opts.getParcelable(KEY_LAUNCH_INTO_PIP_PARAMS);
}
/**
@@ -1556,6 +1580,23 @@
mLaunchWindowingMode = windowingMode;
}
+ /**
+ * @return {@link PictureInPictureParams} used to launch into PiP mode.
+ * @hide
+ */
+ public PictureInPictureParams getLaunchIntoPipParams() {
+ return mLaunchIntoPipParams;
+ }
+
+ /**
+ * @return {@code true} if this instance is used to launch into PiP mode.
+ * @hide
+ */
+ public boolean isLaunchIntoPip() {
+ return mLaunchIntoPipParams != null
+ && mLaunchIntoPipParams.isLaunchIntoPip();
+ }
+
/** @hide */
public int getLaunchActivityType() {
return mLaunchActivityType;
@@ -1867,6 +1908,7 @@
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
+ mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
}
/**
@@ -2039,6 +2081,9 @@
if (mSplashScreenStyle != 0) {
b.putInt(KEY_SPLASH_SCREEN_STYLE, mSplashScreenStyle);
}
+ if (mLaunchIntoPipParams != null) {
+ b.putParcelable(KEY_LAUNCH_INTO_PIP_PARAMS, mLaunchIntoPipParams);
+ }
return b;
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0d1bc05..7c7c7ef 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2687,7 +2687,7 @@
AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
- getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
+ AppOpsManager.MODE_DEFAULT, // SYSTEM_ALERT_WINDOW /*Overridden in opToDefaultMode()*/
AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
AppOpsManager.MODE_ALLOWED, // CAMERA
AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
@@ -3011,6 +3011,8 @@
private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops";
private static final String DEBUG_LOGGING_TAG = "AppOpsManager";
+ private static volatile Integer sOpSystemAlertWindowDefaultMode;
+
/**
* Retrieve the op switch that controls the given operation.
* @hide
@@ -3109,6 +3111,9 @@
* @hide
*/
public static @Mode int opToDefaultMode(int op) {
+ if (op == OP_SYSTEM_ALERT_WINDOW) {
+ return getSystemAlertWindowDefault();
+ }
return sOpDefaultMode[op];
}
@@ -10113,6 +10118,11 @@
}
private static int getSystemAlertWindowDefault() {
+ // This is indeed racy but we aren't expecting the result to change so it's not worth
+ // the synchronization.
+ if (sOpSystemAlertWindowDefaultMode != null) {
+ return sOpSystemAlertWindowDefaultMode;
+ }
final Context context = ActivityThread.currentApplication();
if (context == null) {
return AppOpsManager.MODE_DEFAULT;
@@ -10123,10 +10133,11 @@
// TVs are constantly plugged in and has less concern for memory/power
if (ActivityManager.isLowRamDeviceStatic()
&& !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) {
- return AppOpsManager.MODE_IGNORED;
+ sOpSystemAlertWindowDefaultMode = AppOpsManager.MODE_IGNORED;
+ } else {
+ sOpSystemAlertWindowDefaultMode = AppOpsManager.MODE_DEFAULT;
}
-
- return AppOpsManager.MODE_DEFAULT;
+ return sOpSystemAlertWindowDefaultMode;
}
/**
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index 34ae08fd..00af1c02 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -26,6 +28,8 @@
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -48,6 +52,40 @@
}
/**
+ * Returns whether Settings.Secure.SCREENSAVER_ENABLED is enabled.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean isScreensaverEnabled() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
+ /**
+ * Sets whether Settings.Secure.SCREENSAVER_ENABLED is enabled.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(WRITE_SECURE_SETTINGS)
+ public void setScreensaverEnabled(boolean enabled) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Returns whether dreams are supported.
+ *
+ * @hide
+ */
+ @TestApi
+ public boolean areDreamsSupported() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsSupported);
+ }
+
+ /**
* Starts dream service with name "name".
*
* <p>This is only used for testing the dream service APIs.
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index f9439cb..caf1c41b7 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -88,6 +88,7 @@
boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureParams params);
void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
+ oneway void setPreferDockBigOverlays(in IBinder token, in boolean preferDockBigOverlays);
void toggleFreeformWindowingMode(in IBinder token);
oneway void startLockTaskModeByToken(in IBinder token);
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index 18343fd..2d2788c 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -51,19 +51,46 @@
private List<RemoteAction> mUserActions;
@Nullable
+ private RemoteAction mCloseAction;
+
+ @Nullable
private Rect mSourceRectHint;
private Boolean mAutoEnterEnabled;
private Boolean mSeamlessResizeEnabled;
+ private CharSequence mTitle;
+
+ private CharSequence mSubtitle;
+
+ private Boolean mIsLaunchIntoPip;
+
+ /** Default constructor */
+ public Builder() {}
+
+ /**
+ * Copy constructor
+ * @param original {@link PictureInPictureParams} instance this builder is built upon.
+ */
+ public Builder(@NonNull PictureInPictureParams original) {
+ mAspectRatio = original.mAspectRatio;
+ mUserActions = original.mUserActions;
+ mCloseAction = original.mCloseAction;
+ mSourceRectHint = original.mSourceRectHint;
+ mAutoEnterEnabled = original.mAutoEnterEnabled;
+ mSeamlessResizeEnabled = original.mSeamlessResizeEnabled;
+ mTitle = original.mTitle;
+ mSubtitle = original.mSubtitle;
+ mIsLaunchIntoPip = original.mIsLaunchIntoPip;
+ }
+
/**
* Sets the aspect ratio. This aspect ratio is defined as the desired width / height, and
* does not change upon device rotation.
*
* @param aspectRatio the new aspect ratio for the activity in picture-in-picture, must be
- * between 2.39:1 and 1:2.39 (inclusive).
- *
+ * between 2.39:1 and 1:2.39 (inclusive).
* @return this builder instance.
*/
public Builder setAspectRatio(Rational aspectRatio) {
@@ -111,6 +138,25 @@
}
/**
+ * Sets a close action that should be invoked before the default close PiP action. The
+ * custom action must close the activity quickly using {@link Activity#finish()}.
+ * Otherwise, the system will forcibly close the PiP as if no custom close action was
+ * provided.
+ *
+ * If the action matches one set via {@link PictureInPictureParams.Builder#setActions(List)}
+ * it may be shown in place of that custom action in the menu.
+ *
+ * @param action to replace the system close action
+ * @return this builder instance.
+ * @see RemoteAction
+ */
+ @NonNull
+ public Builder setCloseAction(@Nullable RemoteAction action) {
+ mCloseAction = action;
+ return this;
+ }
+
+ /**
* Sets the source bounds hint. These bounds are only used when an activity first enters
* picture-in-picture, and describe the bounds in window coordinates of activity entering
* picture-in-picture that will be visible following the transition. For the best effect,
@@ -168,6 +214,50 @@
}
/**
+ * Sets a title for the picture-in-picture window, which may be displayed by the system to
+ * give the user information about what this PIP is generally being used for.
+ *
+ * @param title General information about the PIP content
+ * @return this builder instance.
+ */
+ @NonNull
+ public Builder setTitle(@Nullable CharSequence title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets a subtitle for the picture-in-picture window, which may be displayed by the system
+ * to give the user more detailed information about what this PIP is displaying.<br/>
+ *
+ * Setting a title via {@link PictureInPictureParams.Builder#setTitle(CharSequence)} should
+ * be prioritized.
+ *
+ * @param subtitle Details about the PIP content.
+ * @return this builder instance
+ */
+ @NonNull
+ public Builder setSubtitle(@Nullable CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Sets whether the built {@link PictureInPictureParams} represents a launch into
+ * picture-in-picture request.
+ *
+ * This property is {@code false} by default.
+ * @param isLaunchIntoPip {@code true} if the built instance represents a launch into
+ * picture-in-picture request
+ * @return this builder instance.
+ */
+ @NonNull
+ Builder setIsLaunchIntoPip(boolean isLaunchIntoPip) {
+ mIsLaunchIntoPip = isLaunchIntoPip;
+ return this;
+ }
+
+ /**
* @return an immutable {@link PictureInPictureParams} to be used when entering or updating
* the activity in picture-in-picture.
*
@@ -176,8 +266,9 @@
*/
public PictureInPictureParams build() {
PictureInPictureParams params = new PictureInPictureParams(mAspectRatio,
- mExpandedAspectRatio, mUserActions,
- mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled);
+ mExpandedAspectRatio, mUserActions, mCloseAction, mSourceRectHint,
+ mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle,
+ mIsLaunchIntoPip);
return params;
}
}
@@ -201,6 +292,12 @@
private List<RemoteAction> mUserActions;
/**
+ * Action to replace the system close action.
+ */
+ @Nullable
+ private RemoteAction mCloseAction;
+
+ /**
* The source bounds hint used when entering picture-in-picture, relative to the window bounds.
* We can use this internally for the transition into picture-in-picture to ensure that a
* particular source rect is visible throughout the whole transition.
@@ -221,6 +318,25 @@
*/
private Boolean mSeamlessResizeEnabled;
+ /**
+ * Title of the picture-in-picture window to be displayed to the user.
+ */
+ @Nullable
+ private CharSequence mTitle;
+
+ /**
+ * Subtitle for the picture-in-picture window to be displayed to the user.
+ */
+ @Nullable
+ private CharSequence mSubtitle;
+
+ /**
+ * Whether this {@link PictureInPictureParams} represents a launch into
+ * picture-in-picture request.
+ * {@link #isLaunchIntoPip()} defaults to {@code false} is this is not set.
+ */
+ private Boolean mIsLaunchIntoPip;
+
/** {@hide} */
PictureInPictureParams() {
}
@@ -233,6 +349,7 @@
mUserActions = new ArrayList<>();
in.readTypedList(mUserActions, RemoteAction.CREATOR);
}
+ mCloseAction = in.readTypedObject(RemoteAction.CREATOR);
if (in.readInt() != 0) {
mSourceRectHint = Rect.CREATOR.createFromParcel(in);
}
@@ -242,18 +359,32 @@
if (in.readInt() != 0) {
mSeamlessResizeEnabled = in.readBoolean();
}
+ if (in.readInt() != 0) {
+ mTitle = in.readCharSequence();
+ }
+ if (in.readInt() != 0) {
+ mSubtitle = in.readCharSequence();
+ }
+ if (in.readInt() != 0) {
+ mIsLaunchIntoPip = in.readBoolean();
+ }
}
/** {@hide} */
PictureInPictureParams(Rational aspectRatio, Rational expandedAspectRatio,
- List<RemoteAction> actions, Rect sourceRectHint, Boolean autoEnterEnabled,
- Boolean seamlessResizeEnabled) {
+ List<RemoteAction> actions, RemoteAction closeAction, Rect sourceRectHint,
+ Boolean autoEnterEnabled, Boolean seamlessResizeEnabled, CharSequence title,
+ CharSequence subtitle, Boolean isLaunchIntoPip) {
mAspectRatio = aspectRatio;
mExpandedAspectRatio = expandedAspectRatio;
mUserActions = actions;
+ mCloseAction = closeAction;
mSourceRectHint = sourceRectHint;
mAutoEnterEnabled = autoEnterEnabled;
mSeamlessResizeEnabled = seamlessResizeEnabled;
+ mTitle = title;
+ mSubtitle = subtitle;
+ mIsLaunchIntoPip = isLaunchIntoPip;
}
/**
@@ -261,9 +392,10 @@
* @hide
*/
public PictureInPictureParams(PictureInPictureParams other) {
- this(other.mAspectRatio, other.mExpandedAspectRatio, other.mUserActions,
+ this(other.mAspectRatio, other.mExpandedAspectRatio, other.mUserActions, other.mCloseAction,
other.hasSourceBoundsHint() ? new Rect(other.getSourceRectHint()) : null,
- other.mAutoEnterEnabled, other.mSeamlessResizeEnabled);
+ other.mAutoEnterEnabled, other.mSeamlessResizeEnabled,
+ other.mTitle, other.mSubtitle, other.mIsLaunchIntoPip);
}
/**
@@ -281,6 +413,9 @@
if (otherArgs.hasSetActions()) {
mUserActions = otherArgs.mUserActions;
}
+ if (otherArgs.hasSetCloseAction()) {
+ mCloseAction = otherArgs.mCloseAction;
+ }
if (otherArgs.hasSourceBoundsHint()) {
mSourceRectHint = new Rect(otherArgs.getSourceRectHint());
}
@@ -290,6 +425,15 @@
if (otherArgs.mSeamlessResizeEnabled != null) {
mSeamlessResizeEnabled = otherArgs.mSeamlessResizeEnabled;
}
+ if (otherArgs.hasSetTitle()) {
+ mTitle = otherArgs.mTitle;
+ }
+ if (otherArgs.hasSetSubtitle()) {
+ mSubtitle = otherArgs.mSubtitle;
+ }
+ if (otherArgs.mIsLaunchIntoPip != null) {
+ mIsLaunchIntoPip = otherArgs.mIsLaunchIntoPip;
+ }
}
/**
@@ -355,7 +499,26 @@
}
/**
+ * @return the close action.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public RemoteAction getCloseAction() {
+ return mCloseAction;
+ }
+
+ /**
+ * @return whether the close action was set.
+ * @hide
+ */
+ public boolean hasSetCloseAction() {
+ return mCloseAction != null;
+ }
+
+ /**
* Truncates the set of actions to the given {@param size}.
+ *
* @hide
*/
public void truncateActions(int size) {
@@ -399,13 +562,58 @@
}
/**
+ * @return whether a title was set.
+ * @hide
+ */
+ public boolean hasSetTitle() {
+ return mTitle != null;
+ }
+
+ /**
+ * @return title of the pip.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * @return whether a subtitle was set.
+ * @hide
+ */
+ public boolean hasSetSubtitle() {
+ return mSubtitle != null;
+ }
+
+ /**
+ * @return subtitle of the pip.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ /**
+ * @return whether this {@link PictureInPictureParams} represents a launch into pip request.
+ * @hide
+ */
+ public boolean isLaunchIntoPip() {
+ return mIsLaunchIntoPip == null ? false : mIsLaunchIntoPip;
+ }
+
+ /**
* @return True if no parameters are set
* @hide
*/
public boolean empty() {
- return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio()
- && !hasSetExpandedAspectRatio() && mAutoEnterEnabled != null
- && mSeamlessResizeEnabled != null;
+ return !hasSourceBoundsHint() && !hasSetActions() && !hasSetCloseAction()
+ && !hasSetAspectRatio() && !hasSetExpandedAspectRatio() && mAutoEnterEnabled == null
+ && mSeamlessResizeEnabled == null && !hasSetTitle()
+ && !hasSetSubtitle() && mIsLaunchIntoPip == null;
}
@Override
@@ -418,13 +626,18 @@
&& Objects.equals(mAspectRatio, that.mAspectRatio)
&& Objects.equals(mExpandedAspectRatio, that.mExpandedAspectRatio)
&& Objects.equals(mUserActions, that.mUserActions)
- && Objects.equals(mSourceRectHint, that.mSourceRectHint);
+ && Objects.equals(mCloseAction, that.mCloseAction)
+ && Objects.equals(mSourceRectHint, that.mSourceRectHint)
+ && Objects.equals(mTitle, that.mTitle)
+ && Objects.equals(mSubtitle, that.mSubtitle)
+ && Objects.equals(mIsLaunchIntoPip, that.mIsLaunchIntoPip);
}
@Override
public int hashCode() {
- return Objects.hash(mAspectRatio, mExpandedAspectRatio, mUserActions, mSourceRectHint,
- mAutoEnterEnabled, mSeamlessResizeEnabled);
+ return Objects.hash(mAspectRatio, mExpandedAspectRatio, mUserActions, mCloseAction,
+ mSourceRectHint, mAutoEnterEnabled, mSeamlessResizeEnabled, mTitle, mSubtitle,
+ mIsLaunchIntoPip);
}
@Override
@@ -442,6 +655,9 @@
} else {
out.writeInt(0);
}
+
+ out.writeTypedObject(mCloseAction, 0);
+
if (mSourceRectHint != null) {
out.writeInt(1);
mSourceRectHint.writeToParcel(out, 0);
@@ -460,6 +676,24 @@
} else {
out.writeInt(0);
}
+ if (mTitle != null) {
+ out.writeInt(1);
+ out.writeCharSequence(mTitle);
+ } else {
+ out.writeInt(0);
+ }
+ if (mSubtitle != null) {
+ out.writeInt(1);
+ out.writeCharSequence(mSubtitle);
+ } else {
+ out.writeInt(0);
+ }
+ if (mIsLaunchIntoPip != null) {
+ out.writeInt(1);
+ out.writeBoolean(mIsLaunchIntoPip);
+ } else {
+ out.writeInt(0);
+ }
}
private void writeRationalToParcel(Rational rational, Parcel out) {
@@ -486,8 +720,12 @@
+ " expandedAspectRatio=" + mExpandedAspectRatio
+ " sourceRectHint=" + getSourceRectHint()
+ " hasSetActions=" + hasSetActions()
+ + " hasSetCloseAction=" + hasSetCloseAction()
+ " isAutoPipEnabled=" + isAutoEnterEnabled()
+ " isSeamlessResizeEnabled=" + isSeamlessResizeEnabled()
+ + " title=" + getTitle()
+ + " subtitle=" + getSubtitle()
+ + " isLaunchIntoPip=" + isLaunchIntoPip()
+ ")";
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 3d2c03d..5c7c73c 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -186,6 +186,18 @@
public PictureInPictureParams pictureInPictureParams;
/**
+ * @hide
+ */
+ public boolean preferDockBigOverlays;
+
+ /**
+ * The task id of the host Task of the launch-into-pip Activity, i.e., it points to the Task
+ * the launch-into-pip Activity is originated from.
+ * @hide
+ */
+ public int launchIntoPipHostTaskId;
+
+ /**
* The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
* (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
* {@code null} otherwise.
@@ -379,6 +391,12 @@
}
/** @hide */
+ @TestApi
+ public boolean getPreferDockBigOverlays() {
+ return preferDockBigOverlays;
+ }
+
+ /** @hide */
@WindowConfiguration.WindowingMode
public int getWindowingMode() {
return configuration.windowConfiguration.getWindowingMode();
@@ -447,6 +465,7 @@
&& displayAreaFeatureId == that.displayAreaFeatureId
&& Objects.equals(positionInParent, that.positionInParent)
&& Objects.equals(pictureInPictureParams, that.pictureInPictureParams)
+ && Objects.equals(preferDockBigOverlays, that.preferDockBigOverlays)
&& Objects.equals(displayCutoutInsets, that.displayCutoutInsets)
&& getWindowingMode() == that.getWindowingMode()
&& Objects.equals(taskDescription, that.taskDescription)
@@ -503,6 +522,8 @@
token = WindowContainerToken.CREATOR.createFromParcel(source);
topActivityType = source.readInt();
pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
+ preferDockBigOverlays = source.readBoolean();
+ launchIntoPipHostTaskId = source.readInt();
displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
isResizeable = source.readBoolean();
@@ -548,6 +569,8 @@
token.writeToParcel(dest, flags);
dest.writeInt(topActivityType);
dest.writeTypedObject(pictureInPictureParams, flags);
+ dest.writeBoolean(preferDockBigOverlays);
+ dest.writeInt(launchIntoPipHostTaskId);
dest.writeTypedObject(displayCutoutInsets, flags);
dest.writeTypedObject(topActivityInfo, flags);
dest.writeBoolean(isResizeable);
@@ -587,6 +610,8 @@
+ " token=" + token
+ " topActivityType=" + topActivityType
+ " pictureInPictureParams=" + pictureInPictureParams
+ + " preferDockBigOverlays=" + preferDockBigOverlays
+ + " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+ " displayCutoutSafeInsets=" + displayCutoutInsets
+ " topActivityInfo=" + topActivityInfo
+ " launchCookies=" + launchCookies
diff --git a/core/java/android/app/assist/OWNERS b/core/java/android/app/assist/OWNERS
index 46b5ea0..e857c72 100644
--- a/core/java/android/app/assist/OWNERS
+++ b/core/java/android/app/assist/OWNERS
@@ -1,7 +1,5 @@
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+joannechung@google.com
+markpun@google.com
+lpeter@google.com
+tymtsai@google.com
diff --git a/core/java/android/app/cloudsearch/SearchRequest.java b/core/java/android/app/cloudsearch/SearchRequest.java
index ef66c0c..4d6507a 100644
--- a/core/java/android/app/cloudsearch/SearchRequest.java
+++ b/core/java/android/app/cloudsearch/SearchRequest.java
@@ -83,11 +83,13 @@
* presubmit is the input before the user finishes the entire query, i.e. push "ENTER" or
* "SEARCH" button. After the user finishes the entire query, the behavior is postsubmit.
*/
- public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION = "IS_PRESUBMIT_SUGGESTION";
+ public static final String CONSTRAINT_IS_PRESUBMIT_SUGGESTION =
+ "android.app.cloudsearch.IS_PRESUBMIT_SUGGESTION";
/** The target search provider list of package names(separated by ;), String value expected.
* If this is not provided or its value is empty, then no filter will be applied.
*/
- public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER = "SEARCH_PROVIDER_FILTER";
+ public static final String CONSTRAINT_SEARCH_PROVIDER_FILTER =
+ "android.app.cloudsearch.SEARCH_PROVIDER_FILTER";
@NonNull
private Bundle mSearchConstraints;
diff --git a/core/java/android/app/cloudsearch/SearchResult.java b/core/java/android/app/cloudsearch/SearchResult.java
index 3403ab0..af8adac 100644
--- a/core/java/android/app/cloudsearch/SearchResult.java
+++ b/core/java/android/app/cloudsearch/SearchResult.java
@@ -75,47 +75,54 @@
EXTRAINFO_WEB_ICON})
public @interface SearchResultExtraInfoKey {}
/** This App developer website's domain URL, String value expected. */
- public static final String EXTRAINFO_APP_DOMAIN_URL = "APP_DOMAIN_URL";
+ public static final String EXTRAINFO_APP_DOMAIN_URL = "android.app.cloudsearch.APP_DOMAIN_URL";
/** This App icon, android.graphics.drawable.Icon expected. */
- public static final String EXTRAINFO_APP_ICON = "APP_ICON";
+ public static final String EXTRAINFO_APP_ICON = "android.app.cloudsearch.APP_ICON";
/** This App developer's name, String value expected. */
- public static final String EXTRAINFO_APP_DEVELOPER_NAME = "APP_DEVELOPER_NAME";
+ public static final String EXTRAINFO_APP_DEVELOPER_NAME =
+ "android.app.cloudsearch.APP_DEVELOPER_NAME";
/** This App's pkg size in bytes, Double value expected. */
- public static final String EXTRAINFO_APP_SIZE_BYTES = "APP_SIZE_BYTES";
+ public static final String EXTRAINFO_APP_SIZE_BYTES = "android.app.cloudsearch.APP_SIZE_BYTES";
/** This App developer's name, Double value expected. */
- public static final String EXTRAINFO_APP_STAR_RATING = "APP_STAR_RATING";
+ public static final String EXTRAINFO_APP_STAR_RATING =
+ "android.app.cloudsearch.APP_STAR_RATING";
/** This App's IARC rating, String value expected.
* IARC (International Age Rating Coalition) is partnered globally with major
* content rating organizations to provide a centralized and one-stop-shop for
* rating content on a global scale.
*/
- public static final String EXTRAINFO_APP_IARC = "APP_IARC";
+ public static final String EXTRAINFO_APP_IARC = "android.app.cloudsearch.APP_IARC";
/** This App's review count, Double value expected. */
- public static final String EXTRAINFO_APP_REVIEW_COUNT = "APP_REVIEW_COUNT";
+ public static final String EXTRAINFO_APP_REVIEW_COUNT =
+ "android.app.cloudsearch.APP_REVIEW_COUNT";
/** If this App contains the Ads Disclaimer, Boolean value expected. */
public static final String EXTRAINFO_APP_CONTAINS_ADS_DISCLAIMER =
- "APP_CONTAINS_ADS_DISCLAIMER";
+ "android.app.cloudsearch.APP_CONTAINS_ADS_DISCLAIMER";
/** If this App contains the IAP Disclaimer, Boolean value expected. */
public static final String EXTRAINFO_APP_CONTAINS_IAP_DISCLAIMER =
- "APP_CONTAINS_IAP_DISCLAIMER";
+ "android.app.cloudsearch.APP_CONTAINS_IAP_DISCLAIMER";
/** This App's short description, String value expected. */
- public static final String EXTRAINFO_SHORT_DESCRIPTION = "SHORT_DESCRIPTION";
+ public static final String EXTRAINFO_SHORT_DESCRIPTION =
+ "android.app.cloudsearch.SHORT_DESCRIPTION";
/** This App's long description, String value expected. */
- public static final String EXTRAINFO_LONG_DESCRIPTION = "LONG_DESCRIPTION";
- /** This App's screenshots, List<ImageLoadingBundle> value expected. */
- public static final String EXTRAINFO_SCREENSHOTS = "SCREENSHOTS";
- /** Editor's choices for this App, ArrayList<String> value expected. */
- public static final String EXTRAINFO_APP_BADGES = "APP_BADGES";
+ public static final String EXTRAINFO_LONG_DESCRIPTION =
+ "android.app.cloudsearch.LONG_DESCRIPTION";
+ /** This App's screenshots, {@code List<ImageLoadingBundle>} value expected. */
+ public static final String EXTRAINFO_SCREENSHOTS = "android.app.cloudsearch.SCREENSHOTS";
+ /** Editor's choices for this App, {@code ArrayList<String>} value expected. */
+ public static final String EXTRAINFO_APP_BADGES = "android.app.cloudsearch.APP_BADGES";
/** Pre-registration game's action button text, String value expected. */
@SuppressLint("IntentName")
- public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING = "ACTION_BUTTON_TEXT";
+ public static final String EXTRAINFO_ACTION_BUTTON_TEXT_PREREGISTERING =
+ "android.app.cloudsearch.ACTION_BUTTON_TEXT";
/** Pre-registration game's action button image, ImageLoadingBundle value expected. */
@SuppressLint("IntentName")
- public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING = "ACTION_BUTTON_IMAGE";
+ public static final String EXTRAINFO_ACTION_BUTTON_IMAGE_PREREGISTERING =
+ "android.app.cloudsearch.ACTION_BUTTON_IMAGE";
/** Web content's URL, String value expected. */
- public static final String EXTRAINFO_WEB_URL = "WEB_URL";
+ public static final String EXTRAINFO_WEB_URL = "android.app.cloudsearch.WEB_URL";
/** Web content's domain icon, android.graphics.drawable.Icon expected. */
- public static final String EXTRAINFO_WEB_ICON = "WEB_ICON";
+ public static final String EXTRAINFO_WEB_ICON = "android.app.cloudsearch.WEB_ICON";
@NonNull
private Bundle mExtraInfos;
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index 482abb2..cf54c2a 100644
--- a/core/java/android/app/contentsuggestions/OWNERS
+++ b/core/java/android/app/contentsuggestions/OWNERS
@@ -1,9 +1,7 @@
# Bug component: 643919
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+joannechung@google.com
+markpun@google.com
+lpeter@google.com
+tymtsai@google.com
diff --git a/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java
index a07af68..584b176 100644
--- a/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/BaseTemplateData.java
@@ -33,6 +33,9 @@
* <li> title_text (may contain a start drawable) </li>
* <li> subtitle_text (may contain a start drawable) . supplemental_subtitle_text (may
* contain a start drawable) </li>
+ *
+ * <li> supplemental_text (contain a start drawable) . do_not_disturb_view </li>
+ * Or
* <li> next_alarm_text (contain a start drawable) + supplemental_alarm_text .
* do_not_disturb_view </li>
* </ul>
@@ -77,6 +80,14 @@
private final TapAction mPrimaryTapAction;
/**
+ * Primary logging info for the entire card. This will only be used when rendering a sub card
+ * within the base card. For the base card itself, BcSmartspaceCardLoggingInfo should be used,
+ * which has the display-specific info (e.g. display surface).
+ */
+ @Nullable
+ private final SubItemLoggingInfo mPrimaryLoggingInfo;
+
+ /**
* Supplemental subtitle text and icon are shown at the second row following the subtitle text.
* Mainly used for weather info on non-weather card.
*/
@@ -87,19 +98,47 @@
private final Icon mSupplementalSubtitleIcon;
/**
- * Tap action for the supplemental subtitle's text and icon. Will use the primary tap action if
+ * Tap action for the supplemental subtitle's text and icon. Uses the primary tap action if
* not being set.
*/
@Nullable
private final TapAction mSupplementalSubtitleTapAction;
/**
+ * Logging info for the supplemental subtitle's are. Uses the primary logging info if not being
+ * set.
+ */
+ @Nullable
+ private final SubItemLoggingInfo mSupplementalSubtitleLoggingInfo;
+
+ @Nullable
+ private final Text mSupplementalText;
+
+ @Nullable
+ private final Icon mSupplementalIcon;
+
+ @Nullable
+ private final TapAction mSupplementalTapAction;
+
+ /**
+ * Logging info for the supplemental line. Uses the primary logging info if not being set.
+ */
+ @Nullable
+ private final SubItemLoggingInfo mSupplementalLoggingInfo;
+
+ /**
* Supplemental alarm text is specifically used for holiday alarm, which is appended to "next
* alarm".
*/
@Nullable
private final Text mSupplementalAlarmText;
+ /**
+ * The layout weight info for the card, which indicates how much space it should occupy on the
+ * screen. Default weight is 0.
+ */
+ private final int mLayoutWeight;
+
BaseTemplateData(@NonNull Parcel in) {
mTemplateType = in.readInt();
mTitleText = in.readTypedObject(Text.CREATOR);
@@ -107,10 +146,17 @@
mSubtitleText = in.readTypedObject(Text.CREATOR);
mSubtitleIcon = in.readTypedObject(Icon.CREATOR);
mPrimaryTapAction = in.readTypedObject(TapAction.CREATOR);
+ mPrimaryLoggingInfo = in.readTypedObject(SubItemLoggingInfo.CREATOR);
mSupplementalSubtitleText = in.readTypedObject(Text.CREATOR);
mSupplementalSubtitleIcon = in.readTypedObject(Icon.CREATOR);
mSupplementalSubtitleTapAction = in.readTypedObject(TapAction.CREATOR);
+ mSupplementalSubtitleLoggingInfo = in.readTypedObject(SubItemLoggingInfo.CREATOR);
+ mSupplementalText = in.readTypedObject(Text.CREATOR);
+ mSupplementalIcon = in.readTypedObject(Icon.CREATOR);
+ mSupplementalTapAction = in.readTypedObject(TapAction.CREATOR);
+ mSupplementalLoggingInfo = in.readTypedObject(SubItemLoggingInfo.CREATOR);
mSupplementalAlarmText = in.readTypedObject(Text.CREATOR);
+ mLayoutWeight = in.readInt();
}
/**
@@ -123,20 +169,34 @@
@Nullable Text subtitleText,
@Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
- @Nullable Text supplementalAlarmText) {
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
+ @Nullable Text supplementalAlarmText,
+ int layoutWeight) {
mTemplateType = templateType;
mTitleText = titleText;
mTitleIcon = titleIcon;
mSubtitleText = subtitleText;
mSubtitleIcon = subtitleIcon;
mPrimaryTapAction = primaryTapAction;
+ mPrimaryLoggingInfo = primaryLoggingInfo;
mSupplementalSubtitleText = supplementalSubtitleText;
mSupplementalSubtitleIcon = supplementalSubtitleIcon;
mSupplementalSubtitleTapAction = supplementalSubtitleTapAction;
+ mSupplementalSubtitleLoggingInfo = supplementalSubtitleLoggingInfo;
+ mSupplementalText = supplementalText;
+ mSupplementalIcon = supplementalIcon;
+ mSupplementalTapAction = supplementalTapAction;
+ mSupplementalLoggingInfo = supplementalLoggingInfo;
mSupplementalAlarmText = supplementalAlarmText;
+ mLayoutWeight = layoutWeight;
}
/** Returns the template type. By default is UNDEFINED. */
@@ -175,6 +235,12 @@
return mPrimaryTapAction;
}
+ /** Returns the card's primary logging info. */
+ @Nullable
+ public SubItemLoggingInfo getPrimaryLoggingInfo() {
+ return mPrimaryLoggingInfo;
+ }
+
/** Returns the supplemental subtitle's text. */
@Nullable
public Text getSupplementalSubtitleText() {
@@ -193,12 +259,47 @@
return mSupplementalSubtitleTapAction;
}
+ /** Returns the card's supplemental title's logging info. */
+ @Nullable
+ public SubItemLoggingInfo getSupplementalSubtitleLoggingInfo() {
+ return mSupplementalSubtitleLoggingInfo;
+ }
+
+ /** Returns the supplemental text. */
+ @Nullable
+ public Text getSupplementalText() {
+ return mSupplementalText;
+ }
+
+ /** Returns the supplemental icon. */
+ @Nullable
+ public Icon getSupplementalIcon() {
+ return mSupplementalIcon;
+ }
+
+ /** Returns the supplemental line's tap action. Can be null if not being set. */
+ @Nullable
+ public TapAction getSupplementalTapAction() {
+ return mSupplementalTapAction;
+ }
+
+ /** Returns the card's supplemental line logging info. */
+ @Nullable
+ public SubItemLoggingInfo getSupplementalLoggingInfo() {
+ return mSupplementalLoggingInfo;
+ }
+
/** Returns the supplemental alarm text. */
@Nullable
public Text getSupplementalAlarmText() {
return mSupplementalAlarmText;
}
+ /** Returns the card layout weight info. Default weight is 0. */
+ public int getLayoutWeight() {
+ return mLayoutWeight;
+ }
+
/**
* @see Parcelable.Creator
*/
@@ -229,10 +330,17 @@
out.writeTypedObject(mSubtitleText, flags);
out.writeTypedObject(mSubtitleIcon, flags);
out.writeTypedObject(mPrimaryTapAction, flags);
+ out.writeTypedObject(mPrimaryLoggingInfo, flags);
out.writeTypedObject(mSupplementalSubtitleText, flags);
out.writeTypedObject(mSupplementalSubtitleIcon, flags);
out.writeTypedObject(mSupplementalSubtitleTapAction, flags);
+ out.writeTypedObject(mSupplementalSubtitleLoggingInfo, flags);
+ out.writeTypedObject(mSupplementalText, flags);
+ out.writeTypedObject(mSupplementalIcon, flags);
+ out.writeTypedObject(mSupplementalTapAction, flags);
+ out.writeTypedObject(mSupplementalLoggingInfo, flags);
out.writeTypedObject(mSupplementalAlarmText, flags);
+ out.writeInt(mLayoutWeight);
}
@Override
@@ -246,19 +354,31 @@
&& SmartspaceUtils.isEqual(mSubtitleText, that.mSubtitleText)
&& Objects.equals(mSubtitleIcon, that.mSubtitleIcon)
&& Objects.equals(mPrimaryTapAction, that.mPrimaryTapAction)
+ && Objects.equals(mPrimaryLoggingInfo, that.mPrimaryLoggingInfo)
&& SmartspaceUtils.isEqual(mSupplementalSubtitleText,
that.mSupplementalSubtitleText)
&& Objects.equals(mSupplementalSubtitleIcon, that.mSupplementalSubtitleIcon)
&& Objects.equals(mSupplementalSubtitleTapAction,
that.mSupplementalSubtitleTapAction)
- && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText);
+ && Objects.equals(mSupplementalSubtitleLoggingInfo,
+ that.mSupplementalSubtitleLoggingInfo)
+ && SmartspaceUtils.isEqual(mSupplementalText,
+ that.mSupplementalText)
+ && Objects.equals(mSupplementalIcon, that.mSupplementalIcon)
+ && Objects.equals(mSupplementalTapAction, that.mSupplementalTapAction)
+ && Objects.equals(mSupplementalLoggingInfo, that.mSupplementalLoggingInfo)
+ && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText)
+ && mLayoutWeight == that.mLayoutWeight;
}
@Override
public int hashCode() {
return Objects.hash(mTemplateType, mTitleText, mTitleIcon, mSubtitleText, mSubtitleIcon,
- mPrimaryTapAction, mSupplementalSubtitleText, mSupplementalSubtitleIcon,
- mSupplementalSubtitleTapAction, mSupplementalAlarmText);
+ mPrimaryTapAction, mPrimaryLoggingInfo, mSupplementalSubtitleText,
+ mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction,
+ mSupplementalSubtitleLoggingInfo,
+ mSupplementalText, mSupplementalIcon, mSupplementalTapAction,
+ mSupplementalLoggingInfo, mSupplementalAlarmText, mLayoutWeight);
}
@Override
@@ -270,10 +390,17 @@
+ ", mSubtitleText=" + mSubtitleText
+ ", mSubTitleIcon=" + mSubtitleIcon
+ ", mPrimaryTapAction=" + mPrimaryTapAction
+ + ", mPrimaryLoggingInfo=" + mPrimaryLoggingInfo
+ ", mSupplementalSubtitleText=" + mSupplementalSubtitleText
+ ", mSupplementalSubtitleIcon=" + mSupplementalSubtitleIcon
+ ", mSupplementalSubtitleTapAction=" + mSupplementalSubtitleTapAction
+ + ", mSupplementalSubtitleLoggingInfo=" + mSupplementalSubtitleLoggingInfo
+ + ", mSupplementalText=" + mSupplementalText
+ + ", mSupplementalIcon=" + mSupplementalIcon
+ + ", mSupplementalTapAction=" + mSupplementalTapAction
+ + ", mSupplementalLoggingInfo=" + mSupplementalLoggingInfo
+ ", mSupplementalAlarmText=" + mSupplementalAlarmText
+ + ", mLayoutWeight=" + mLayoutWeight
+ '}';
}
@@ -292,18 +419,26 @@
private Text mSubtitleText;
private Icon mSubtitleIcon;
private TapAction mPrimaryTapAction;
+ private SubItemLoggingInfo mPrimaryLoggingInfo;
private Text mSupplementalSubtitleText;
private Icon mSupplementalSubtitleIcon;
private TapAction mSupplementalSubtitleTapAction;
+ private SubItemLoggingInfo mSupplementalSubtitleLoggingInfo;
+ private Text mSupplementalText;
+ private Icon mSupplementalIcon;
+ private TapAction mSupplementalTapAction;
+ private SubItemLoggingInfo mSupplementalLoggingInfo;
private Text mSupplementalAlarmText;
+ private int mLayoutWeight;
/**
- * A builder for {@link BaseTemplateData}.
+ * A builder for {@link BaseTemplateData}. By default sets the layout weight to be 0.
*
* @param templateType the {@link UiTemplateType} of this template data.
*/
public Builder(@UiTemplateType int templateType) {
mTemplateType = templateType;
+ mLayoutWeight = 0;
}
/** Should ONLY be used by the subclasses */
@@ -351,6 +486,13 @@
/** Should ONLY be used by the subclasses */
@Nullable
@SuppressLint("GetterOnBuilder")
+ SubItemLoggingInfo getPrimaryLoggingInfo() {
+ return mPrimaryLoggingInfo;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
Text getSupplementalSubtitleText() {
return mSupplementalSubtitleText;
}
@@ -372,10 +514,51 @@
/** Should ONLY be used by the subclasses */
@Nullable
@SuppressLint("GetterOnBuilder")
+ SubItemLoggingInfo getSupplementalSubtitleLoggingInfo() {
+ return mSupplementalSubtitleLoggingInfo;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ Text getSupplementalText() {
+ return mSupplementalText;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ Icon getSupplementalIcon() {
+ return mSupplementalIcon;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ TapAction getSupplementalTapAction() {
+ return mSupplementalTapAction;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
+ SubItemLoggingInfo getSupplementalLoggingInfo() {
+ return mSupplementalLoggingInfo;
+ }
+
+ /** Should ONLY be used by the subclasses */
+ @Nullable
+ @SuppressLint("GetterOnBuilder")
Text getSupplementalAlarmText() {
return mSupplementalAlarmText;
}
+ /** Should ONLY be used by the subclasses */
+ @SuppressLint("GetterOnBuilder")
+ int getLayoutWeight() {
+ return mLayoutWeight;
+ }
+
/**
* Sets the card title.
*/
@@ -422,6 +605,15 @@
}
/**
+ * Sets the card primary logging info.
+ */
+ @NonNull
+ public Builder setPrimaryLoggingInfo(@NonNull SubItemLoggingInfo primaryLoggingInfo) {
+ mPrimaryLoggingInfo = primaryLoggingInfo;
+ return this;
+ }
+
+ /**
* Sets the supplemental subtitle text.
*/
@NonNull
@@ -443,8 +635,7 @@
/**
* Sets the supplemental subtitle tap action. {@code mPrimaryTapAction} will be used if not
- * being
- * set.
+ * being set.
*/
@NonNull
public Builder setSupplementalSubtitleTapAction(
@@ -454,6 +645,54 @@
}
/**
+ * Sets the card supplemental title's logging info.
+ */
+ @NonNull
+ public Builder setSupplementalSubtitleLoggingInfo(
+ @NonNull SubItemLoggingInfo supplementalSubtitleLoggingInfo) {
+ mSupplementalSubtitleLoggingInfo = supplementalSubtitleLoggingInfo;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental text.
+ */
+ @NonNull
+ public Builder setSupplementalText(@NonNull Text supplementalText) {
+ mSupplementalText = supplementalText;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental icon.
+ */
+ @NonNull
+ public Builder setSupplementalIcon(@NonNull Icon supplementalIcon) {
+ mSupplementalIcon = supplementalIcon;
+ return this;
+ }
+
+ /**
+ * Sets the supplemental line tap action. {@code mPrimaryTapAction} will be used if not
+ * being set.
+ */
+ @NonNull
+ public Builder setSupplementalTapAction(@NonNull TapAction supplementalTapAction) {
+ mSupplementalTapAction = supplementalTapAction;
+ return this;
+ }
+
+ /**
+ * Sets the card supplemental line's logging info.
+ */
+ @NonNull
+ public Builder setSupplementalLoggingInfo(
+ @NonNull SubItemLoggingInfo supplementalLoggingInfo) {
+ mSupplementalLoggingInfo = supplementalLoggingInfo;
+ return this;
+ }
+
+ /**
* Sets the supplemental alarm text.
*/
@NonNull
@@ -463,14 +702,136 @@
}
/**
+ * Sets the layout weight.
+ */
+ @NonNull
+ public Builder setLayoutWeight(int layoutWeight) {
+ mLayoutWeight = layoutWeight;
+ return this;
+ }
+
+ /**
* Builds a new SmartspaceDefaultUiTemplateData instance.
*/
@NonNull
public BaseTemplateData build() {
return new BaseTemplateData(mTemplateType, mTitleText, mTitleIcon,
- mSubtitleText, mSubtitleIcon, mPrimaryTapAction, mSupplementalSubtitleText,
- mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction,
- mSupplementalAlarmText);
+ mSubtitleText, mSubtitleIcon, mPrimaryTapAction,
+ mPrimaryLoggingInfo,
+ mSupplementalSubtitleText, mSupplementalSubtitleIcon,
+ mSupplementalSubtitleTapAction, mSupplementalSubtitleLoggingInfo,
+ mSupplementalText, mSupplementalIcon,
+ mSupplementalTapAction, mSupplementalLoggingInfo,
+ mSupplementalAlarmText, mLayoutWeight);
+ }
+ }
+
+ /**
+ * Holds all the logging info needed for a sub item within the base card. For example, the
+ * supplemental-subtitle part should have its own logging info.
+ */
+ public static final class SubItemLoggingInfo implements Parcelable {
+
+ /** A unique instance id for the sub item. */
+ private final int mInstanceId;
+
+ /** The feature type for this sub item. */
+ private final int mFeatureType;
+
+ SubItemLoggingInfo(@NonNull Parcel in) {
+ mInstanceId = in.readInt();
+ mFeatureType = in.readInt();
+ }
+
+ private SubItemLoggingInfo(int instanceId, int featureType) {
+ mInstanceId = instanceId;
+ mFeatureType = featureType;
+ }
+
+ public int getInstanceId() {
+ return mInstanceId;
+ }
+
+ public int getFeatureType() {
+ return mFeatureType;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SubItemLoggingInfo> CREATOR =
+ new Creator<SubItemLoggingInfo>() {
+ @Override
+ public SubItemLoggingInfo createFromParcel(Parcel in) {
+ return new SubItemLoggingInfo(in);
+ }
+
+ @Override
+ public SubItemLoggingInfo[] newArray(int size) {
+ return new SubItemLoggingInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mInstanceId);
+ out.writeInt(mFeatureType);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SubItemLoggingInfo)) return false;
+ SubItemLoggingInfo that = (SubItemLoggingInfo) o;
+ return mInstanceId == that.mInstanceId && mFeatureType == that.mFeatureType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mInstanceId, mFeatureType);
+ }
+
+ @Override
+ public String toString() {
+ return "SubItemLoggingInfo{"
+ + "mInstanceId=" + mInstanceId
+ + ", mFeatureType=" + mFeatureType
+ + '}';
+ }
+
+ /**
+ * A builder for {@link SubItemLoggingInfo} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+
+ private final int mInstanceId;
+ private final int mFeatureType;
+
+ /**
+ * A builder for {@link SubItemLoggingInfo}.
+ *
+ * @param instanceId A unique instance id for the sub item
+ * @param featureType The feature type for this sub item
+ */
+ public Builder(int instanceId, int featureType) {
+ mInstanceId = instanceId;
+ mFeatureType = featureType;
+ }
+
+ /** Builds a new {@link SubItemLoggingInfo} instance. */
+ @NonNull
+ public SubItemLoggingInfo build() {
+ return new SubItemLoggingInfo(mInstanceId, mFeatureType);
+ }
}
}
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java
index feb1c34..fbdb7be 100644
--- a/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/CarouselTemplateData.java
@@ -59,17 +59,29 @@
@Nullable Text titleText,
@Nullable Icon titleIcon,
@Nullable Text subtitleText,
- @Nullable Icon subTitleIcon,
+ @Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
@Nullable Text supplementalAlarmText,
+ int layoutWeight,
@NonNull List<CarouselItem> carouselItems,
@Nullable TapAction carouselAction) {
- super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
- supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
- supplementalAlarmText);
+ super(templateType, titleText, titleIcon, subtitleText, subtitleIcon,
+ primaryTapAction, primaryLoggingInfo,
+ supplementalSubtitleText, supplementalSubtitleIcon,
+ supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo,
+ supplementalText, supplementalIcon,
+ supplementalTapAction, supplementalLoggingInfo,
+ supplementalAlarmText, layoutWeight);
+
mCarouselItems = carouselItems;
mCarouselAction = carouselAction;
}
@@ -179,10 +191,14 @@
}
return new CarouselTemplateData(getTemplateType(), getTitleText(),
- getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(),
+ getTitleIcon(), getSubtitleText(), getSubtitleIcon(),
+ getPrimaryTapAction(), getPrimaryLoggingInfo(),
getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
- getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mCarouselItems,
- mCarouselAction);
+ getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(),
+ getSupplementalText(), getSupplementalIcon(),
+ getSupplementalTapAction(), getSupplementalLoggingInfo(),
+ getSupplementalAlarmText(), getLayoutWeight(),
+ mCarouselItems, mCarouselAction);
}
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java
index 13091e2..1d13066 100644
--- a/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/CombinedCardsTemplateData.java
@@ -54,16 +54,27 @@
@Nullable Text titleText,
@Nullable Icon titleIcon,
@Nullable Text subtitleText,
- @Nullable Icon subTitleIcon,
+ @Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
@Nullable Text supplementalAlarmText,
+ int layoutWeight,
@NonNull List<BaseTemplateData> combinedCardDataList) {
- super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
- supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
- supplementalAlarmText);
+ super(templateType, titleText, titleIcon, subtitleText, subtitleIcon,
+ primaryTapAction, primaryLoggingInfo,
+ supplementalSubtitleText, supplementalSubtitleIcon,
+ supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo,
+ supplementalText, supplementalIcon,
+ supplementalTapAction, supplementalLoggingInfo,
+ supplementalAlarmText, layoutWeight);
mCombinedCardDataList = combinedCardDataList;
}
@@ -151,9 +162,13 @@
throw new IllegalStateException("Please assign a value to all @NonNull args.");
}
return new CombinedCardsTemplateData(getTemplateType(), getTitleText(),
- getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(),
+ getTitleIcon(), getSubtitleText(), getSubtitleIcon(),
+ getPrimaryTapAction(), getPrimaryLoggingInfo(),
getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
- getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(),
+ getSupplementalText(), getSupplementalIcon(),
+ getSupplementalTapAction(), getSupplementalLoggingInfo(),
+ getSupplementalAlarmText(), getLayoutWeight(),
mCombinedCardDataList);
}
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java
index eb56e93..19177df 100644
--- a/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/HeadToHeadTemplateData.java
@@ -69,21 +69,33 @@
@Nullable Text titleText,
@Nullable Icon titleIcon,
@Nullable Text subtitleText,
- @Nullable Icon subTitleIcon,
+ @Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
@Nullable Text supplementalAlarmText,
+ int layoutWeight,
@Nullable Text headToHeadTitle,
@Nullable Icon headToHeadFirstCompetitorIcon,
@Nullable Icon headToHeadSecondCompetitorIcon,
@Nullable Text headToHeadFirstCompetitorText,
@Nullable Text headToHeadSecondCompetitorText,
@Nullable TapAction headToHeadAction) {
- super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
- supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
- supplementalAlarmText);
+ super(templateType, titleText, titleIcon, subtitleText, subtitleIcon,
+ primaryTapAction, primaryLoggingInfo,
+ supplementalSubtitleText, supplementalSubtitleIcon,
+ supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo,
+ supplementalText, supplementalIcon,
+ supplementalTapAction, supplementalLoggingInfo,
+ supplementalAlarmText, layoutWeight);
+
mHeadToHeadTitle = headToHeadTitle;
mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon;
mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon;
@@ -285,9 +297,13 @@
@NonNull
public HeadToHeadTemplateData build() {
return new HeadToHeadTemplateData(getTemplateType(), getTitleText(),
- getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(),
+ getTitleIcon(), getSubtitleText(), getSubtitleIcon(),
+ getPrimaryTapAction(), getPrimaryLoggingInfo(),
getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
- getSupplementalSubtitleTapAction(), getSupplementalAlarmText(),
+ getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(),
+ getSupplementalText(), getSupplementalIcon(),
+ getSupplementalTapAction(), getSupplementalLoggingInfo(),
+ getSupplementalAlarmText(), getLayoutWeight(),
mHeadToHeadTitle,
mHeadToHeadFirstCompetitorIcon,
mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText,
diff --git a/core/java/android/app/smartspace/uitemplatedata/Icon.java b/core/java/android/app/smartspace/uitemplatedata/Icon.java
index 2b1f420..6bdc926 100644
--- a/core/java/android/app/smartspace/uitemplatedata/Icon.java
+++ b/core/java/android/app/smartspace/uitemplatedata/Icon.java
@@ -69,7 +69,10 @@
return mContentDescription;
}
- /** Return shouldTint value. The default value is true. */
+ /**
+ * Return shouldTint value, which means whether should tint the icon with the system's theme
+ * color. The default value is true.
+ */
public boolean shouldTint() {
return mShouldTint;
}
@@ -155,7 +158,7 @@
}
/**
- * Sets should tint icon.
+ * Sets should tint icon with the system's theme color.
*/
@NonNull
public Builder setShouldTint(boolean shouldTint) {
diff --git a/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java
index 9c8330d..48af9c1 100644
--- a/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/SubCardTemplateData.java
@@ -62,18 +62,30 @@
@Nullable Text titleText,
@Nullable Icon titleIcon,
@Nullable Text subtitleText,
- @Nullable Icon subTitleIcon,
+ @Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
@Nullable Text supplementalAlarmText,
+ int layoutWeight,
@NonNull Icon subCardIcon,
@Nullable Text subCardText,
@Nullable TapAction subCardAction) {
- super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
- supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
- supplementalAlarmText);
+ super(templateType, titleText, titleIcon, subtitleText, subtitleIcon,
+ primaryTapAction, primaryLoggingInfo,
+ supplementalSubtitleText, supplementalSubtitleIcon,
+ supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo,
+ supplementalText, supplementalIcon,
+ supplementalTapAction, supplementalLoggingInfo,
+ supplementalAlarmText, layoutWeight);
+
mSubCardIcon = subCardIcon;
mSubCardText = subCardText;
mSubCardAction = subCardAction;
@@ -196,9 +208,14 @@
@NonNull
public SubCardTemplateData build() {
return new SubCardTemplateData(getTemplateType(), getTitleText(),
- getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(),
+ getTitleIcon(), getSubtitleText(), getSubtitleIcon(),
+ getPrimaryTapAction(), getPrimaryLoggingInfo(),
getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
- getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubCardIcon,
+ getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(),
+ getSupplementalText(), getSupplementalIcon(),
+ getSupplementalTapAction(), getSupplementalLoggingInfo(),
+ getSupplementalAlarmText(), getLayoutWeight(),
+ mSubCardIcon,
mSubCardText,
mSubCardAction);
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java
index 7df5238..38692cd 100644
--- a/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/SubImageTemplateData.java
@@ -63,18 +63,30 @@
@Nullable Text titleText,
@Nullable Icon titleIcon,
@Nullable Text subtitleText,
- @Nullable Icon subTitleIcon,
+ @Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
@Nullable Text supplementalAlarmText,
+ int layoutWeight,
@NonNull List<Text> subImageTexts,
@NonNull List<Icon> subImages,
@Nullable TapAction subImageAction) {
- super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
- supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
- supplementalAlarmText);
+ super(templateType, titleText, titleIcon, subtitleText, subtitleIcon,
+ primaryTapAction, primaryLoggingInfo,
+ supplementalSubtitleText, supplementalSubtitleIcon,
+ supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo,
+ supplementalText, supplementalIcon,
+ supplementalTapAction, supplementalLoggingInfo,
+ supplementalAlarmText, layoutWeight);
+
mSubImageTexts = subImageTexts;
mSubImages = subImages;
mSubImageAction = subImageAction;
@@ -193,9 +205,14 @@
@NonNull
public SubImageTemplateData build() {
return new SubImageTemplateData(getTemplateType(), getTitleText(),
- getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(),
+ getTitleIcon(), getSubtitleText(), getSubtitleIcon(),
+ getPrimaryTapAction(), getPrimaryLoggingInfo(),
getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
- getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubImageTexts,
+ getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(),
+ getSupplementalText(), getSupplementalIcon(),
+ getSupplementalTapAction(), getSupplementalLoggingInfo(),
+ getSupplementalAlarmText(), getLayoutWeight(),
+ mSubImageTexts,
mSubImages,
mSubImageAction);
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java
index 6f6034d..b1535f1 100644
--- a/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java
+++ b/core/java/android/app/smartspace/uitemplatedata/SubListTemplateData.java
@@ -62,18 +62,30 @@
@Nullable Text titleText,
@Nullable Icon titleIcon,
@Nullable Text subtitleText,
- @Nullable Icon subTitleIcon,
+ @Nullable Icon subtitleIcon,
@Nullable TapAction primaryTapAction,
+ @Nullable SubItemLoggingInfo primaryLoggingInfo,
@Nullable Text supplementalSubtitleText,
@Nullable Icon supplementalSubtitleIcon,
@Nullable TapAction supplementalSubtitleTapAction,
+ @Nullable SubItemLoggingInfo supplementalSubtitleLoggingInfo,
+ @Nullable Text supplementalText,
+ @Nullable Icon supplementalIcon,
+ @Nullable TapAction supplementalTapAction,
+ @Nullable SubItemLoggingInfo supplementalLoggingInfo,
@Nullable Text supplementalAlarmText,
+ int layoutWeight,
@Nullable Icon subListIcon,
@NonNull List<Text> subListTexts,
@Nullable TapAction subListAction) {
- super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction,
- supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction,
- supplementalAlarmText);
+ super(templateType, titleText, titleIcon, subtitleText, subtitleIcon,
+ primaryTapAction, primaryLoggingInfo,
+ supplementalSubtitleText, supplementalSubtitleIcon,
+ supplementalSubtitleTapAction, supplementalSubtitleLoggingInfo,
+ supplementalText, supplementalIcon,
+ supplementalTapAction, supplementalLoggingInfo,
+ supplementalAlarmText, layoutWeight);
+
mSubListIcon = subListIcon;
mSubListTexts = subListTexts;
mSubListAction = subListAction;
@@ -196,9 +208,14 @@
@NonNull
public SubListTemplateData build() {
return new SubListTemplateData(getTemplateType(), getTitleText(),
- getTitleIcon(), getSubtitleText(), getSubtitleIcon(), getPrimaryTapAction(),
+ getTitleIcon(), getSubtitleText(), getSubtitleIcon(),
+ getPrimaryTapAction(), getPrimaryLoggingInfo(),
getSupplementalSubtitleText(), getSupplementalSubtitleIcon(),
- getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubListIcon,
+ getSupplementalSubtitleTapAction(), getSupplementalSubtitleLoggingInfo(),
+ getSupplementalText(), getSupplementalIcon(),
+ getSupplementalTapAction(), getSupplementalLoggingInfo(),
+ getSupplementalAlarmText(), getLayoutWeight(),
+ mSubListIcon,
mSubListTexts,
mSubListAction);
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/TapAction.java b/core/java/android/app/smartspace/uitemplatedata/TapAction.java
index 83ff6ab..b8e1afb 100644
--- a/core/java/android/app/smartspace/uitemplatedata/TapAction.java
+++ b/core/java/android/app/smartspace/uitemplatedata/TapAction.java
@@ -58,7 +58,13 @@
private final UserHandle mUserHandle;
@Nullable
- private Bundle mExtras;
+ private final Bundle mExtras;
+
+ /**
+ * Whether the tap action's result should be shown on the lockscreen (e.g. turn off the
+ * flashlight can be done on LS bypassing the keyguard). Default value is false.
+ */
+ private final boolean mShouldShowOnLockscreen;
TapAction(@NonNull Parcel in) {
mId = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -66,16 +72,18 @@
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mUserHandle = in.readTypedObject(UserHandle.CREATOR);
mExtras = in.readBundle();
+ mShouldShowOnLockscreen = in.readBoolean();
}
private TapAction(@Nullable CharSequence id, @Nullable Intent intent,
@Nullable PendingIntent pendingIntent, @Nullable UserHandle userHandle,
- @Nullable Bundle extras) {
+ @Nullable Bundle extras, boolean shouldShowOnLockscreen) {
mId = id;
mIntent = intent;
mPendingIntent = pendingIntent;
mUserHandle = userHandle;
mExtras = extras;
+ mShouldShowOnLockscreen = shouldShowOnLockscreen;
}
/** Returns the unique id of the tap action. */
@@ -110,6 +118,14 @@
return mExtras;
}
+ /**
+ * Whether the tap action's result should be shown on the lockscreen. If true, the tap action's
+ * handling should bypass the keyguard. Default value is false.
+ */
+ public boolean shouldShowOnLockscreen() {
+ return mShouldShowOnLockscreen;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
TextUtils.writeToParcel(mId, out, flags);
@@ -117,6 +133,7 @@
out.writeTypedObject(mPendingIntent, flags);
out.writeTypedObject(mUserHandle, flags);
out.writeBundle(mExtras);
+ out.writeBoolean(mShouldShowOnLockscreen);
}
@Override
@@ -158,6 +175,7 @@
+ ", mPendingIntent=" + mPendingIntent
+ ", mUserHandle=" + mUserHandle
+ ", mExtras=" + mExtras
+ + ", mShouldShowOnLockscreen=" + mShouldShowOnLockscreen
+ '}';
}
@@ -174,14 +192,16 @@
private PendingIntent mPendingIntent;
private UserHandle mUserHandle;
private Bundle mExtras;
+ private boolean mShouldShowOnLockScreen;
/**
- * A builder for {@link TapAction}.
+ * A builder for {@link TapAction}. By default sets should_show_on_lockscreen to false.
*
* @param id A unique Id of this {@link TapAction}.
*/
public Builder(@NonNull CharSequence id) {
mId = Objects.requireNonNull(id);
+ mShouldShowOnLockScreen = false;
}
/**
@@ -222,6 +242,16 @@
}
/**
+ * Sets whether the tap action's result should be shown on the lockscreen, to bypass the
+ * keyguard when the tap action is triggered.
+ */
+ @NonNull
+ public Builder setShouldShowOnLockscreen(@NonNull boolean shouldShowOnLockScreen) {
+ mShouldShowOnLockScreen = shouldShowOnLockScreen;
+ return this;
+ }
+
+ /**
* Builds a new SmartspaceTapAction instance.
*
* @throws IllegalStateException if the tap action is empty.
@@ -231,7 +261,8 @@
if (mIntent == null && mPendingIntent == null && mExtras == null) {
throw new IllegalStateException("Please assign at least 1 valid tap field");
}
- return new TapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras);
+ return new TapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras,
+ mShouldShowOnLockScreen);
}
}
}
diff --git a/core/java/android/app/smartspace/uitemplatedata/Text.java b/core/java/android/app/smartspace/uitemplatedata/Text.java
index b733394..e1afce7 100644
--- a/core/java/android/app/smartspace/uitemplatedata/Text.java
+++ b/core/java/android/app/smartspace/uitemplatedata/Text.java
@@ -131,16 +131,6 @@
}
/**
- * A builder for {@link Text} with specifying {@link TextUtils.TruncateAt} type, and by
- * default set the max lines to 1.
- */
- public Builder(@NonNull CharSequence text, @NonNull TextUtils.TruncateAt truncateAtType) {
- mText = Objects.requireNonNull(text);
- mTruncateAtType = Objects.requireNonNull(truncateAtType);
- mMaxLines = 1;
- }
-
- /**
* Sets truncateAtType, where the text content should be truncated if not all the content
* can be presented.
*/
diff --git a/core/java/android/app/usage/BroadcastResponseStats.java b/core/java/android/app/usage/BroadcastResponseStats.java
index 5acc3dda..e1d37e1 100644
--- a/core/java/android/app/usage/BroadcastResponseStats.java
+++ b/core/java/android/app/usage/BroadcastResponseStats.java
@@ -23,6 +23,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* Class containing a collection of stats related to response events started from an app
* after receiving a broadcast.
@@ -32,17 +34,30 @@
@SystemApi
public final class BroadcastResponseStats implements Parcelable {
private final String mPackageName;
+ private final long mId;
private int mBroadcastsDispatchedCount;
private int mNotificationsPostedCount;
private int mNotificationsUpdatedCount;
private int mNotificationsCancelledCount;
- public BroadcastResponseStats(@NonNull String packageName) {
+ /**
+ * Creates a new {@link BroadcastResponseStats} object that contain the stats for broadcasts
+ * with {@code id} (specified using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)} by the sender) that
+ * were sent to {@code packageName}.
+ *
+ * @param packageName the name of the package that broadcasts were sent to.
+ * @param id the ID specified by the sender using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ */
+ public BroadcastResponseStats(@NonNull String packageName, @IntRange(from = 1) long id) {
mPackageName = packageName;
+ mId = id;
}
private BroadcastResponseStats(@NonNull Parcel in) {
mPackageName = in.readString8();
+ mId = in.readLong();
mBroadcastsDispatchedCount = in.readInt();
mNotificationsPostedCount = in.readInt();
mNotificationsUpdatedCount = in.readInt();
@@ -58,6 +73,14 @@
}
/**
+ * @return the ID of the broadcasts that the stats in this object correspond to.
+ */
+ @IntRange(from = 1)
+ public long getId() {
+ return mId;
+ }
+
+ /**
* Returns the total number of broadcasts that were dispatched to the app by the caller.
*
* <b> Note that the returned count will only include the broadcasts that the caller explicitly
@@ -148,9 +171,35 @@
}
@Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || !(obj instanceof BroadcastResponseStats)) {
+ return false;
+ }
+ final BroadcastResponseStats other = (BroadcastResponseStats) obj;
+ return this.mBroadcastsDispatchedCount == other.mBroadcastsDispatchedCount
+ && this.mNotificationsPostedCount == other.mNotificationsPostedCount
+ && this.mNotificationsUpdatedCount == other.mNotificationsUpdatedCount
+ && this.mNotificationsCancelledCount == other.mNotificationsCancelledCount
+ && this.mId == other.mId
+ && this.mPackageName.equals(other.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mId, mBroadcastsDispatchedCount,
+ mNotificationsPostedCount, mNotificationsUpdatedCount,
+ mNotificationsCancelledCount);
+ }
+
+ @Override
public @NonNull String toString() {
return "stats {"
- + "broadcastsSent=" + mBroadcastsDispatchedCount
+ + "package=" + mPackageName
+ + ",id=" + mId
+ + ",broadcastsSent=" + mBroadcastsDispatchedCount
+ ",notificationsPosted=" + mNotificationsPostedCount
+ ",notificationsUpdated=" + mNotificationsUpdatedCount
+ ",notificationsCancelled=" + mNotificationsCancelledCount
@@ -165,6 +214,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
dest.writeString8(mPackageName);
+ dest.writeLong(mId);
dest.writeInt(mBroadcastsDispatchedCount);
dest.writeInt(mNotificationsPostedCount);
dest.writeInt(mNotificationsUpdatedCount);
diff --git a/apex/media/aidl/private/android/media/Session2Command.aidl b/core/java/android/app/usage/BroadcastResponseStatsList.aidl
similarity index 80%
rename from apex/media/aidl/private/android/media/Session2Command.aidl
rename to core/java/android/app/usage/BroadcastResponseStatsList.aidl
index 43a7b12..7380359 100644
--- a/apex/media/aidl/private/android/media/Session2Command.aidl
+++ b/core/java/android/app/usage/BroadcastResponseStatsList.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.media;
+package android.app.usage;
-parcelable Session2Command;
+/** {@hide} */
+parcelable BroadcastResponseStatsList;
\ No newline at end of file
diff --git a/core/java/android/app/usage/BroadcastResponseStatsList.java b/core/java/android/app/usage/BroadcastResponseStatsList.java
new file mode 100644
index 0000000..4d2ff286
--- /dev/null
+++ b/core/java/android/app/usage/BroadcastResponseStatsList.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** @hide */
+public final class BroadcastResponseStatsList implements Parcelable {
+ private List<BroadcastResponseStats> mBroadcastResponseStats;
+
+ public BroadcastResponseStatsList(
+ @NonNull List<BroadcastResponseStats> broadcastResponseStats) {
+ mBroadcastResponseStats = broadcastResponseStats;
+ }
+
+ private BroadcastResponseStatsList(@NonNull Parcel in) {
+ mBroadcastResponseStats = new ArrayList<>();
+ final byte[] bytes = in.readBlob();
+ final Parcel data = Parcel.obtain();
+ try {
+ data.unmarshall(bytes, 0, bytes.length);
+ data.setDataPosition(0);
+ data.readTypedList(mBroadcastResponseStats, BroadcastResponseStats.CREATOR);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @NonNull
+ public List<BroadcastResponseStats> getList() {
+ return mBroadcastResponseStats == null ? Collections.emptyList() : mBroadcastResponseStats;
+ }
+
+ @Override
+ public @ContentsFlags int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
+ final Parcel data = Parcel.obtain();
+ try {
+ data.writeTypedList(mBroadcastResponseStats);
+ dest.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ public static final @NonNull Creator<BroadcastResponseStatsList> CREATOR =
+ new Creator<BroadcastResponseStatsList>() {
+ @Override
+ public @NonNull BroadcastResponseStatsList createFromParcel(
+ @NonNull Parcel source) {
+ return new BroadcastResponseStatsList(source);
+ }
+
+ @Override
+ public @NonNull BroadcastResponseStatsList[] newArray(int size) {
+ return new BroadcastResponseStatsList[size];
+ }
+ };
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 6f8fea1..a430714 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.app.usage.BroadcastResponseStats;
+import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.UsageEvents;
import android.content.pm.ParceledListSlice;
@@ -73,9 +74,11 @@
void forceUsageSourceSettingRead();
long getLastTimeAnyComponentUsed(String packageName, String callingPackage);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
- BroadcastResponseStats queryBroadcastResponseStats(
+ BroadcastResponseStatsList queryBroadcastResponseStats(
String packageName, long id, String callingPackage, int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
void clearBroadcastResponseStats(String packageName, long id, String callingPackage,
int userId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
+ void clearBroadcastEvents(String callingPackage, int userId);
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index b81c62d..d7152b3 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -1397,29 +1397,46 @@
* Returns the broadcast response stats since the last boot corresponding to
* {@code packageName} and {@code id}.
*
- * <p>Broadcast response stats will include the aggregated data of what actions an app took upon
- * receiving a broadcast. This data will consider the broadcasts that the caller sent to
+ * <p> Broadcast response stats will include the aggregated data of what actions an app took
+ * upon receiving a broadcast. This data will consider the broadcasts that the caller sent to
* {@code packageName} and explicitly requested to record the response events using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
*
- * @param packageName The name of the package that the caller wants to query for.
- * @param id The ID corresponding to the broadcasts that the caller wants to query for. This is
- * the ID the caller specifies when requesting a broadcast response event to be
- * recorded using {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ * <p> The returned list could one or more {@link BroadcastResponseStats} objects or be empty
+ * depending on the {@code packageName} and {@code id} and whether there is any data
+ * corresponding to these. If the {@code packageName} is not {@code null} and {@code id} is
+ * {@code > 0}, then the returned list would contain at most one {@link BroadcastResponseStats}
+ * object. Otherwise, the returned list could contain more than one
+ * {@link BroadcastResponseStats} object in no particular order.
*
- * @return the broadcast response stats corresponding to {@code packageName} and {@code id}.
+ * <p> Note: It is possible that same {@code id} was used for broadcasts sent to different
+ * packages. So, callers can query the data corresponding to
+ * all broadcasts with a particular {@code id} by passing {@code packageName} as {@code null}.
*
+ * @param packageName The name of the package that the caller wants to query for
+ * or {@code null} to indicate that data corresponding to all packages
+ * should be returned.
+ * @param id The ID corresponding to the broadcasts that the caller wants to query for, or
+ * {@code 0} to indicate that data corresponding to all IDs should be returned.
+ * This is the ID the caller specifies when requesting a broadcast response event
+ * to be recorded using
+ * {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
+ *
+ * @return the list of broadcast response stats corresponding to {@code packageName}
+ * and {@code id}.
+ *
+ * @see #clearBroadcastResponseStats(String, long)
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
@UserHandleAware
@NonNull
- public BroadcastResponseStats queryBroadcastResponseStats(
- @NonNull String packageName, @IntRange(from = 1) long id) {
+ public List<BroadcastResponseStats> queryBroadcastResponseStats(
+ @Nullable String packageName, @IntRange(from = 0) long id) {
try {
return mService.queryBroadcastResponseStats(packageName, id,
- mContext.getOpPackageName(), mContext.getUserId());
+ mContext.getOpPackageName(), mContext.getUserId()).getList();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1428,12 +1445,15 @@
/**
* Clears the broadcast response stats corresponding to {@code packageName} and {@code id}.
*
- * When a caller uses this API, stats related to the events occurring till that point will be
- * cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will
+ * <p> When a caller uses this API, stats related to the events occurring till that point will
+ * be cleared and subsequent calls to {@link #queryBroadcastResponseStats(String, long)} will
* return stats related to events occurring after this.
*
- * @param packageName The name of the package that the caller wants to clear the data for.
- * @param id The ID corresponding to the broadcasts that the caller wants to clear the data for.
+ * @param packageName The name of the package that the caller wants to clear the data for or
+ * {@code null} to indicate that data corresponding to all packages should
+ * be cleared.
+ * @param id The ID corresponding to the broadcasts that the caller wants to clear the data
+ * for, or {code 0} to indicate that data corresponding to all IDs should be deleted.
* This is the ID the caller specifies when requesting a broadcast response event
* to be recorded using
* {@link BroadcastOptions#recordResponseEventWhileInBackground(long)}.
@@ -1444,8 +1464,8 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
@UserHandleAware
- public void clearBroadcastResponseStats(@NonNull String packageName,
- @IntRange(from = 1) long id) {
+ public void clearBroadcastResponseStats(@Nullable String packageName,
+ @IntRange(from = 0) long id) {
try {
mService.clearBroadcastResponseStats(packageName, id,
mContext.getOpPackageName(), mContext.getUserId());
@@ -1453,4 +1473,19 @@
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Clears the broadcast events that were sent by the caller uid.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
+ @UserHandleAware
+ public void clearBroadcastEvents() {
+ try {
+ mService.clearBroadcastEvents(mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java
index 4e00da1..47bec61 100644
--- a/core/java/android/attention/AttentionManagerInternal.java
+++ b/core/java/android/attention/AttentionManagerInternal.java
@@ -49,21 +49,19 @@
/**
* Requests the continuous updates of proximity signal via the provided callback,
* until the given callback is unregistered. Currently, AttentionManagerService only
- * anticipates one client and updates one client at a time. If a new client wants to
- * onboard to receiving Proximity updates, please make a feature request to make proximity
- * feature multi-client before depending on this feature.
+ * anticipates one client and updates one client at a time.
*
* @param callback a callback that receives the proximity updates
* @return {@code true} if the registration should succeed.
*/
- public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback);
+ public abstract boolean onStartProximityUpdates(ProximityUpdateCallbackInternal callback);
/**
* Requests to stop providing continuous updates until the callback is registered.
*
* @param callback a callback that was used in {@link #onStartProximityUpdates}
*/
- public abstract void onStopProximityUpdates(ProximityCallbackInternal callback);
+ public abstract void onStopProximityUpdates(ProximityUpdateCallbackInternal callback);
/** Internal interface for attention callback. */
public abstract static class AttentionCallbackInternal {
@@ -85,7 +83,7 @@
}
/** Internal interface for proximity callback. */
- public abstract static class ProximityCallbackInternal {
+ public abstract static class ProximityUpdateCallbackInternal {
/**
* @param distance the estimated distance of the user (in meter)
* The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d50a6ba..99ce147 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -148,6 +148,8 @@
}
}
};
+ @Nullable
+ private VirtualAudioDevice mVirtualAudioDevice;
private VirtualDevice(
IVirtualDeviceManager service,
@@ -255,8 +257,8 @@
}
/**
- * Closes the virtual device, stopping and tearing down any virtual displays,
- * audio policies, and event injection that's currently in progress.
+ * Closes the virtual device, stopping and tearing down any virtual displays, associated
+ * virtual audio device, and event injection that's currently in progress.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
@@ -265,6 +267,10 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ if (mVirtualAudioDevice != null) {
+ mVirtualAudioDevice.close();
+ mVirtualAudioDevice = null;
+ }
}
/**
@@ -351,8 +357,10 @@
* Creates a VirtualAudioDevice, capable of recording audio emanating from this device,
* or injecting audio from another device.
*
- * <p>Note: This object does not support capturing privileged playback, such as voice call
- * audio.
+ * <p>Note: One {@link VirtualDevice} can only create one {@link VirtualAudioDevice}, so
+ * calling this method multiple times will return the same instance. When
+ * {@link VirtualDevice#close()} is called, the associated {@link VirtualAudioDevice} will
+ * also be closed automatically.
*
* @param display The target virtual display to capture from and inject into.
* @param executor The {@link Executor} object for the thread on which to execute
@@ -368,7 +376,11 @@
@NonNull VirtualDisplay display,
@Nullable Executor executor,
@Nullable AudioConfigurationChangeCallback callback) {
- return new VirtualAudioDevice(mContext, mVirtualDevice, display, executor, callback);
+ if (mVirtualAudioDevice == null) {
+ mVirtualAudioDevice = new VirtualAudioDevice(
+ mContext, mVirtualDevice, display, executor, callback);
+ }
+ return mVirtualAudioDevice;
}
/**
diff --git a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
index 5c246d3..c816da7 100644
--- a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
+++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java
@@ -60,7 +60,6 @@
/** Registers user restrictions change. */
void register(@NonNull UserRestrictionsCallback callback) {
mUserRestrictionsCallback = callback;
-
IntentFilter filter = new IntentFilter();
filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
mContext.registerReceiver(/* receiver= */ this, filter);
@@ -73,8 +72,10 @@
/** Unregisters user restrictions change. */
void unregister() {
- mUserRestrictionsCallback = null;
- mContext.unregisterReceiver(/* receiver= */ this);
+ if (mUserRestrictionsCallback != null) {
+ mUserRestrictionsCallback = null;
+ mContext.unregisterReceiver(/* receiver= */ this);
+ }
}
@Override
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
index 38e37ec..3f7299f 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.hardware.display.VirtualDisplay;
import android.media.AudioFormat;
+import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.os.RemoteException;
@@ -96,7 +97,7 @@
if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) {
throw new IllegalStateException("Cannot start an audio injection while a session is "
- + "ongoing. Call close() on this device first to end the previous injection.");
+ + "ongoing. Call close() on this device first to end the previous session.");
}
if (mOngoingSession == null) {
mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor);
@@ -114,6 +115,9 @@
/**
* Begins recording audio emanating from this device.
*
+ * <p>Note: This method does not support capturing privileged playback, which means the
+ * application can opt out of capturing by {@link AudioManager#setAllowedCapturePolicy(int)}.
+ *
* @return An {@link AudioCapture} containing the recorded audio.
*/
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -150,6 +154,7 @@
return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null;
}
+ /** Stops audio capture and injection then releases all the resources */
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@Override
public void close() {
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioSession.java b/core/java/android/companion/virtual/audio/VirtualAudioSession.java
index bc71bd6..c6a1045 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioSession.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java
@@ -120,7 +120,7 @@
@NonNull
public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) {
Objects.requireNonNull(injectionFormat, "injectionFormat must not be null");
- mUserRestrictionsDetector.register(/* callback= */ this);
+
synchronized (mLock) {
if (mAudioInjection != null) {
throw new IllegalStateException(
@@ -130,6 +130,8 @@
mInjectionFormat = injectionFormat;
mAudioInjection = new AudioInjection();
mAudioInjection.play();
+
+ mUserRestrictionsDetector.register(/* callback= */ this);
mAudioInjection.setSilent(mUserRestrictionsDetector.isUnmuteMicrophoneDisallowed());
return mAudioInjection;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0fed733..8f82a0a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3525,7 +3525,7 @@
public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
/**
- * Connect to an application service, creating it if needed. This defines
+ * Connects to an application service, creating it if needed. This defines
* a dependency between your application and the service. The given
* <var>conn</var> will receive the service object when it is created and be
* told if it dies and restarts. The service will be considered required
@@ -3540,11 +3540,8 @@
* will be invoked instead of
* {@link ServiceConnection#onServiceConnected(ComponentName, IBinder) onServiceConnected()}.
*
- * <p>This method will throw {@link SecurityException} if the calling app does not
- * have permission to bind to the given service.
- *
- * <p class="note">Note: this method <em>cannot be called from a
- * {@link BroadcastReceiver} component</em>. A pattern you can use to
+ * <p class="note"><b>Note:</b> This method <em>cannot</em> be called from a
+ * {@link BroadcastReceiver} component. A pattern you can use to
* communicate from a BroadcastReceiver to a Service is to call
* {@link #startService} with the arguments containing the command to be
* sent, with the service calling its
@@ -3559,33 +3556,34 @@
* specify an explicit component name.
* @param conn Receives information as the service is started and stopped.
* This must be a valid ServiceConnection object; it must not be null.
- * @param flags Operation options for the binding. May be 0,
- * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
- * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
- * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
- * {@link #BIND_IMPORTANT}, {@link #BIND_ADJUST_WITH_ACTIVITY},
- * {@link #BIND_NOT_PERCEPTIBLE}, or {@link #BIND_INCLUDE_CAPABILITIES}.
- * @return {@code true} if the system is in the process of bringing up a
+ * @param flags Operation options for the binding. Can be:
+ * <ul>
+ * <li>0
+ * <li>{@link #BIND_AUTO_CREATE}
+ * <li>{@link #BIND_DEBUG_UNBIND}
+ * <li>{@link #BIND_NOT_FOREGROUND}
+ * <li>{@link #BIND_ABOVE_CLIENT}
+ * <li>{@link #BIND_ALLOW_OOM_MANAGEMENT}
+ * <li>{@link #BIND_WAIVE_PRIORITY}
+ * <li>{@link #BIND_IMPORTANT}
+ * <li>{@link #BIND_ADJUST_WITH_ACTIVITY}
+ * <li>{@link #BIND_NOT_PERCEPTIBLE}
+ * <li>{@link #BIND_INCLUDE_CAPABILITIES}
+ * </ul>
+ *
+ * @return {@code true} if the system is in the process of bringing up a
* service that your client has permission to bind to; {@code false}
* if the system couldn't find the service or if your client doesn't
* have permission to bind to it. You should call {@link #unbindService}
* to release the connection even if this method returned {@code false}.
*
- * @throws SecurityException If the caller does not have permission to access the service
- * or the service can not be found.
+ * @throws SecurityException If the caller does not have permission to
+ * access the service or the service cannot be found. Call
+ * {@link #unbindService} to release the connection when this exception
+ * is thrown.
*
* @see #unbindService
* @see #startService
- * @see #BIND_AUTO_CREATE
- * @see #BIND_DEBUG_UNBIND
- * @see #BIND_NOT_FOREGROUND
- * @see #BIND_ABOVE_CLIENT
- * @see #BIND_ALLOW_OOM_MANAGEMENT
- * @see #BIND_WAIVE_PRIORITY
- * @see #BIND_IMPORTANT
- * @see #BIND_ADJUST_WITH_ACTIVITY
- * @see #BIND_NOT_PERCEPTIBLE
- * @see #BIND_INCLUDE_CAPABILITIES
*/
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
diff --git a/core/java/android/content/ServiceConnection.java b/core/java/android/content/ServiceConnection.java
index 21398f6..660a7f0 100644
--- a/core/java/android/content/ServiceConnection.java
+++ b/core/java/android/content/ServiceConnection.java
@@ -63,8 +63,12 @@
* happen, for example, if the application hosting the service it is bound to
* has been updated.
*
- * @param name The concrete component name of the service whose
- * connection is dead.
+ * <p class="note"><b>Note:</b> The app that requested the binding must call
+ * {@link Context#unbindService(ServiceConnection)} to release the tracking
+ * resources associated with this ServiceConnection even if this callback was
+ * invoked following {@link Context#bindService Context.bindService() bindService()}.
+ *
+ * @param name The concrete component name of the service whose connection is dead.
*/
default void onBindingDied(ComponentName name) {
}
@@ -72,10 +76,10 @@
/**
* Called when the service being bound has returned {@code null} from its
* {@link android.app.Service#onBind(Intent) onBind()} method. This indicates
- * that the attempting service binding represented by this ServiceConnection
+ * that the attempted service binding represented by this ServiceConnection
* will never become usable.
*
- * <p class="note">The app which requested the binding must still call
+ * <p class="note"><b>Note:</b> The app that requested the binding must still call
* {@link Context#unbindService(ServiceConnection)} to release the tracking
* resources associated with this ServiceConnection even if this callback was
* invoked following {@link Context#bindService Context.bindService() bindService()}.
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 45d4c09..6113932 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -17,6 +17,7 @@
package android.hardware;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
/**
@@ -664,16 +665,20 @@
* The first three elements provide the transform from the (arbitrary, possibly slowly drifting)
* reference frame to the head frame. The magnitude of this vector is in range [0, π]
* radians, while the value of individual axes is in range [-π, π]. The next three
- * elements provide the estimated rotational velocity of the user's head relative to itself, in
- * radians per second.
+ * elements optionally provide the estimated rotational velocity of the user's head relative to
+ * itself, in radians per second. If a given sensor does not support determining velocity, these
+ * elements are set to 0.
*
* <ul>
* <li> values[0] : X component of Euler vector representing rotation</li>
* <li> values[1] : Y component of Euler vector representing rotation</li>
* <li> values[2] : Z component of Euler vector representing rotation</li>
- * <li> values[3] : X component of Euler vector representing angular velocity</li>
- * <li> values[4] : Y component of Euler vector representing angular velocity</li>
- * <li> values[5] : Z component of Euler vector representing angular velocity</li>
+ * <li> values[3] : X component of Euler vector representing angular velocity (if
+ * supported, otherwise 0)</li>
+ * <li> values[4] : Y component of Euler vector representing angular velocity (if
+ * supported, otherwise 0)</li>
+ * <li> values[5] : Z component of Euler vector representing angular velocity (if
+ * supported, otherwise 0)</li>
* </ul>
*
* <h4>{@link android.hardware.Sensor#TYPE_ACCELEROMETER_LIMITED_AXES
@@ -820,6 +825,21 @@
*/
public long timestamp;
+ /**
+ * Set to true when this is the first sensor event after a discontinuity.
+ *
+ * The exact meaning of discontinuity depends on the sensor type. For
+ * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}, this means that
+ * the reference frame has suddenly and significantly changed, for example if the head tracking
+ * device was removed then put back.
+ *
+ * Note that this concept is either not relevant to or not supported by most sensor types,
+ * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER} being the notable
+ * exception.
+ */
+ @SuppressLint("MutableBareField")
+ public boolean firstEventAfterDiscontinuity;
+
@UnsupportedAppUsage
SensorEvent(int valueSize) {
values = new float[valueSize];
diff --git a/core/java/android/hardware/SensorEventCallback.java b/core/java/android/hardware/SensorEventCallback.java
index 7b0092d..bac212a 100644
--- a/core/java/android/hardware/SensorEventCallback.java
+++ b/core/java/android/hardware/SensorEventCallback.java
@@ -16,8 +16,6 @@
package android.hardware;
-import android.annotation.NonNull;
-
/**
* Used for receiving sensor additional information frames.
*/
@@ -54,21 +52,4 @@
* reported from sensor hardware.
*/
public void onSensorAdditionalInfo(SensorAdditionalInfo info) {}
-
- /**
- * Called when the next {@link android.hardware.SensorEvent SensorEvent} to be delivered via the
- * {@link #onSensorChanged(SensorEvent) onSensorChanged} method represents the first event after
- * a discontinuity.
- *
- * The exact meaning of discontinuity depends on the sensor type. For {@link
- * android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}, this means that the
- * reference frame has suddenly and significantly changed.
- *
- * Note that this concept is either not relevant to or not supported by most sensor types,
- * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER} being the notable
- * exception.
- *
- * @param sensor The {@link android.hardware.Sensor Sensor} which experienced the discontinuity.
- */
- public void onSensorDiscontinuity(@NonNull Sensor sensor) {}
}
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 4399af9..a3cc01c 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -158,36 +158,30 @@
}
+
+ /**
+ * Constant for software toggle.
+ */
+ public static final int TOGGLE_TYPE_SOFTWARE =
+ SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
+
+ /**
+ * Constant for hardware toggle.
+ */
+ public static final int TOGGLE_TYPE_HARDWARE =
+ SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
+
/**
* Types of toggles which can exist for sensor privacy
+ *
* @hide
*/
- public static class ToggleTypes {
- private ToggleTypes() {}
-
- /**
- * Constant for software toggle.
- */
- public static final int SOFTWARE = SensorPrivacyIndividualEnabledSensorProto.SOFTWARE;
-
- /**
- * Constant for hardware toggle.
- */
- public static final int HARDWARE = SensorPrivacyIndividualEnabledSensorProto.HARDWARE;
-
- /**
- * Types of toggles which can exist for sensor privacy
- *
- * @hide
- */
- @IntDef(value = {
- SOFTWARE,
- HARDWARE
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ToggleType {}
-
- }
+ @IntDef(value = {
+ TOGGLE_TYPE_SOFTWARE,
+ TOGGLE_TYPE_HARDWARE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ToggleType {}
/**
* Types of state which can exist for the sensor privacy toggle
@@ -232,20 +226,23 @@
/**
* Callback invoked when the sensor privacy state changes.
*
+ * @param params Parameters describing the new state
+ */
+ default void onSensorPrivacyChanged(@NonNull SensorPrivacyChangedParams params) {
+ onSensorPrivacyChanged(params.mSensor, params.mEnabled);
+ }
+
+ /**
+ * Callback invoked when the sensor privacy state changes.
+ *
* @param sensor the sensor whose state is changing
* @param enabled true if sensor privacy is enabled, false otherwise.
+ *
+ * @deprecated Please use
+ * {@link #onSensorPrivacyChanged(SensorPrivacyChangedParams)}
*/
+ @Deprecated
void onSensorPrivacyChanged(int sensor, boolean enabled);
- }
-
- /**
- * A class implementing this interface can register with the {@link
- * android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
- * state changes.
- *
- * @hide
- */
- public interface OnToggleSensorPrivacyChangedListener {
/**
* A class containing information about what the sensor privacy state has changed to.
@@ -262,7 +259,7 @@
mEnabled = enabled;
}
- public @ToggleTypes.ToggleType int getToggleType() {
+ public @ToggleType int getToggleType() {
return mToggleType;
}
@@ -274,13 +271,6 @@
return mEnabled;
}
}
-
- /**
- * Callback invoked when the sensor privacy state changes.
- *
- * @param params Parameters describing the new state
- */
- void onSensorPrivacyChanged(@NonNull SensorPrivacyChangedParams params);
}
private static final Object sInstanceLock = new Object();
@@ -305,15 +295,15 @@
/** Registered listeners */
@GuardedBy("mLock")
@NonNull
- private final ArrayMap<OnToggleSensorPrivacyChangedListener, Executor> mToggleListeners =
+ private final ArrayMap<OnSensorPrivacyChangedListener, Executor> mToggleListeners =
new ArrayMap<>();
/** Listeners registered using the deprecated APIs and which
- * OnToggleSensorPrivacyChangedListener they're using. */
+ * OnSensorPrivacyChangedListener they're using. */
@GuardedBy("mLock")
@NonNull
private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
- OnToggleSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
+ OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
/** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
* listeners */
@@ -323,9 +313,9 @@
public void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled) {
synchronized (mLock) {
for (int i = 0; i < mToggleListeners.size(); i++) {
- OnToggleSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
+ OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
mToggleListeners.valueAt(i).execute(() -> listener
- .onSensorPrivacyChanged(new OnToggleSensorPrivacyChangedListener
+ .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener
.SensorPrivacyChangedParams(toggleType, sensor, enabled)));
}
}
@@ -367,12 +357,24 @@
}
/**
+ * Returns the single instance of the SensorPrivacyManager.
+ *
+ * @hide
+ */
+ public static SensorPrivacyManager getInstance(Context context, ISensorPrivacyManager service) {
+ synchronized (sInstanceLock) {
+ sInstance = new SensorPrivacyManager(context, service);
+ return sInstance;
+ }
+ }
+
+ /**
* Checks if the given toggle is supported on this device
* @param sensor The sensor to check
* @return whether the toggle for the sensor is supported on this device.
*/
public boolean supportsSensorToggle(@Sensors.Sensor int sensor) {
- return supportsSensorToggle(ToggleTypes.SOFTWARE, sensor);
+ return supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor);
}
/**
@@ -380,10 +382,8 @@
* @param sensor The sensor to check
* @return whether the toggle for the sensor is supported on this device.
*
- * @hide
*/
- public boolean supportsSensorToggle(@ToggleTypes.ToggleType int toggleType,
- @Sensors.Sensor int sensor) {
+ public boolean supportsSensorToggle(@ToggleType int toggleType, @Sensors.Sensor int sensor) {
try {
Pair key = new Pair(toggleType, sensor);
synchronized (mLock) {
@@ -408,8 +408,6 @@
* @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
* privacy changes.
*
- * {@link #addSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@SystemApi
@@ -429,8 +427,6 @@
* @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
* privacy changes.
*
- * {@link #addSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@@ -449,19 +445,22 @@
* @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
* privacy changes.
*
- * {@link #addSensorPrivacyListener(Executor, OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor,
@NonNull OnSensorPrivacyChangedListener listener) {
-
Pair<Integer, OnSensorPrivacyChangedListener> pair = new Pair(sensor, listener);
- OnToggleSensorPrivacyChangedListener toggleListener = params -> {
- if (params.getSensor() == sensor) {
- listener.onSensorPrivacyChanged(params.getSensor(), params.isEnabled());
+ OnSensorPrivacyChangedListener toggleListener = new OnSensorPrivacyChangedListener() {
+ @Override
+ public void onSensorPrivacyChanged(SensorPrivacyChangedParams params) {
+ if (params.getSensor() == sensor) {
+ listener.onSensorPrivacyChanged(params.getSensor(), params.isEnabled());
+ }
+ }
+ @Override
+ public void onSensorPrivacyChanged(int sensor, boolean enabled) {
}
};
@@ -476,13 +475,14 @@
* Registers a new listener to receive notification when the state of sensor privacy
* changes.
*
- * @param listener the OnToggleSensorPrivacyChangedListener to be notified when the state of
+ * @param listener the OnSensorPrivacyChangedListener to be notified when the state of
* sensor privacy changes.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public void addSensorPrivacyListener(@NonNull OnToggleSensorPrivacyChangedListener listener) {
+ public void addSensorPrivacyListener(@NonNull OnSensorPrivacyChangedListener listener) {
addSensorPrivacyListener(mContext.getMainExecutor(), listener);
}
@@ -492,14 +492,15 @@
* changes.
*
* @param executor the executor to dispatch the callback on
- * @param listener the OnToggleSensorPrivacyChangedListener to be notified when the state of
+ * @param listener the OnSensorPrivacyChangedListener to be notified when the state of
* sensor privacy changes.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public void addSensorPrivacyListener(@NonNull Executor executor,
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
addSensorPrivacyListenerLocked(executor, listener);
}
@@ -507,7 +508,7 @@
@GuardedBy("mLock")
private void addSensorPrivacyListenerLocked(@NonNull Executor executor,
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
if (!mToggleListenerRegistered) {
try {
mService.addToggleSensorPrivacyListener(mIToggleListener);
@@ -529,10 +530,6 @@
* @param listener the OnSensorPrivacyChangedListener to be unregistered from notifications when
* sensor privacy changes.
*
- * {@link #removeSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)} with
- * {@link #addSensorPrivacyListener(OnToggleSensorPrivacyChangedListener)} or
- * {@link #addSensorPrivacyListener(Executor, OnToggleSensorPrivacyChangedListener)}
- *
* @hide
*/
@SystemApi
@@ -541,7 +538,7 @@
@NonNull OnSensorPrivacyChangedListener listener) {
Pair<Integer, OnSensorPrivacyChangedListener> pair = new Pair(sensor, listener);
synchronized (mLock) {
- OnToggleSensorPrivacyChangedListener onToggleSensorPrivacyChangedListener =
+ OnSensorPrivacyChangedListener onToggleSensorPrivacyChangedListener =
mLegacyToggleListeners.remove(pair);
if (onToggleSensorPrivacyChangedListener != null) {
removeSensorPrivacyListenerLocked(onToggleSensorPrivacyChangedListener);
@@ -553,14 +550,15 @@
* Unregisters the specified listener from receiving notifications when the state of any sensor
* privacy changes.
*
- * @param listener the {@link OnToggleSensorPrivacyChangedListener} to be unregistered from
+ * @param listener the {@link OnSensorPrivacyChangedListener} to be unregistered from
* notifications when sensor privacy changes.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public void removeSensorPrivacyListener(
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
removeSensorPrivacyListenerLocked(listener);
}
@@ -568,7 +566,7 @@
@GuardedBy("mLock")
private void removeSensorPrivacyListenerLocked(
- @NonNull OnToggleSensorPrivacyChangedListener listener) {
+ @NonNull OnSensorPrivacyChangedListener listener) {
mToggleListeners.remove(listener);
if (mToggleListeners.size() == 0) {
try {
@@ -586,12 +584,15 @@
*
* @return true if sensor privacy is currently enabled, false otherwise.
*
+ * @deprecated Prefer to use {@link #isSensorPrivacyEnabled(int, int)}
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
- return isSensorPrivacyEnabled(ToggleTypes.SOFTWARE, sensor);
+ return isSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor);
}
/**
@@ -601,8 +602,9 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public boolean isSensorPrivacyEnabled(@ToggleTypes.ToggleType int toggleType,
+ public boolean isSensorPrivacyEnabled(@ToggleType int toggleType,
@Sensors.Sensor int sensor) {
try {
return mService.isToggleSensorPrivacyEnabled(toggleType, sensor);
@@ -613,14 +615,16 @@
/**
* Returns whether sensor privacy is currently enabled for a specific sensor.
- * Combines the state of the SW + HW toggles and returns the actual privacy state.
+ * Combines the state of the SW + HW toggles and returns true if either the
+ * SOFTWARE or the HARDWARE toggles are enabled.
*
* @return true if sensor privacy is currently enabled, false otherwise.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public boolean isToggleSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
+ public boolean areAnySensorPrivacyTogglesEnabled(@Sensors.Sensor int sensor) {
try {
return mService.isCombinedToggleSensorPrivacyEnabled(sensor);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 32a5ee7..18d86d6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -881,13 +881,13 @@
mListener.onAccuracyChanged(t.sensor, t.accuracy);
}
- // call onSensorDiscontinuity() if the discontinuity counter changed
- if (t.sensor.getType() == Sensor.TYPE_HEAD_TRACKER
- && mListener instanceof SensorEventCallback) {
+ // Indicate if the discontinuity count changed
+ if (t.sensor.getType() == Sensor.TYPE_HEAD_TRACKER) {
final int lastCount = mSensorDiscontinuityCounts.get(handle);
final int curCount = Float.floatToIntBits(values[6]);
if (lastCount >= 0 && lastCount != curCount) {
- ((SensorEventCallback) mListener).onSensorDiscontinuity(t.sensor);
+ mSensorDiscontinuityCounts.put(handle, curCount);
+ t.firstEventAfterDiscontinuity = true;
}
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index b6078e0..c3e3180 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -346,7 +346,7 @@
*/
@AnyThread
public static boolean canImeRenderGesturalNavButtons() {
- return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true);
+ return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false);
}
/**
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index b9252d6..7379443 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -104,6 +104,8 @@
public final class SystemClock {
private static final String TAG = "SystemClock";
+ private static volatile IAlarmManager sIAlarmManager;
+
/**
* This class is uninstantiable.
*/
@@ -151,8 +153,7 @@
* @return if the clock was successfully set to the specified time.
*/
public static boolean setCurrentTimeMillis(long millis) {
- final IAlarmManager mgr = IAlarmManager.Stub
- .asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+ final IAlarmManager mgr = getIAlarmManager();
if (mgr == null) {
Slog.e(TAG, "Unable to set RTC: mgr == null");
return false;
@@ -280,8 +281,7 @@
* @hide
*/
public static long currentNetworkTimeMillis() {
- final IAlarmManager mgr = IAlarmManager.Stub
- .asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+ final IAlarmManager mgr = getIAlarmManager();
if (mgr != null) {
try {
return mgr.currentNetworkTimeMillis();
@@ -296,6 +296,14 @@
}
}
+ private static IAlarmManager getIAlarmManager() {
+ if (sIAlarmManager == null) {
+ sIAlarmManager = IAlarmManager.Stub
+ .asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+ }
+ return sIAlarmManager;
+ }
+
/**
* Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC,
* synchronized using a remote network source outside the device.
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 6b869f1..043e688 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -106,6 +106,9 @@
/** @hide */
public static final long TRACE_TAG_RRO = 1L << 26;
/** @hide */
+ public static final long TRACE_TAG_THERMAL = 1L << 27;
+ /** @hide */
+
public static final long TRACE_TAG_APEX_MANAGER = 1L << 18;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index d223a19..642c618 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -27,7 +27,7 @@
import java.util.Objects;
/**
- * A class to encapsulate a collection of attributes describing information about a vibration
+ * Encapsulates a collection of attributes describing information about a vibration.
*/
public final class VibrationAttributes implements Parcelable {
private static final String TAG = "VibrationAttributes";
@@ -174,7 +174,7 @@
FLAG_BYPASS_INTERRUPTION_POLICY | FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
/** Creates a new {@link VibrationAttributes} instance with given usage. */
- public static @NonNull VibrationAttributes createForUsage(int usage) {
+ public static @NonNull VibrationAttributes createForUsage(@Usage int usage) {
return new VibrationAttributes.Builder().setUsage(usage).build();
}
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 21c6487..237f6ed 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -48,7 +48,7 @@
/**
* A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
*
- * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ * <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
*/
public abstract class VibrationEffect implements Parcelable {
// Stevens' coefficient to scale the perceived vibration intensity.
@@ -110,7 +110,7 @@
/**
* A texture effect meant to replicate soft ticks.
*
- * Unlike normal effects, texture effects are meant to be called repeatedly, generally in
+ * <p>Unlike normal effects, texture effects are meant to be called repeatedly, generally in
* response to some motion, in order to replicate the feeling of some texture underneath the
* user's fingers.
*
@@ -175,7 +175,7 @@
/**
* Create a one shot vibration.
*
- * One shot vibrations will vibrate constantly for the specified period of time at the
+ * <p>One shot vibrations will vibrate constantly for the specified period of time at the
* specified amplitude, and then stop.
*
* @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
@@ -269,13 +269,13 @@
/**
* Create a predefined vibration effect.
*
- * Predefined effects are a set of common vibration effects that should be identical, regardless
- * of the app they come from, in order to provide a cohesive experience for users across
- * the entire device. They also may be custom tailored to the device hardware in order to
+ * <p>Predefined effects are a set of common vibration effects that should be identical,
+ * regardless of the app they come from, in order to provide a cohesive experience for users
+ * across the entire device. They also may be custom tailored to the device hardware in order to
* provide a better experience than you could otherwise build using the generic building
* blocks.
*
- * This will fallback to a generic pattern if one exists and there does not exist a
+ * <p>This will fallback to a generic pattern if one exists and there does not exist a
* hardware-specific implementation of the effect.
*
* @param effectId The ID of the effect to perform:
@@ -291,13 +291,13 @@
/**
* Get a predefined vibration effect.
*
- * Predefined effects are a set of common vibration effects that should be identical, regardless
- * of the app they come from, in order to provide a cohesive experience for users across
- * the entire device. They also may be custom tailored to the device hardware in order to
+ * <p>Predefined effects are a set of common vibration effects that should be identical,
+ * regardless of the app they come from, in order to provide a cohesive experience for users
+ * across the entire device. They also may be custom tailored to the device hardware in order to
* provide a better experience than you could otherwise build using the generic building
* blocks.
*
- * This will fallback to a generic pattern if one exists and there does not exist a
+ * <p>This will fallback to a generic pattern if one exists and there does not exist a
* hardware-specific implementation of the effect.
*
* @param effectId The ID of the effect to perform:
@@ -314,16 +314,16 @@
/**
* Get a predefined vibration effect.
*
- * Predefined effects are a set of common vibration effects that should be identical, regardless
- * of the app they come from, in order to provide a cohesive experience for users across
- * the entire device. They also may be custom tailored to the device hardware in order to
+ * <p>Predefined effects are a set of common vibration effects that should be identical,
+ * regardless of the app they come from, in order to provide a cohesive experience for users
+ * across the entire device. They also may be custom tailored to the device hardware in order to
* provide a better experience than you could otherwise build using the generic building
* blocks.
*
- * Some effects you may only want to play if there's a hardware specific implementation because
- * they may, for example, be too disruptive to the user without tuning. The {@code fallback}
- * parameter allows you to decide whether you want to fallback to the generic implementation or
- * only play if there's a tuned, hardware specific one available.
+ * <p>Some effects you may only want to play if there's a hardware specific implementation
+ * because they may, for example, be too disruptive to the user without tuning. The
+ * {@code fallback} parameter allows you to decide whether you want to fallback to the generic
+ * implementation or only play if there's a tuned, hardware specific one available.
*
* @param effectId The ID of the effect to perform:
* {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
@@ -344,9 +344,9 @@
/**
* Get a predefined vibration effect associated with a given URI.
*
- * Predefined effects are a set of common vibration effects that should be identical, regardless
- * of the app they come from, in order to provide a cohesive experience for users across
- * the entire device. They also may be custom tailored to the device hardware in order to
+ * <p>Predefined effects are a set of common vibration effects that should be identical,
+ * regardless of the app they come from, in order to provide a cohesive experience for users
+ * across the entire device. They also may be custom tailored to the device hardware in order to
* provide a better experience than you could otherwise build using the generic building
* blocks.
*
@@ -474,7 +474,7 @@
/**
* Gets the estimated duration of the vibration in milliseconds.
*
- * For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
+ * <p>For effects without a defined end (e.g. a Waveform with a non-negative repeat index), this
* returns Long.MAX_VALUE. For effects with an unknown duration (e.g. Prebaked effects where
* the length is device and potentially run-time dependent), this returns -1.
*
@@ -817,28 +817,26 @@
* effect that grows in intensity and then dies off, with a longer rising portion for emphasis
* and an extra tick 100ms after:
*
- * <code>
- * VibrationEffect effect = VibrationEffect.startComposition()
+ * <pre>
+ * {@code VibrationEffect effect = VibrationEffect.startComposition()
* .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f)
* .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f)
* .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100)
- * .compose();
- * </code>
+ * .compose();}</pre>
*
* <p>Composition elements can also be {@link VibrationEffect} instances, including other
* compositions, and off durations, which are periods of time when the vibrator will be
* turned off. Here is an example of a composition that "warms up" with a light tap,
* a stronger double tap, then repeats a vibration pattern indefinitely:
*
- * <code>
- * VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+ * <pre>
+ * {@code VibrationEffect repeatingEffect = VibrationEffect.startComposition()
* .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)
* .addOffDuration(Duration.ofMillis(10))
* .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK))
* .addOffDuration(Duration.ofMillis(50))
* .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex))
- * .compose();
- * </code>
+ * .compose();}</pre>
*
* <p>When choosing to play a composed effect, you should check that individual components are
* supported by the device by using the appropriate vibrator method:
@@ -932,7 +930,7 @@
/**
* Adds a time duration to the current composition, during which the vibrator will be
- * turned off
+ * turned off.
*
* @param duration The length of time the vibrator should be off. Value must be non-negative
* and will be truncated to milliseconds.
@@ -1004,7 +1002,7 @@
/**
* Add a haptic primitive to the end of the current composition.
*
- * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
+ * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a
* default scale applied.
*
* @param primitiveId The primitive to add
@@ -1021,7 +1019,7 @@
/**
* Add a haptic primitive to the end of the current composition.
*
- * Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
+ * <p>Similar to {@link #addPrimitive(int, float, int)}, but with no delay.
*
* @param primitiveId The primitive to add
* @param scale The scale to apply to the intensity of the primitive.
@@ -1081,9 +1079,9 @@
/**
* Compose all of the added primitives together into a single {@link VibrationEffect}.
*
- * The {@link Composition} object is still valid after this call, so you can continue adding
- * more primitives to it and generating more {@link VibrationEffect}s by calling this method
- * again.
+ * <p>The {@link Composition} object is still valid after this call, so you can continue
+ * adding more primitives to it and generating more {@link VibrationEffect}s by calling this
+ * method again.
*
* @return The {@link VibrationEffect} resulting from the composition of the primitives.
*/
@@ -1099,7 +1097,7 @@
}
/**
- * Convert the primitive ID to a human readable string for debugging
+ * Convert the primitive ID to a human readable string for debugging.
* @param id The ID to convert
* @return The ID in a human readable format.
* @hide
@@ -1139,16 +1137,15 @@
* <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms
* starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms:
*
- * <code>
- * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
+ * <pre>
+ * {@code import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
* import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
*
* VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
* .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
* .addSustain(Duration.ofMillis(200))
* .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
- * .build();
- * </code>
+ * .build();}</pre>
*
* <p>The initial state of the waveform can be set via
* {@link VibrationEffect#startWaveform(VibrationParameter)} or
@@ -1169,8 +1166,8 @@
* a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a
* repeated beating effect with a rise that stretches out and a sharp finish.
*
- * <code>
- * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f))
+ * <pre>
+ * {@code VibrationEffect patternToRepeat = VibrationEffect.startWaveform(targetAmplitude(0.2f))
* .addSustain(Duration.ofMillis(10))
* .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f))
* .addSustain(Duration.ofMillis(30))
@@ -1182,16 +1179,15 @@
* VibrationEffect effect = VibrationEffect.startComposition()
* .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
* .addOffDuration(Duration.ofMillis(20))
- * .repeatEffectIndefinitely(patternToBeRepeated)
- * .compose();
- * </code>
+ * .repeatEffectIndefinitely(patternToRepeat)
+ * .compose();}</pre>
*
* <p>The amplitude step waveforms that can be created via
* {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with
* {@link WaveformBuilder} by adding zero duration transitions:
*
- * <code>
- * // These two effects are the same
+ * <pre>
+ * {@code // These two effects are the same
* VibrationEffect waveform = VibrationEffect.createWaveform(
* new long[] { 10, 20, 30 }, // timings in milliseconds
* new int[] { 51, 102, 204 }, // amplitudes in [0,255]
@@ -1203,8 +1199,7 @@
* .addSustain(Duration.ofMillis(20))
* .addTransition(Duration.ZERO, targetAmplitude(0.8f))
* .addSustain(Duration.ofMillis(30))
- * .build();
- * </code>
+ * .build();}</pre>
*
* @see VibrationEffect#startWaveform
*/
@@ -1307,7 +1302,7 @@
/**
* Build the waveform as a single {@link VibrationEffect}.
*
- * The {@link WaveformBuilder} object is still valid after this call, so you can
+ * <p>The {@link WaveformBuilder} object is still valid after this call, so you can
* continue adding more primitives to it and generating more {@link VibrationEffect}s by
* calling this method again.
*
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 78f1cb1..7f0d634 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -83,7 +83,7 @@
/**
* Vibration effect support: unknown
*
- * The hardware doesn't report it's supported effects, so we can't determine whether the
+ * <p>The hardware doesn't report its supported effects, so we can't determine whether the
* effect is supported or not.
*/
public static final int VIBRATION_EFFECT_SUPPORT_UNKNOWN = 0;
@@ -91,14 +91,14 @@
/**
* Vibration effect support: supported
*
- * This effect is supported by the underlying hardware.
+ * <p>This effect is supported by the underlying hardware.
*/
public static final int VIBRATION_EFFECT_SUPPORT_YES = 1;
/**
* Vibration effect support: unsupported
*
- * This effect is <b>not</b> natively supported by the underlying hardware, although
+ * <p>This effect is <b>not</b> natively supported by the underlying hardware, although
* the system may still play a fallback vibration.
*/
public static final int VIBRATION_EFFECT_SUPPORT_NO = 2;
@@ -317,7 +317,7 @@
/**
* Vibrate constantly for the specified period of time.
*
- * <p>The app should be in foreground for the vibration to happen.</p>
+ * <p>The app should be in the foreground for the vibration to happen.</p>
*
* @param milliseconds The number of milliseconds to vibrate.
* @deprecated Use {@link #vibrate(VibrationEffect)} instead.
@@ -331,7 +331,7 @@
/**
* Vibrate constantly for the specified period of time.
*
- * <p>The app should be in foreground for the vibration to happen. Background apps should
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
* specify a ringtone, notification or alarm usage in order to vibrate.</p>
*
* @param milliseconds The number of milliseconds to vibrate.
@@ -368,7 +368,7 @@
* to start the repeat, or -1 to disable repeating.
* </p>
*
- * <p>The app should be in foreground for the vibration to happen.</p>
+ * <p>The app should be in the foreground for the vibration to happen.</p>
*
* @param pattern an array of longs of times for which to turn the vibrator on or off.
* @param repeat the index into pattern at which to repeat, or -1 if
@@ -395,7 +395,7 @@
* to start the repeat, or -1 to disable repeating.
* </p>
*
- * <p>The app should be in foreground for the vibration to happen. Background apps should
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
* specify a ringtone, notification or alarm usage in order to vibrate.</p>
*
* @param pattern an array of longs of times for which to turn the vibrator on or off.
@@ -428,7 +428,7 @@
/**
* Vibrate with a given effect.
*
- * <p>The app should be in foreground for the vibration to happen.</p>
+ * <p>The app should be in the foreground for the vibration to happen.</p>
*
* @param vibe {@link VibrationEffect} describing the vibration to be performed.
*/
@@ -440,7 +440,7 @@
/**
* Vibrate with a given effect.
*
- * <p>The app should be in foreground for the vibration to happen. Background apps should
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
* specify a ringtone, notification or alarm usage in order to vibrate.</p>
*
* @param vibe {@link VibrationEffect} describing the vibration to be performed.
@@ -461,7 +461,7 @@
/**
* Vibrate with a given effect.
*
- * <p>The app should be in foreground for the vibration to happen. Background apps should
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
* specify a ringtone, notification or alarm usage in order to vibrate.</p>
*
* @param vibe {@link VibrationEffect} describing the vibration to be performed.
@@ -477,7 +477,7 @@
/**
* Like {@link #vibrate(VibrationEffect, VibrationAttributes)}, but allows the
- * caller to specify the vibration is owned by someone else and set reason for vibration.
+ * caller to specify the vibration is owned by someone else and set a reason for vibration.
*
* @hide
*/
@@ -519,7 +519,7 @@
}
/**
- * Query whether the vibrator supports all of the given effects.
+ * Query whether the vibrator supports all the given effects.
*
* <p>If an effect is not supported, the system may still automatically fall back to a simpler
* vibration instead, which is not optimised for the specific device, however vibration isn't
@@ -533,7 +533,7 @@
* vibration.
*
* <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know
- * whether all of the effects are supported. It may support any or all of the queried effects,
+ * whether all the effects are supported. It may support any or all of the queried effects,
* but there's no way to programmatically know whether a {@link #vibrate} call will successfully
* cause a vibration. It's guaranteed, however, that none of the queried effects are
* definitively unsupported by the hardware.
@@ -541,7 +541,7 @@
* <p>Use {@link #areEffectsSupported(int...)} to get individual results for each effect.
*
* @param effectIds Which effects to query for.
- * @return Whether all of the effects are natively supported by the device.
+ * @return Whether all the effects are natively supported by the device.
*/
@VibrationEffectSupport
public final int areAllEffectsSupported(
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 00ce14f..71ec096 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -35,7 +35,8 @@
/**
* A VibratorInfo describes the capabilities of a {@link Vibrator}.
*
- * This description includes its capabilities, list of supported effects and composition primitives.
+ * <p>This description includes its capabilities, list of supported effects and composition
+ * primitives.
*
* @hide
*/
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index c82a516..f506ef8 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -25,7 +25,7 @@
import android.util.Log;
/**
- * Class that provides access to all vibrators from the device, as well as the ability to run them
+ * Provides access to all vibrators from the device, as well as the ability to run them
* in a synchronized fashion.
* <p>
* If your process exits, any vibration you started will stop.
diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 02db274..29b4570 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -19,10 +19,54 @@
/**
* @hide
*/
-interface ILogcatManagerService {
+oneway interface ILogcatManagerService {
+ /**
+ * The function is called by logd to notify LogcatManagerService
+ * that a client makes privileged log data access request.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void startThread(in int uid, in int gid, in int pid, in int fd);
+
+
+ /**
+ * The function is called by logd to notify LogcatManagerService
+ * that a client finished the privileged log data access.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void finishThread(in int uid, in int gid, in int pid, in int fd);
+
+
+ /**
+ * The function is called by UX component to notify
+ * LogcatManagerService that the user approved
+ * the privileged log data access.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void approve(in int uid, in int gid, in int pid, in int fd);
+
+
+ /**
+ * The function is called by UX component to notify
+ * LogcatManagerService that the user declined
+ * the privileged log data access.
+ *
+ * @param uid The UID of client who makes the request.
+ * @param gid The GID of client who makes the request.
+ * @param pid The PID of client who makes the request.
+ * @param fd The FD (Socket) of client who makes the request.
+ */
void decline(in int uid, in int gid, in int pid, in int fd);
}
diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
index 23b45ae..0f2aa15 100644
--- a/core/java/android/os/vibrator/VibratorFrequencyProfile.java
+++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
@@ -61,8 +61,7 @@
* <p>The returned list will not be empty, and will have entries representing frequencies from
* {@link #getMinFrequency()} to {@link #getMaxFrequency()}, inclusive.
*
- * @return Array of maximum relative amplitude measurements, each value is between 0 and 1,
- * inclusive.
+ * @return Array of maximum relative amplitude measurements.
*/
@NonNull
@FloatRange(from = 0, to = 1)
diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java
deleted file mode 100644
index 440d6f2..0000000
--- a/core/java/android/permission/PermGroupUsage.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.permission;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-
-/**
- * Represents the usage of a permission group by an app. Supports package name, user, permission
- * group, whether or not the access is running or recent, whether the access is tied to a phone
- * call, and an optional special attribution.
- *
- * @hide
- */
-@TestApi
-public final class PermGroupUsage {
-
- private final String mPackageName;
- private final int mUid;
- private final long mLastAccess;
- private final String mPermGroupName;
- private final boolean mIsActive;
- private final boolean mIsPhoneCall;
- private final CharSequence mAttribution;
-
- /**
- *
- * @param packageName The package name of the using app
- * @param uid The uid of the using app
- * @param permGroupName The name of the permission group being used
- * @param lastAccess The time of last access
- * @param isActive Whether this is active
- * @param isPhoneCall Whether this is a usage by the phone
- * @param attribution An optional string attribution to show
- * @hide
- */
- @TestApi
- public PermGroupUsage(@NonNull String packageName, int uid,
- @NonNull String permGroupName, long lastAccess, boolean isActive, boolean isPhoneCall,
- @Nullable CharSequence attribution) {
- this.mPackageName = packageName;
- this.mUid = uid;
- this.mPermGroupName = permGroupName;
- this.mLastAccess = lastAccess;
- this.mIsActive = isActive;
- this.mIsPhoneCall = isPhoneCall;
- this.mAttribution = attribution;
- }
-
- /**
- * @hide
- */
- @TestApi
- public @NonNull String getPackageName() {
- return mPackageName;
- }
-
- /**
- * @hide
- */
- @TestApi
- public int getUid() {
- return mUid;
- }
-
- /**
- * @hide
- */
- @TestApi
- public @NonNull String getPermGroupName() {
- return mPermGroupName;
- }
-
- /**
- * @hide
- */
- @TestApi
- public long getLastAccess() {
- return mLastAccess;
- }
-
- /**
- * @hide
- */
- @TestApi
- public boolean isActive() {
- return mIsActive;
- }
-
- /**
- * @hide
- */
- @TestApi
- public boolean isPhoneCall() {
- return mIsPhoneCall;
- }
-
- /**
- * @hide
- */
- @TestApi
- public @Nullable CharSequence getAttribution() {
- return mAttribution;
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))
- + " packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: "
- + mPermGroupName + ", lastAccess: " + mLastAccess + ", isActive: " + mIsActive
- + ", attribution: " + mAttribution;
- }
-}
diff --git a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl b/core/java/android/permission/PermissionGroupUsage.aidl
similarity index 81%
rename from apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl
rename to core/java/android/permission/PermissionGroupUsage.aidl
index 92d673f..f18698a 100644
--- a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl
+++ b/core/java/android/permission/PermissionGroupUsage.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright 2020, The Android Open Source Project
+/**
+ * Copyright (c) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media;
+package android.permission;
-parcelable MediaParceledListSlice<T>;
+parcelable PermissionGroupUsage;
\ No newline at end of file
diff --git a/core/java/android/permission/PermissionGroupUsage.java b/core/java/android/permission/PermissionGroupUsage.java
new file mode 100644
index 0000000..49b7463
--- /dev/null
+++ b/core/java/android/permission/PermissionGroupUsage.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Represents the usage of a permission group by an app. Supports package name, user, permission
+ * group, whether or not the access is running or recent, whether the access is tied to a phone
+ * call, and an optional special attribution tag, label and proxy label.
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(
+ genHiddenConstructor = true,
+ genEqualsHashCode = true,
+ genToString = true
+)
+public final class PermissionGroupUsage implements Parcelable {
+
+ private final @NonNull String mPackageName;
+ private final int mUid;
+ private final long mLastAccessTimeMillis;
+ private final @NonNull String mPermissionGroupName;
+ private final boolean mActive;
+ private final boolean mPhoneCall;
+ private final @Nullable CharSequence mAttributionTag;
+ private final @Nullable CharSequence mAttributionLabel;
+ private final @Nullable CharSequence mProxyLabel;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/permission/PermissionGroupUsage.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new PermissionGroupUsage.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public PermissionGroupUsage(
+ @NonNull String packageName,
+ int uid,
+ long lastAccessTimeMillis,
+ @NonNull String permissionGroupName,
+ boolean active,
+ boolean phoneCall,
+ @Nullable CharSequence attributionTag,
+ @Nullable CharSequence attributionLabel,
+ @Nullable CharSequence proxyLabel) {
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUid = uid;
+ this.mLastAccessTimeMillis = lastAccessTimeMillis;
+ this.mPermissionGroupName = permissionGroupName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPermissionGroupName);
+ this.mActive = active;
+ this.mPhoneCall = phoneCall;
+ this.mAttributionTag = attributionTag;
+ this.mAttributionLabel = attributionLabel;
+ this.mProxyLabel = proxyLabel;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * @return Package name for the usage
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * @return UID for the usage
+ */
+ @DataClass.Generated.Member
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * @return Last access time in millis for the usage
+ */
+ @CurrentTimeMillisLong
+ @DataClass.Generated.Member
+ public long getLastAccessTimeMillis() {
+ return mLastAccessTimeMillis;
+ }
+
+ /**
+ * @return Permission group name for the usage
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPermissionGroupName() {
+ return mPermissionGroupName;
+ }
+
+ /**
+ * @return If usage is active
+ */
+ @DataClass.Generated.Member
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * @return If usage is a phone call
+ */
+ @DataClass.Generated.Member
+ public boolean isPhoneCall() {
+ return mPhoneCall;
+ }
+
+ /**
+ * @return Attribution tag associated with the usage
+ */
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ /**
+ * @return Attribution label associated with the usage
+ */
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getAttributionLabel() {
+ return mAttributionLabel;
+ }
+
+ /**
+ * @return Proxy label associated with the usage
+ */
+ @DataClass.Generated.Member
+ public @Nullable CharSequence getProxyLabel() {
+ return mProxyLabel;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "PermissionGroupUsage { " +
+ "packageName = " + mPackageName + ", " +
+ "uid = " + mUid + ", " +
+ "lastAccessTimeMillis = " + mLastAccessTimeMillis + ", " +
+ "permissionGroupName = " + mPermissionGroupName + ", " +
+ "active = " + mActive + ", " +
+ "phoneCall = " + mPhoneCall + ", " +
+ "attributionTag = " + mAttributionTag + ", " +
+ "attributionLabel = " + mAttributionLabel + ", " +
+ "proxyLabel = " + mProxyLabel +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(PermissionGroupUsage other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ PermissionGroupUsage that = (PermissionGroupUsage) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && mUid == that.mUid
+ && mLastAccessTimeMillis == that.mLastAccessTimeMillis
+ && java.util.Objects.equals(mPermissionGroupName, that.mPermissionGroupName)
+ && mActive == that.mActive
+ && mPhoneCall == that.mPhoneCall
+ && java.util.Objects.equals(mAttributionTag, that.mAttributionTag)
+ && java.util.Objects.equals(mAttributionLabel, that.mAttributionLabel)
+ && java.util.Objects.equals(mProxyLabel, that.mProxyLabel);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + mUid;
+ _hash = 31 * _hash + Long.hashCode(mLastAccessTimeMillis);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPermissionGroupName);
+ _hash = 31 * _hash + Boolean.hashCode(mActive);
+ _hash = 31 * _hash + Boolean.hashCode(mPhoneCall);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionLabel);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mProxyLabel);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ int flg = 0;
+ if (mActive) flg |= 0x10;
+ if (mPhoneCall) flg |= 0x20;
+ if (mAttributionTag != null) flg |= 0x40;
+ if (mAttributionLabel != null) flg |= 0x80;
+ if (mProxyLabel != null) flg |= 0x100;
+ dest.writeInt(flg);
+ dest.writeString(mPackageName);
+ dest.writeInt(mUid);
+ dest.writeLong(mLastAccessTimeMillis);
+ dest.writeString(mPermissionGroupName);
+ if (mAttributionTag != null) dest.writeCharSequence(mAttributionTag);
+ if (mAttributionLabel != null) dest.writeCharSequence(mAttributionLabel);
+ if (mProxyLabel != null) dest.writeCharSequence(mProxyLabel);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ PermissionGroupUsage(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int flg = in.readInt();
+ boolean active = (flg & 0x10) != 0;
+ boolean phoneCall = (flg & 0x20) != 0;
+ String packageName = in.readString();
+ int uid = in.readInt();
+ long lastAccessTimeMillis = in.readLong();
+ String permissionGroupName = in.readString();
+ CharSequence attributionTag = (flg & 0x40) == 0 ? null : (CharSequence) in.readCharSequence();
+ CharSequence attributionLabel = (flg & 0x80) == 0 ? null : (CharSequence) in.readCharSequence();
+ CharSequence proxyLabel = (flg & 0x100) == 0 ? null : (CharSequence) in.readCharSequence();
+
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mUid = uid;
+ this.mLastAccessTimeMillis = lastAccessTimeMillis;
+ this.mPermissionGroupName = permissionGroupName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPermissionGroupName);
+ this.mActive = active;
+ this.mPhoneCall = phoneCall;
+ this.mAttributionTag = attributionTag;
+ this.mAttributionLabel = attributionLabel;
+ this.mProxyLabel = proxyLabel;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<PermissionGroupUsage> CREATOR
+ = new Parcelable.Creator<PermissionGroupUsage>() {
+ @Override
+ public PermissionGroupUsage[] newArray(int size) {
+ return new PermissionGroupUsage[size];
+ }
+
+ @Override
+ public PermissionGroupUsage createFromParcel(@NonNull android.os.Parcel in) {
+ return new PermissionGroupUsage(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1645067417023L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/permission/PermissionGroupUsage.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final int mUid\nprivate final long mLastAccessTimeMillis\nprivate final @android.annotation.NonNull java.lang.String mPermissionGroupName\nprivate final boolean mActive\nprivate final boolean mPhoneCall\nprivate final @android.annotation.Nullable java.lang.CharSequence mAttributionTag\nprivate final @android.annotation.Nullable java.lang.CharSequence mAttributionLabel\nprivate final @android.annotation.Nullable java.lang.CharSequence mProxyLabel\nclass PermissionGroupUsage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fc7ac11..c509de6 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -193,6 +193,17 @@
*/
public static final boolean DEBUG_TRACE_PERMISSION_UPDATES = false;
+ /**
+ * Intent extra: List of PermissionGroupUsages
+ * <p>
+ * Type: {@code List<PermissionGroupUsage>}
+ * </p>
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PERMISSION_USAGES =
+ "android.permission.extra.PERMISSION_USAGES";
+
private final @NonNull Context mContext;
private final IPackageManager mPackageManager;
@@ -1108,7 +1119,7 @@
@TestApi
@NonNull
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
- public List<PermGroupUsage> getIndicatorAppOpUsageData() {
+ public List<PermissionGroupUsage> getIndicatorAppOpUsageData() {
return getIndicatorAppOpUsageData(new AudioManager().isMicrophoneMute());
}
@@ -1122,7 +1133,7 @@
@TestApi
@NonNull
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
- public List<PermGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) {
+ public List<PermissionGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) {
// Lazily initialize the usage helper
initializeUsageHelper();
return mUsageHelper.getOpUsageData(micMuted);
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 658e033..0b57842 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -42,7 +42,10 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.icu.text.ListFormatter;
import android.media.AudioManager;
import android.os.Process;
@@ -70,20 +73,30 @@
public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener,
AppOpsManager.OnOpStartedListener {
- /** Whether to show the mic and camera icons. */
+ /**
+ * Whether to show the mic and camera icons.
+ */
private static final String PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled";
- /** Whether to show the location indicators. */
+ /**
+ * Whether to show the location indicators.
+ */
private static final String PROPERTY_LOCATION_INDICATORS_ENABLED =
"location_indicators_enabled";
- /** Whether to show the Permissions Hub. */
+ /**
+ * Whether to show the Permissions Hub.
+ */
private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled";
- /** How long after an access to show it as "recent" */
+ /**
+ * How long after an access to show it as "recent"
+ */
private static final String RECENT_ACCESS_TIME_MS = "recent_access_time_ms";
- /** How long after an access to show it as "running" */
+ /**
+ * How long after an access to show it as "running"
+ */
private static final String RUNNING_ACCESS_TIME_MS = "running_access_time_ms";
private static final String SYSTEM_PKG = "android";
@@ -132,7 +145,7 @@
);
private static @NonNull String getGroupForOp(String op) {
- switch(op) {
+ switch (op) {
case OPSTR_RECORD_AUDIO:
return MICROPHONE;
case OPSTR_CAMERA:
@@ -158,6 +171,7 @@
/**
* Constructor for PermissionUsageHelper
+ *
* @param context The context from which to derive the package information
*/
public PermissionUsageHelper(@NonNull Context context) {
@@ -167,9 +181,9 @@
mUserContexts = new ArrayMap<>();
mUserContexts.put(Process.myUserHandle(), mContext);
// TODO ntmyren: make this listen for flag enable/disable changes
- String[] opStrs = { OPSTR_CAMERA, OPSTR_RECORD_AUDIO };
+ String[] opStrs = {OPSTR_CAMERA, OPSTR_RECORD_AUDIO};
mAppOpsManager.startWatchingActive(opStrs, context.getMainExecutor(), this);
- int[] ops = { OP_CAMERA, OP_RECORD_AUDIO };
+ int[] ops = {OP_CAMERA, OP_RECORD_AUDIO};
mAppOpsManager.startWatchingStarted(ops, this);
}
@@ -225,8 +239,8 @@
@Override
public void onOpStarted(int op, int uid, String packageName, String attributionTag,
- @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
- // not part of an attribution chain. Do nothing
+ @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
+ // not part of an attribution chain. Do nothing
}
@Override
@@ -274,8 +288,8 @@
/**
* @see PermissionManager.getIndicatorAppOpUsageData
*/
- public @NonNull List<PermGroupUsage> getOpUsageData(boolean isMicMuted) {
- List<PermGroupUsage> usages = new ArrayList<>();
+ public @NonNull List<PermissionGroupUsage> getOpUsageData(boolean isMicMuted) {
+ List<PermissionGroupUsage> usages = new ArrayList<>();
if (!shouldShowIndicators()) {
return usages;
@@ -313,6 +327,9 @@
}
}
+ // map of package name -> map of attribution tag -> attribution labels
+ ArrayMap<String, Map<String, String>> subAttributionLabelsMap = new ArrayMap<>();
+
for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
boolean isPhone = false;
String permGroup = usedPermGroups.get(permGroupNum);
@@ -320,6 +337,8 @@
ArrayMap<OpUsage, CharSequence> usagesWithLabels =
getUniqueUsagesWithLabels(permGroup, rawUsages.get(permGroup));
+ updateSubattributionLabelsMap(rawUsages.get(permGroup), subAttributionLabelsMap);
+
if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) {
isPhone = true;
permGroup = MICROPHONE;
@@ -330,15 +349,86 @@
for (int usageNum = 0; usageNum < usagesWithLabels.size(); usageNum++) {
OpUsage usage = usagesWithLabels.keyAt(usageNum);
- usages.add(new PermGroupUsage(usage.packageName, usage.uid, permGroup,
- usage.lastAccessTime, usage.isRunning, isPhone,
- usagesWithLabels.valueAt(usageNum)));
+ String attributionLabel = subAttributionLabelsMap.getOrDefault(usage.packageName,
+ new ArrayMap<>()).getOrDefault(usage.attributionTag, null);
+ usages.add(
+ new PermissionGroupUsage(usage.packageName, usage.uid, usage.lastAccessTime,
+ permGroup,
+ usage.isRunning, isPhone, usage.attributionTag, attributionLabel,
+ usagesWithLabels.valueAt(usageNum)));
}
}
return usages;
}
+ private void updateSubattributionLabelsMap(List<OpUsage> usages,
+ ArrayMap<String, Map<String, String>> subAttributionLabelsMap) {
+ if (usages == null || usages.isEmpty()) {
+ return;
+ }
+ for (OpUsage usage : usages) {
+ if (usage.attributionTag != null && !subAttributionLabelsMap.containsKey(
+ usage.packageName)) {
+ subAttributionLabelsMap.put(usage.packageName,
+ getSubattributionLabelsForPackage(usage.packageName, usage.uid));
+ }
+ }
+ }
+
+ /**
+ * Query attribution labels for a package
+ *
+ * @param packageName
+ * @param uid
+ * @return map of attribution tag -> attribution labels for a package
+ */
+ private ArrayMap<String, String> getSubattributionLabelsForPackage(String packageName,
+ int uid) {
+ ArrayMap<String, String> attributionLabelMap = new ArrayMap<>();
+ UserHandle user = UserHandle.getUserHandleForUid(uid);
+ try {
+ if (!isSubattributionSupported(packageName, uid)) {
+ return attributionLabelMap;
+ }
+ Context userContext = getUserContext(user);
+ PackageInfo packageInfo = userContext.getPackageManager().getPackageInfo(
+ packageName,
+ PackageManager.GET_PERMISSIONS | PackageManager.GET_ATTRIBUTIONS);
+ Context pkgContext = userContext.createPackageContext(packageInfo.packageName, 0);
+ for (Attribution attribution : packageInfo.attributions) {
+ try {
+ String resourceForLabel = pkgContext.getString(attribution.getLabel());
+ attributionLabelMap.put(attribution.getTag(), resourceForLabel);
+ } catch (Resources.NotFoundException e) {
+ // Shouldn't happen, do nothing
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Did not find the package, do nothing
+ }
+ return attributionLabelMap;
+ }
+
+ /**
+ * Returns true if the app supports subattribution.
+ */
+ private boolean isSubattributionSupported(String packageName, int uid) {
+ try {
+ PackageManager userPkgManager =
+ getUserContext(UserHandle.getUserHandleForUid(uid)).getPackageManager();
+ ApplicationInfo appInfo = userPkgManager.getApplicationInfoAsUser(packageName,
+ PackageManager.ApplicationInfoFlags.of(0),
+ UserHandle.getUserId(uid));
+ if (appInfo != null) {
+ return appInfo.areAttributionsUserVisible();
+ }
+ return false;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
/**
* Get the raw usages from the system, and then parse out the ones that are not recent enough,
* determine which permission group each belongs in, and removes duplicates (if the same app
@@ -346,7 +436,6 @@
* running/recent info, if the usage is a phone call, per permission group.
*
* @param opNames a list of op names to get usage for
- *
* @return A map of permission group -> list of usages that are recent or running
*/
private Map<String, List<OpUsage>> getOpUsages(List<String> opNames) {
@@ -378,6 +467,7 @@
List<String> attributionTags =
new ArrayList<>(opEntry.getAttributedOpEntries().keySet());
+
int numAttrEntries = opEntry.getAttributedOpEntries().size();
for (int attrOpEntryNum = 0; attrOpEntryNum < numAttrEntries; attrOpEntryNum++) {
String attributionTag = attributionTags.get(attrOpEntryNum);
@@ -456,6 +546,7 @@
ArrayMap<OpUsage, ArrayList<CharSequence>> proxyLabels = new ArrayMap<>();
// map of usage.proxy hash -> usage hash, telling us if a usage is a proxy
ArrayMap<Integer, OpUsage> proxies = new ArrayMap<>();
+
for (int i = 0; i < usages.size(); i++) {
OpUsage usage = usages.get(i);
allUsages.put(usage.getPackageIdHash(), usage);
@@ -588,7 +679,6 @@
} catch (PackageManager.NameNotFoundException e) {
// do nothing
}
-
}
usagesAndLabels.put(start.usage, proxyLabel);
}
diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java
index f5c59b5..462ac14 100644
--- a/core/java/android/service/attention/AttentionService.java
+++ b/core/java/android/service/attention/AttentionService.java
@@ -128,9 +128,9 @@
/** {@inheritDoc} */
@Override
- public void onStartProximityUpdates(IProximityCallback callback) {
+ public void onStartProximityUpdates(IProximityUpdateCallback callback) {
Objects.requireNonNull(callback);
- AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback));
+ AttentionService.this.onStartProximityUpdates(new ProximityUpdateCallback(callback));
}
@@ -166,11 +166,11 @@
/**
* Requests the continuous updates of proximity signal via the provided callback,
- * until the given callback is unregistered.
+ * until {@link #onStopProximityUpdates} is called.
*
* @param callback the callback to return the result to
*/
- public void onStartProximityUpdates(@NonNull ProximityCallback callback) {
+ public void onStartProximityUpdates(@NonNull ProximityUpdateCallback callback) {
Slog.w(LOG_TAG, "Override this method.");
}
@@ -213,22 +213,24 @@
}
}
- /** Callbacks for ProximityCallback results. */
- public static final class ProximityCallback {
- @NonNull private final WeakReference<IProximityCallback> mCallback;
+ /** Callbacks for ProximityUpdateCallback results. */
+ public static final class ProximityUpdateCallback {
+ @NonNull private final WeakReference<IProximityUpdateCallback> mCallback;
- private ProximityCallback(@NonNull IProximityCallback callback) {
+ private ProximityUpdateCallback(@NonNull IProximityUpdateCallback callback) {
mCallback = new WeakReference<>(callback);
}
/**
* @param distance the estimated distance of the user (in meter)
- * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive.
- *
+ * The distance will be {@link #PROXIMITY_UNKNOWN} if the proximity sensing
+ * was inconclusive.
*/
public void onProximityUpdate(double distance) {
try {
- mCallback.get().onProximityUpdate(distance);
+ if (mCallback.get() != null) {
+ mCallback.get().onProximityUpdate(distance);
+ }
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl
index 8bb881b..e3ce114 100644
--- a/core/java/android/service/attention/IAttentionService.aidl
+++ b/core/java/android/service/attention/IAttentionService.aidl
@@ -17,7 +17,7 @@
package android.service.attention;
import android.service.attention.IAttentionCallback;
-import android.service.attention.IProximityCallback;
+import android.service.attention.IProximityUpdateCallback;
/**
* Interface for a concrete implementation to provide to the AttentionManagerService.
@@ -27,6 +27,6 @@
oneway interface IAttentionService {
void checkAttention(IAttentionCallback callback);
void cancelAttentionCheck(IAttentionCallback callback);
- void onStartProximityUpdates(IProximityCallback callback);
+ void onStartProximityUpdates(IProximityUpdateCallback callback);
void onStopProximityUpdates();
}
\ No newline at end of file
diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityUpdateCallback.aidl
similarity index 77%
rename from core/java/android/service/attention/IProximityCallback.aidl
rename to core/java/android/service/attention/IProximityUpdateCallback.aidl
index 9ecf9bc..26daa5c 100644
--- a/core/java/android/service/attention/IProximityCallback.aidl
+++ b/core/java/android/service/attention/IProximityUpdateCallback.aidl
@@ -5,6 +5,6 @@
*
* @hide
*/
-oneway interface IProximityCallback {
+oneway interface IProximityUpdateCallback {
void onProximityUpdate(double distance);
}
diff --git a/core/java/android/service/autofill/OWNERS b/core/java/android/service/autofill/OWNERS
index a088632..9a30e82 100644
--- a/core/java/android/service/autofill/OWNERS
+++ b/core/java/android/service/autofill/OWNERS
@@ -1,9 +1,3 @@
# Bug component: 351486
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
-augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+include /core/java/android/view/autofill/OWNERS
diff --git a/core/java/android/service/contentcapture/OWNERS b/core/java/android/service/contentcapture/OWNERS
index 6337327..24561c5 100644
--- a/core/java/android/service/contentcapture/OWNERS
+++ b/core/java/android/service/contentcapture/OWNERS
@@ -1,9 +1,3 @@
# Bug component: 544200
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
-augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+include /core/java/android/view/contentcapture/OWNERS
diff --git a/core/java/android/service/contentsuggestions/OWNERS b/core/java/android/service/contentsuggestions/OWNERS
index 46b5ea0..72fe0b1 100644
--- a/core/java/android/service/contentsuggestions/OWNERS
+++ b/core/java/android/service/contentsuggestions/OWNERS
@@ -1,7 +1,2 @@
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
-augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+
+include /core/java/android/app/contentsuggestions/OWNERS
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index 08a7205..f028ed3 100644
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -24,6 +24,8 @@
import android.util.SparseArray;
import android.view.selectiontoolbar.ShowInfo;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.UUID;
/**
@@ -130,5 +132,22 @@
}
}
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ int size = mToolbarCache.size();
+ pw.print("number selectionToolbar: "); pw.println(size);
+ String pfx = " ";
+ for (int i = 0; i < size; i++) {
+ pw.print("#"); pw.println(i);
+ int callingUid = mToolbarCache.keyAt(i);
+ pw.print(pfx); pw.print("callingUid: "); pw.println(callingUid);
+ Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
+ RemoteSelectionToolbar selectionToolbar = toolbarPair.second;
+ pw.print(pfx); pw.print("selectionToolbar: ");
+ selectionToolbar.dump(pfx, pw);
+ pw.println();
+ }
+ }
}
diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
index 04491f0..8fe6f71 100644
--- a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
+++ b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
@@ -24,6 +24,8 @@
import android.view.MotionEvent;
import android.widget.LinearLayout;
+import java.io.PrintWriter;
+
/**
* This class is the root view for the selection toolbar. It is responsible for
* detecting the click on the item and to also transfer input focus to the application.
@@ -40,6 +42,9 @@
private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
private Rect mContentRect;
+ private int mLastDownX = -1;
+ private int mLastDownY = -1;
+
public FloatingToolbarRoot(Context context, IBinder targetInputToken,
SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
super(context);
@@ -59,13 +64,13 @@
@SuppressLint("ClickableViewAccessibility")
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- int downX = (int) event.getX();
- int downY = (int) event.getY();
+ mLastDownX = (int) event.getX();
+ mLastDownY = (int) event.getY();
if (DEBUG) {
- Log.d(TAG, "downX=" + downX + " downY=" + downY);
+ Log.d(TAG, "downX=" + mLastDownX + " downY=" + mLastDownY);
}
// TODO(b/215497659): Check FLAG_WINDOW_IS_PARTIALLY_OBSCURED
- if (!mContentRect.contains(downX, downY)) {
+ if (!mContentRect.contains(mLastDownX, mLastDownY)) {
if (DEBUG) {
Log.d(TAG, "Transfer touch focus to application.");
}
@@ -75,4 +80,10 @@
}
return super.dispatchTouchEvent(event);
}
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.println("FloatingToolbarRoot:");
+ pw.print(prefix + " "); pw.print("last down X: "); pw.println(mLastDownX);
+ pw.print(prefix + " "); pw.print("last down Y: "); pw.println(mLastDownY);
+ }
}
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index 179d39d..d75fbc0 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -58,6 +58,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
+import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -1318,6 +1319,7 @@
contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
+ contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG);
contentContainer.setClipToOutline(true);
return contentContainer;
}
@@ -1371,4 +1373,32 @@
Log.v(TAG, message);
}
}
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("toolbar token: "); pw.println(mSelectionToolbarToken);
+ pw.print(prefix); pw.print("dismissed: "); pw.println(mDismissed);
+ pw.print(prefix); pw.print("hidden: "); pw.println(mHidden);
+ pw.print(prefix); pw.print("popup width: "); pw.println(mPopupWidth);
+ pw.print(prefix); pw.print("popup height: "); pw.println(mPopupHeight);
+ pw.print(prefix); pw.print("relative coords: "); pw.println(mRelativeCoordsForToolbar);
+ pw.print(prefix); pw.print("main panel size: "); pw.println(mMainPanelSize);
+ boolean hasOverflow = hasOverflow();
+ pw.print(prefix); pw.print("has overflow: "); pw.println(hasOverflow);
+ if (hasOverflow) {
+ pw.print(prefix); pw.print("overflow open: "); pw.println(mIsOverflowOpen);
+ pw.print(prefix); pw.print("overflow size: "); pw.println(mOverflowPanelSize);
+ }
+ if (mSurfaceControlViewHost != null) {
+ FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView();
+ root.dump(prefix, pw);
+ }
+ if (mMenuItems != null) {
+ int menuItemSize = mMenuItems.size();
+ pw.print(prefix); pw.print("number menu items: "); pw.println(menuItemSize);
+ for (int i = 0; i < menuItemSize; i++) {
+ pw.print(prefix); pw.print("#"); pw.println(i);
+ pw.print(prefix + " "); pw.println(mMenuItems.get(i));
+ }
+ }
+ }
}
diff --git a/core/java/android/service/textclassifier/OWNERS b/core/java/android/service/textclassifier/OWNERS
index a535f52..c85c69e 100644
--- a/core/java/android/service/textclassifier/OWNERS
+++ b/core/java/android/service/textclassifier/OWNERS
@@ -1,9 +1,3 @@
# Bug component: 709498
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
-augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+include /core/java/android/view/textclassifier/OWNERS
diff --git a/core/java/android/service/translation/OWNERS b/core/java/android/service/translation/OWNERS
index a1e663a..440f9a8 100644
--- a/core/java/android/service/translation/OWNERS
+++ b/core/java/android/service/translation/OWNERS
@@ -1,8 +1,3 @@
# Bug component: 994311
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+include /core/java/android/view/translation/OWNERS
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index 46b5ea0..59a0c2e 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -1,7 +1,3 @@
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
-augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+# Bug component: 533220
+
+include /core/java/android/app/assist/OWNERS
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 22cffc1..6a65efb 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -199,7 +199,7 @@
}
private void dispatchTriggerModelDownload(Intent intent) {
- RecognitionService.this.triggerModelDownload(intent);
+ RecognitionService.this.onTriggerModelDownload(intent);
}
private class StartListeningArgs {
@@ -283,7 +283,7 @@
/**
* Requests the download of the recognizer support for {@code recognizerIntent}.
*/
- public void triggerModelDownload(@NonNull Intent recognizerIntent) {
+ public void onTriggerModelDownload(@NonNull Intent recognizerIntent) {
if (DBG) {
Log.i(TAG, String.format("#downloadModel [%s]", recognizerIntent));
}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 748d551..bedd409 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -287,6 +287,16 @@
}
/**
+ * Retrieves the font metrics for the given range.
+ *
+ * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
+ */
+ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+ @NonNull Paint.FontMetricsInt fmi) {
+ mMeasuredText.getFontMetricsInt(start, end, fmi);
+ }
+
+ /**
* Returns a width of the character at the offset.
*
* This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index be66db2..9307e56 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -21,6 +21,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
@@ -697,6 +698,38 @@
}
/**
+ * Retrieves the text font metrics for the given range.
+ * Both {@code start} and {@code end} offset need to be in the same paragraph, otherwise
+ * IllegalArgumentException will be thrown.
+ *
+ * @param start the inclusive start offset in the text
+ * @param end the exclusive end offset in the text
+ * @param outMetrics the output font metrics
+ * @throws IllegalArgumentException if start and end offset are in the different paragraph.
+ */
+ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+ @NonNull Paint.FontMetricsInt outMetrics) {
+ Preconditions.checkArgument(0 <= start && start <= mText.length(), "invalid start offset");
+ Preconditions.checkArgument(0 <= end && end <= mText.length(), "invalid end offset");
+ Preconditions.checkArgument(start <= end, "start offset can not be larger than end offset");
+ Objects.requireNonNull(outMetrics);
+ if (start == end) {
+ mParams.getTextPaint().getFontMetricsInt(outMetrics);
+ return;
+ }
+ final int paraIndex = findParaIndex(start);
+ final int paraStart = getParagraphStart(paraIndex);
+ final int paraEnd = getParagraphEnd(paraIndex);
+ if (start < paraStart || paraEnd < end) {
+ throw new IllegalArgumentException("Cannot measured across the paragraph:"
+ + "para: (" + paraStart + ", " + paraEnd + "), "
+ + "request: (" + start + ", " + end + ")");
+ }
+ getMeasuredParagraph(paraIndex).getFontMetricsInt(start - paraStart,
+ end - paraStart, outMetrics);
+ }
+
+ /**
* Returns a width of a character at offset
*
* @param offset an offset of the text.
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 49e2111..e39231c 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -866,8 +866,12 @@
wp.getFontMetricsInt(mChars, start, count, contextStart, contextCount, runIsRtl,
fmi);
} else {
- wp.getFontMetricsInt(mText, mStart + start, count, mStart + contextStart, contextCount,
- runIsRtl, fmi);
+ if (mComputed == null) {
+ wp.getFontMetricsInt(mText, mStart + start, count, mStart + contextStart,
+ contextCount, runIsRtl, fmi);
+ } else {
+ mComputed.getFontMetricsInt(mStart + start, mStart + end, fmi);
+ }
}
updateMetrics(fmi, previousTop, previousAscent, previousDescent, previousBottom,
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index ff04825..c5ab82d 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -18,13 +18,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.pm.Signature;
import android.text.TextUtils;
import libcore.util.HexEncoding;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@@ -35,6 +39,9 @@
*/
public final class PackageUtils {
+ private static final int LOW_RAM_BUFFER_SIZE_BYTES = 1 * 1000; // 1 kB
+ private static final int HIGH_RAM_BUFFER_SIZE_BYTES = 1 * 1000 * 1000; // 1 MB
+
private PackageUtils() {
/* hide constructor */
}
@@ -162,4 +169,55 @@
return TextUtils.join(separator, pieces);
}
+
+ /**
+ * @see #computeSha256DigestForLargeFile(String, String)
+ */
+ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath) {
+ return computeSha256DigestForLargeFile(filePath, null);
+ }
+
+ /**
+ * Computes the SHA256 digest of large files.
+ * @param filePath The path to which the file's content is to be hashed.
+ * @param separator Separator between each pair of characters, such as colon, or null to omit.
+ * @return The digest or null if an error occurs.
+ */
+ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath,
+ @Nullable String separator) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA256");
+ messageDigest.reset();
+ } catch (NoSuchAlgorithmException e) {
+ // this shouldn't happen!
+ return null;
+ }
+
+ boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+ int bufferSize = isLowRamDevice ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES;
+
+ File f = new File(filePath);
+ try {
+ DigestInputStream digestStream = new DigestInputStream(new FileInputStream(f),
+ messageDigest);
+ byte[] buffer = new byte[bufferSize];
+ while (digestStream.read(buffer) != -1);
+ } catch (IOException e) {
+ return null;
+ }
+
+ byte[] resultBytes = messageDigest.digest();
+
+ if (separator == null) {
+ return HexEncoding.encodeToString(resultBytes, true);
+ }
+
+ int length = resultBytes.length;
+ String[] pieces = new String[length];
+ for (int index = 0; index < length; index++) {
+ pieces[index] = HexEncoding.encodeToString(resultBytes[index], true);
+ }
+ return TextUtils.join(separator, pieces);
+ }
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index b8eb602..8e3cc34 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -499,9 +499,9 @@
*
* @param callback The extended frame callback to run during the next frame.
*
- * @see #removeExtendedFrameCallback
+ * @see #removeVsyncCallback
*/
- public void postExtendedFrameCallback(@NonNull ExtendedFrameCallback callback) {
+ public void postVsyncCallback(@NonNull VsyncCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
@@ -603,9 +603,9 @@
*
* @param callback The extended frame callback to remove.
*
- * @see #postExtendedFrameCallback
+ * @see #postVsyncCallback
*/
- public void removeExtendedFrameCallback(@Nullable ExtendedFrameCallback callback) {
+ public void removeVsyncCallback(@Nullable VsyncCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
@@ -1021,7 +1021,7 @@
* The time in {@link System#nanoTime()} timebase which this frame is expected to be
* presented.
*/
- public long getExpectedPresentTimeNanos() {
+ public long getExpectedPresentationTimeNanos() {
return mExpectedPresentTimeNanos;
}
@@ -1034,7 +1034,7 @@
}
/**
- * The payload for {@link ExtendedFrameCallback} which includes frame information such as when
+ * The payload for {@link VsyncCallback} which includes frame information such as when
* the frame started being rendered, and multiple possible frame timelines and their
* information including deadline and expected present time.
*/
@@ -1101,7 +1101,7 @@
*
* @see FrameCallback
*/
- public interface ExtendedFrameCallback {
+ public interface VsyncCallback {
/**
* Called when a new display frame is being rendered.
*
@@ -1214,7 +1214,7 @@
void run(FrameData frameData) {
if (token == EXTENDED_FRAME_CALLBACK_TOKEN) {
- ((ExtendedFrameCallback) action).onVsync(frameData);
+ ((VsyncCallback) action).onVsync(frameData);
} else {
run(frameData.getFrameTimeNanos());
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c08177e..98cef958 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -389,9 +389,7 @@
public static final int JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED = 0x4;
// Either App or GPU took too long on the frame
public static final int JANK_APP_DEADLINE_MISSED = 0x8;
- // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a
- // jank
- // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame.
+ // Vsync predictions have drifted beyond the threshold from the actual HWVsync
public static final int PREDICTION_ERROR = 0x10;
// Latching a buffer early might cause an early present of the frame
public static final int SURFACE_FLINGER_SCHEDULING = 0x20;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 179f6ee..4dc1fca 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11785,7 +11785,10 @@
* user and that ideally it should not be covered. Setting this is only appropriate for UI
* where the user would likely take action to uncover it.
* <p>
- * The system will try to respect this, but when not possible will ignore it.
+ * The system will try to respect this preference, but when not possible will ignore it.
+ * <p>
+ * Note: while this is set to {@code true}, the system will ignore the {@code Rect}s provided
+ * through {@link #setPreferKeepClearRects} (but not clear them).
* <p>
* @see #setPreferKeepClearRects
* @see #isPreferKeepClear
@@ -11817,11 +11820,11 @@
* user and that ideally they should not be covered. Setting this is only appropriate for UI
* where the user would likely take action to uncover it.
* <p>
- * If the whole view is preferred to be clear ({@link #isPreferKeepClear}), the rects set here
- * will be ignored.
- * <p>
* The system will try to respect this preference, but when not possible will ignore it.
* <p>
+ * Note: While {@link #isPreferKeepClear} is {@code true}, the {@code Rect}s set here are
+ * ignored.
+ * <p>
* @see #setPreferKeepClear
* @see #getPreferKeepClearRects
*/
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index a088632..108c42c 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -1,9 +1,7 @@
# Bug component: 351486
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+joannechung@google.com
+markpun@google.com
+lpeter@google.com
+tymtsai@google.com
diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS
index 6337327..1a5cb1e4 100644
--- a/core/java/android/view/contentcapture/OWNERS
+++ b/core/java/android/view/contentcapture/OWNERS
@@ -1,9 +1,7 @@
# Bug component: 544200
-joannechung@google.com
-adamhe@google.com
-tymtsai@google.com
-lpeter@google.com
augale@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
+joannechung@google.com
+markpun@google.com
+lpeter@google.com
+tymtsai@google.com
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 9a70667..d7c1846 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -64,6 +64,7 @@
* @attr ref android.R.styleable#InputMethod_isDefault
* @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
* @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions
+ * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestionsWithTouchExploration
* @attr ref android.R.styleable#InputMethod_suppressesSpellChecker
* @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
* @attr ref android.R.styleable#InputMethod_configChanges
@@ -125,6 +126,11 @@
private final boolean mInlineSuggestionsEnabled;
/**
+ * The flag whether this IME supports inline suggestions when touch exploration is enabled.
+ */
+ private final boolean mSupportsInlineSuggestionsWithTouchExploration;
+
+ /**
* The flag whether this IME suppresses spell checker.
*/
private final boolean mSuppressesSpellChecker;
@@ -189,6 +195,7 @@
boolean isAuxIme = true;
boolean supportsSwitchingToNextInputMethod = false; // false as default
boolean inlineSuggestionsEnabled = false; // false as default
+ boolean supportsInlineSuggestionsWithTouchExploration = false; // false as default
boolean suppressesSpellChecker = false; // false as default
boolean showInInputMethodPicker = true; // true as default
mForceDefault = false;
@@ -234,6 +241,9 @@
false);
inlineSuggestionsEnabled = sa.getBoolean(
com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false);
+ supportsInlineSuggestionsWithTouchExploration = sa.getBoolean(
+ com.android.internal.R.styleable
+ .InputMethod_supportsInlineSuggestionsWithTouchExploration, false);
suppressesSpellChecker = sa.getBoolean(
com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false);
showInInputMethodPicker = sa.getBoolean(
@@ -314,6 +324,8 @@
mIsAuxIme = isAuxIme;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+ mSupportsInlineSuggestionsWithTouchExploration =
+ supportsInlineSuggestionsWithTouchExploration;
mSuppressesSpellChecker = suppressesSpellChecker;
mShowInInputMethodPicker = showInInputMethodPicker;
mIsVrOnly = isVrOnly;
@@ -326,6 +338,7 @@
mIsAuxIme = source.readInt() == 1;
mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
mInlineSuggestionsEnabled = source.readInt() == 1;
+ mSupportsInlineSuggestionsWithTouchExploration = source.readInt() == 1;
mSuppressesSpellChecker = source.readBoolean();
mShowInInputMethodPicker = source.readBoolean();
mIsVrOnly = source.readBoolean();
@@ -345,7 +358,8 @@
settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
- 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */);
+ 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
+ false /* inlineSuggestionsEnabled */);
}
/**
@@ -360,7 +374,8 @@
settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges,
- false /* supportsStylusHandwriting */);
+ false /* supportsStylusHandwriting */,
+ false /* inlineSuggestionsEnabled */);
}
/**
@@ -373,7 +388,8 @@
this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
false /* isVrOnly */, 0 /* handledconfigChanges */,
- false /* supportsStylusHandwriting */);
+ false /* supportsStylusHandwriting */,
+ false /* inlineSuggestionsEnabled */);
}
/**
@@ -385,7 +401,8 @@
boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) {
this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
- 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */);
+ 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
+ false /* inlineSuggestionsEnabled */);
}
/**
@@ -395,7 +412,8 @@
public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
- boolean isVrOnly, int handledConfigChanges, boolean supportsStylusHandwriting) {
+ boolean isVrOnly, int handledConfigChanges, boolean supportsStylusHandwriting,
+ boolean supportsInlineSuggestionsWithTouchExploration) {
final ServiceInfo si = ri.serviceInfo;
mService = ri;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -406,6 +424,8 @@
mForceDefault = forceDefault;
mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
mInlineSuggestionsEnabled = inlineSuggestionsEnabled;
+ mSupportsInlineSuggestionsWithTouchExploration =
+ supportsInlineSuggestionsWithTouchExploration;
mSuppressesSpellChecker = false;
mShowInInputMethodPicker = true;
mIsVrOnly = isVrOnly;
@@ -583,6 +603,8 @@
+ " mIsVrOnly=" + mIsVrOnly
+ " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
+ " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled
+ + " mSupportsInlineSuggestionsWithTouchExploration="
+ + mSupportsInlineSuggestionsWithTouchExploration
+ " mSuppressesSpellChecker=" + mSuppressesSpellChecker
+ " mShowInInputMethodPicker=" + mShowInInputMethodPicker
+ " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting);
@@ -654,6 +676,15 @@
}
/**
+ * Returns {@code true} if this input method supports inline suggestions when touch exploration
+ * is enabled.
+ * @hide
+ */
+ public boolean supportsInlineSuggestionsWithTouchExploration() {
+ return mSupportsInlineSuggestionsWithTouchExploration;
+ }
+
+ /**
* Return {@code true} if this input method suppresses spell checker.
*/
public boolean suppressesSpellChecker() {
@@ -683,6 +714,7 @@
dest.writeInt(mIsAuxIme ? 1 : 0);
dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0);
+ dest.writeInt(mSupportsInlineSuggestionsWithTouchExploration ? 1 : 0);
dest.writeBoolean(mSuppressesSpellChecker);
dest.writeBoolean(mShowInInputMethodPicker);
dest.writeBoolean(mIsVrOnly);
diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS
index d7db7c7..9fa7e8f 100644
--- a/core/java/android/view/inputmethod/OWNERS
+++ b/core/java/android/view/inputmethod/OWNERS
@@ -3,4 +3,4 @@
include /services/core/java/com/android/server/inputmethod/OWNERS
-per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
+per-file *InlineSuggestion* = file:/core/java/android/view/autofill/OWNERS
diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
index 6de0316..ef04b2c 100644
--- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
+++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
@@ -105,7 +105,7 @@
private boolean isRemoteSelectionToolbarEnabled() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR,
- REMOTE_SELECTION_TOOLBAR_ENABLED, false);
+ REMOTE_SELECTION_TOOLBAR_ENABLED, true);
}
/**
diff --git a/core/java/android/view/textclassifier/OWNERS b/core/java/android/view/textclassifier/OWNERS
index 4bcdeea..a205be2 100644
--- a/core/java/android/view/textclassifier/OWNERS
+++ b/core/java/android/view/textclassifier/OWNERS
@@ -2,8 +2,6 @@
mns@google.com
toki@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
augale@google.com
joannechung@google.com
tonymak@google.com
diff --git a/core/java/android/view/textclassifier/logging/OWNERS b/core/java/android/view/textclassifier/logging/OWNERS
deleted file mode 100644
index ac80d9f..0000000
--- a/core/java/android/view/textclassifier/logging/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 709498
-
-mns@google.com
-toki@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-augale@google.com
-joannechung@google.com
diff --git a/core/java/android/view/translation/OWNERS b/core/java/android/view/translation/OWNERS
index a1e663a..b772ad3 100644
--- a/core/java/android/view/translation/OWNERS
+++ b/core/java/android/view/translation/OWNERS
@@ -1,8 +1,7 @@
# Bug component: 994311
-adamhe@google.com
augale@google.com
joannechung@google.com
+markpun@google.com
lpeter@google.com
-svetoslavganov@google.com
tymtsai@google.com
diff --git a/core/java/android/widget/inline/OWNERS b/core/java/android/widget/inline/OWNERS
new file mode 100644
index 0000000..9a30e82
--- /dev/null
+++ b/core/java/android/widget/inline/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 351486
+
+include /core/java/android/view/autofill/OWNERS
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index aaaf729..7718a3b 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -364,8 +364,13 @@
private final Point mEndRelOffset = new Point();
private ActivityManager.RunningTaskInfo mTaskInfo = null;
private boolean mAllowEnterPip;
- private int mStartRotation = ROTATION_UNDEFINED;
- private int mEndRotation = ROTATION_UNDEFINED;
+ private @Surface.Rotation int mStartRotation = ROTATION_UNDEFINED;
+ private @Surface.Rotation int mEndRotation = ROTATION_UNDEFINED;
+ /**
+ * The end rotation of the top activity after fixed rotation is finished. If the top
+ * activity is not in fixed rotation, it will be {@link ROTATION_UNDEFINED}.
+ */
+ private @Surface.Rotation int mEndFixedRotation = ROTATION_UNDEFINED;
private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED;
private @ColorInt int mBackgroundColor;
@@ -388,6 +393,7 @@
mAllowEnterPip = in.readBoolean();
mStartRotation = in.readInt();
mEndRotation = in.readInt();
+ mEndFixedRotation = in.readInt();
mRotationAnimation = in.readInt();
mBackgroundColor = in.readInt();
}
@@ -441,6 +447,11 @@
mEndRotation = end;
}
+ /** Sets end rotation that top activity will be launched to after fixed rotation. */
+ public void setEndFixedRotation(@Surface.Rotation int endFixedRotation) {
+ mEndFixedRotation = endFixedRotation;
+ }
+
/**
* Sets the app-requested animation type for rotation. Will be one of the
* ROTATION_ANIMATION_ values in {@link android.view.WindowManager.LayoutParams};
@@ -521,14 +532,21 @@
return mAllowEnterPip;
}
+ @Surface.Rotation
public int getStartRotation() {
return mStartRotation;
}
+ @Surface.Rotation
public int getEndRotation() {
return mEndRotation;
}
+ @Surface.Rotation
+ public int getEndFixedRotation() {
+ return mEndFixedRotation;
+ }
+
/** @return the rotation animation. */
public int getRotationAnimation() {
return mRotationAnimation;
@@ -555,6 +573,7 @@
dest.writeBoolean(mAllowEnterPip);
dest.writeInt(mStartRotation);
dest.writeInt(mEndRotation);
+ dest.writeInt(mEndFixedRotation);
dest.writeInt(mRotationAnimation);
dest.writeInt(mBackgroundColor);
}
@@ -584,7 +603,8 @@
return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ " m=" + modeToString(mMode) + " f=" + flagsToString(mFlags) + " sb="
+ mStartAbsBounds + " eb=" + mEndAbsBounds + " eo=" + mEndRelOffset + " r="
- + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation + "}";
+ + mStartRotation + "->" + mEndRotation + ":" + mRotationAnimation
+ + " endFixedRotation=" + mEndFixedRotation + "}";
}
}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 17b675f..5007df5 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -16,16 +16,22 @@
package android.window;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+
/**
* The controller to manage {@link WindowContext}, such as attaching to a window manager node or
* detaching from the current attached node. The user must call
@@ -35,13 +41,43 @@
* @hide
*/
public class WindowContextController {
+ // TODO(220049234): Disable attach debug logging before shipping.
+ private static final boolean DEBUG_ATTACH = true;
+ private static final String TAG = "WindowContextController";
+
/**
- * {@code true} to indicate that the {@code mToken} is associated with a
+ * {@link AttachStatus.STATUS_ATTACHED} to indicate that the {@code mToken} is associated with a
* {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a
- * WindowToken after this flag sets to {@code true}.
+ * WindowToken after this flag sets to {@link AttachStatus.STATUS_ATTACHED}.
*/
@VisibleForTesting
- public boolean mAttachedToDisplayArea;
+ public int mAttachedToDisplayArea = AttachStatus.STATUS_INITIALIZED;
+
+ /**
+ * Status to indicate that the Window Context attach to a
+ * {@link com.android.server.wm.DisplayArea}.
+ */
+ @Retention(SOURCE)
+ @IntDef({AttachStatus.STATUS_INITIALIZED, AttachStatus.STATUS_ATTACHED,
+ AttachStatus.STATUS_DETACHED, AttachStatus.STATUS_FAILED})
+ public @interface AttachStatus{
+ /**
+ * The Window Context haven't attached to a {@link com.android.server.wm.DisplayArea}.
+ */
+ int STATUS_INITIALIZED = 0;
+ /**
+ * The Window Context has already attached to a {@link com.android.server.wm.DisplayArea}.
+ */
+ int STATUS_ATTACHED = 1;
+ /**
+ * The Window Context has detached from a {@link com.android.server.wm.DisplayArea}.
+ */
+ int STATUS_DETACHED = 2;
+ /**
+ * The Window Context fails to attach to a {@link com.android.server.wm.DisplayArea}.
+ */
+ int STATUS_FAILED = 3;
+ }
@NonNull
private final WindowTokenClient mToken;
@@ -65,11 +101,19 @@
* DisplayArea.
*/
public void attachToDisplayArea(@WindowType int type, int displayId, @Nullable Bundle options) {
- if (mAttachedToDisplayArea) {
+ if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
throw new IllegalStateException("A Window Context can be only attached to "
+ "a DisplayArea once.");
}
- mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options);
+ mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options)
+ ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED;
+ if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) {
+ Log.w(TAG, "attachToDisplayArea fail, type:" + type + ", displayId:"
+ + displayId);
+ } else if (DEBUG_ATTACH) {
+ Log.d(TAG, "attachToDisplayArea success, type:" + type + ", displayId:"
+ + displayId);
+ }
}
/**
@@ -93,18 +137,21 @@
* @see IWindowManager#attachWindowContextToWindowToken(IBinder, IBinder)
*/
public void attachToWindowToken(IBinder windowToken) {
- if (!mAttachedToDisplayArea) {
+ if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) {
throw new IllegalStateException("The Window Context should have been attached"
- + " to a DisplayArea.");
+ + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea);
}
mToken.attachToWindowToken(windowToken);
}
/** Detaches the window context from the node it's currently associated with. */
public void detachIfNeeded() {
- if (mAttachedToDisplayArea) {
+ if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
mToken.detachFromWindowContainerIfNeeded();
- mAttachedToDisplayArea = false;
+ mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED;
+ if (DEBUG_ATTACH) {
+ Log.d(TAG, "Detach Window Context.");
+ }
}
}
}
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index 0443ad0..0835824 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -455,7 +455,14 @@
if (mSourceU != null) {
// T done
mResultT = (T) res;
- mSourceU.whenComplete(this);
+
+ // Subscribe to the second job completion.
+ mSourceU.whenComplete((r, e) -> {
+ // Mark the first job completion by setting mSourceU to null, so that next time
+ // the execution flow goes to the else case below.
+ mSourceU = null;
+ accept(r, e);
+ });
} else {
// U done
try {
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index e1a67d8..34c47ed 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -98,6 +98,7 @@
private final Handler mHandler;
private final ChoreographerWrapper mChoreographer;
private final Object mLock = InteractionJankMonitor.getInstance().getLock();
+ private final boolean mDeferMonitoring;
@VisibleForTesting
public final boolean mSurfaceOnly;
@@ -153,6 +154,7 @@
mHandler = handler;
mChoreographer = choreographer;
mSurfaceControlWrapper = surfaceControlWrapper;
+ mDeferMonitoring = config.shouldDeferMonitor();
// HWUI instrumentation init.
mRendererWrapper = mSurfaceOnly ? null : renderer;
@@ -228,12 +230,25 @@
*/
public void begin() {
synchronized (mLock) {
- mBeginVsyncId = mChoreographer.getVsyncId() + 1;
+ final long currentVsync = mChoreographer.getVsyncId();
+ // In normal case, we should begin at the next frame,
+ // the id of the next frame is not simply increased by 1,
+ // but we can exclude the current frame at least.
+ mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync;
if (DEBUG) {
- Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId);
+ Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId
+ + ", defer=" + mDeferMonitoring);
}
if (mSurfaceControl != null) {
- postTraceStartMarker();
+ if (mDeferMonitoring) {
+ // Normal case, we begin the instrument from the very beginning,
+ // except the first frame.
+ postTraceStartMarker();
+ } else {
+ // If we don't begin the instrument from the very beginning,
+ // there is no need to skip the frame where the begin invocation happens.
+ beginInternal();
+ }
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
}
if (!mSurfaceOnly) {
@@ -247,15 +262,18 @@
*/
@VisibleForTesting
public void postTraceStartMarker() {
- mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, () -> {
- synchronized (mLock) {
- if (mCancelled || mEndVsyncId != INVALID_ID) {
- return;
- }
- mTracingStarted = true;
- Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ mChoreographer.mChoreographer.postCallback(
+ Choreographer.CALLBACK_INPUT, this::beginInternal, null);
+ }
+
+ private void beginInternal() {
+ synchronized (mLock) {
+ if (mCancelled || mEndVsyncId != INVALID_ID) {
+ return;
}
- }, null);
+ mTracingStarted = true;
+ Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ }
}
/**
@@ -464,8 +482,7 @@
if (info.surfaceControlCallbackFired) {
totalFramesCount++;
boolean missedFrame = false;
- if ((info.jankType & PREDICTION_ERROR) != 0
- || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0)) {
+ if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
Log.w(TAG, "Missed App frame:" + info.jankType);
missedAppFramesCount++;
missedFrame = true;
@@ -473,7 +490,8 @@
if ((info.jankType & DISPLAY_HAL) != 0
|| (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
|| (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
- || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0) {
+ || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
+ || (info.jankType & PREDICTION_ERROR) != 0) {
Log.w(TAG, "Missed SF frame:" + info.jankType);
missedSfFramesCount++;
missedFrame = true;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 5a66e9a..3746bfd 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -717,6 +717,7 @@
private final boolean mSurfaceOnly;
private final SurfaceControl mSurfaceControl;
private final @CujType int mCujType;
+ private final boolean mDeferMonitor;
/**
* A builder for building Configuration. {@link #setView(View)} is essential
@@ -733,6 +734,7 @@
private boolean mAttrSurfaceOnly;
private SurfaceControl mAttrSurfaceControl;
private @CujType int mAttrCujType;
+ private boolean mAttrDeferMonitor = true;
/**
* Creates a builder which instruments only surface.
@@ -823,6 +825,16 @@
}
/**
+ * Indicates if the instrument should be deferred to the next frame.
+ * @param defer true if the instrument should be deferred to the next frame.
+ * @return builder
+ */
+ public Builder setDeferMonitorForAnimationStart(boolean defer) {
+ mAttrDeferMonitor = defer;
+ return this;
+ }
+
+ /**
* Builds the {@link Configuration} instance
* @return the instance of {@link Configuration}
* @throws IllegalArgumentException if any invalid attribute is set
@@ -830,12 +842,14 @@
public Configuration build() throws IllegalArgumentException {
return new Configuration(
mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
- mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl);
+ mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl,
+ mAttrDeferMonitor);
}
}
private Configuration(@CujType int cuj, View view, String tag, long timeout,
- boolean surfaceOnly, Context context, SurfaceControl surfaceControl) {
+ boolean surfaceOnly, Context context, SurfaceControl surfaceControl,
+ boolean deferMonitor) {
mCujType = cuj;
mTag = tag;
mTimeout = timeout;
@@ -845,6 +859,7 @@
? context
: (view != null ? view.getContext().getApplicationContext() : null);
mSurfaceControl = surfaceControl;
+ mDeferMonitor = deferMonitor;
validate();
}
@@ -901,6 +916,13 @@
Context getContext() {
return mContext;
}
+
+ /**
+ * @return true if the monitoring should be deferred to the next frame, false otherwise.
+ */
+ public boolean shouldDeferMonitor() {
+ return mDeferMonitor;
+ }
}
/**
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
index c758504..f3af528 100644
--- a/core/java/com/android/internal/util/PerfettoTrigger.java
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -18,6 +18,7 @@
import android.os.SystemClock;
import android.util.Log;
+import android.util.SparseLongArray;
import java.io.IOException;
@@ -28,8 +29,9 @@
public class PerfettoTrigger {
private static final String TAG = "PerfettoTrigger";
private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
- private static final long THROTTLE_MILLIS = 60000;
- private static volatile long sLastTriggerTime = -THROTTLE_MILLIS;
+ private static final long THROTTLE_MILLIS = 300000;
+ private static final SparseLongArray sLastInvocationPerTrigger = new SparseLongArray(100);
+ private static final Object sLock = new Object();
/**
* @param triggerName The name of the trigger. Must match the value defined in the AOT
@@ -38,18 +40,23 @@
public static void trigger(String triggerName) {
// Trace triggering has a non-negligible cost (fork+exec).
// To mitigate potential excessive triggering by the API client we ignore calls that happen
- // too quickl after the most recent trigger.
- long sinceLastTrigger = SystemClock.elapsedRealtime() - sLastTriggerTime;
- if (sinceLastTrigger < THROTTLE_MILLIS) {
- Log.v(TAG, "Not triggering " + triggerName + " - not enough time since last trigger");
- return;
+ // too quickly after the most recent trigger.
+ synchronized (sLock) {
+ long lastTrigger = sLastInvocationPerTrigger.get(triggerName.hashCode());
+ long sinceLastTrigger = SystemClock.elapsedRealtime() - lastTrigger;
+ if (sinceLastTrigger < THROTTLE_MILLIS) {
+ Log.v(TAG, "Not triggering " + triggerName
+ + " - not enough time since last trigger");
+ return;
+ }
+
+ sLastInvocationPerTrigger.put(triggerName.hashCode(), SystemClock.elapsedRealtime());
}
try {
ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
pb.start();
- sLastTriggerTime = SystemClock.elapsedRealtime();
} catch (IOException e) {
Log.w(TAG, "Failed to trigger " + triggerName, e);
}
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index 17b84ff..d9c1e57 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -46,11 +46,21 @@
* Converts a given drawable to a bitmap.
*/
public static Bitmap convertToBitmap(Drawable icon) {
+ return convertToBitmapAtSize(icon, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ }
+
+ /**
+ * Converts a given drawable to a bitmap, with width and height equal to the default icon size.
+ */
+ public static Bitmap convertToBitmapAtUserIconSize(Resources res, Drawable icon) {
+ int size = res.getDimensionPixelSize(R.dimen.user_icon_size);
+ return convertToBitmapAtSize(icon, size, size);
+ }
+
+ private static Bitmap convertToBitmapAtSize(Drawable icon, int width, int height) {
if (icon == null) {
return null;
}
- final int width = icon.getIntrinsicWidth();
- final int height = icon.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
icon.setBounds(0, 0, width, height);
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index cfb2bf9..869da1f 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -20,15 +20,14 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.ContentObserver;
-import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.Display;
import android.view.IWindowManager;
import android.view.Surface;
import android.view.WindowManagerGlobal;
@@ -73,19 +72,16 @@
* otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable.
*/
public static int getRotationLockOrientation(Context context) {
- if (!areAllRotationsAllowed(context)) {
- final Point size = new Point();
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- try {
- final int displayId = context.getDisplayId();
- wm.getInitialDisplaySize(displayId, size);
- return size.x < size.y ?
- Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to get the display size");
- }
+ if (areAllRotationsAllowed(context)) {
+ return Configuration.ORIENTATION_UNDEFINED;
}
- return Configuration.ORIENTATION_UNDEFINED;
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final int rotation =
+ context.getResources().getConfiguration().windowConfiguration.getRotation();
+ final boolean rotated = rotation % 2 != 0;
+ final int w = rotated ? metrics.heightPixels : metrics.widthPixels;
+ final int h = rotated ? metrics.widthPixels : metrics.heightPixels;
+ return w < h ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
}
/**
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 78bb53d..5fa4a65 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -150,6 +150,7 @@
private Icon mShortcutIcon;
private View mAppNameDivider;
private TouchDelegateComposite mTouchDelegate = new TouchDelegateComposite(this);
+ private ArrayList<MessagingGroup> mToRecycle = new ArrayList<>();
public ConversationLayout(@NonNull Context context) {
super(context);
@@ -472,6 +473,12 @@
updateTitleAndNamesDisplay();
updateConversationLayout();
+
+ // Recycle everything at the end of the update, now that we know it's no longer needed.
+ for (MessagingGroup group : mToRecycle) {
+ group.recycle();
+ }
+ mToRecycle.clear();
}
/**
@@ -745,18 +752,18 @@
MessagingGroup group = oldGroups.get(i);
if (!mGroups.contains(group)) {
List<MessagingMessage> messages = group.getMessages();
- Runnable endRunnable = () -> {
- mMessagingLinearLayout.removeTransientView(group);
- group.recycle();
- };
-
boolean wasShown = group.isShown();
mMessagingLinearLayout.removeView(group);
if (wasShown && !MessagingLinearLayout.isGone(group)) {
mMessagingLinearLayout.addTransientView(group, 0);
- group.removeGroupAnimated(endRunnable);
+ group.removeGroupAnimated(() -> {
+ mMessagingLinearLayout.removeTransientView(group);
+ group.recycle();
+ });
} else {
- endRunnable.run();
+ // Defer recycling until after the update is done, since we may still need the
+ // old group around to perform other updates.
+ mToRecycle.add(group);
}
mMessages.removeAll(messages);
mHistoricMessages.removeAll(messages);
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index 80d8bd7..8c61a12 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -1475,6 +1475,7 @@
contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
+ contentContainer.setContentDescription(FloatingToolbar.FLOATING_TOOLBAR_TAG);
contentContainer.setClipToOutline(true);
return contentContainer;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 47cb754..4aa00f6 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -64,7 +64,6 @@
"libbase",
"libcutils",
"libharfbuzz_ng",
- "libhwui",
"liblog",
"libminikin",
"libz",
@@ -266,6 +265,7 @@
"libui",
"libgraphicsenv",
"libgui",
+ "libhwui",
"libmediandk",
"libpermission",
"libsensor",
@@ -344,9 +344,21 @@
],
static_libs: [
"libandroidfw",
- "libcompiler_rt",
- "libutils",
+ "libbinary_parse",
+ "libdng_sdk",
+ "libft2",
"libhostgraphics",
+ "libhwui",
+ "libimage_type_recognition",
+ "libjpeg",
+ "libpiex",
+ "libpng",
+ "libtiff_directory",
+ "libui-types",
+ "libutils",
+ "libwebp-decode",
+ "libwebp-encode",
+ "libwuffs_mirror_release_c",
],
},
linux_glibc: {
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 3e513df..93ba23b 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -14,15 +14,31 @@
* limitations under the License.
*/
-#include "jni.h"
-#include "core_jni_helpers.h"
-
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/graphics/jni_runtime.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/jni_macros.h>
#include <unicode/putil.h>
+#include <unicode/udata.h>
+
#include <clocale>
#include <sstream>
#include <unordered_map>
#include <vector>
+#include "core_jni_helpers.h"
+#include "jni.h"
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#endif
+
+#include <iostream>
+
using namespace std;
/*
@@ -33,6 +49,33 @@
*/
static JavaVM* javaVM;
+static jclass bridge;
+static jclass layoutLog;
+static jmethodID getLogId;
+static jmethodID logMethodId;
+
+extern int register_android_os_Binder(JNIEnv* env);
+extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env);
+
+typedef void (*FreeFunction)(void*);
+
+static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, jclass,
+ jlong freeFunction,
+ jlong ptr) {
+ void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr));
+ FreeFunction nativeFreeFunction =
+ reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction));
+ nativeFreeFunction(nativePtr);
+}
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(NativeAllocationRegistry_Delegate, nativeApplyFreeFunction, "(JJ)V"),
+};
+
+int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry_Delegate", gMethods,
+ NELEM(gMethods));
+}
namespace android {
@@ -47,6 +90,7 @@
extern int register_android_database_SQLiteDebug(JNIEnv* env);
extern int register_android_os_FileObserver(JNIEnv* env);
extern int register_android_os_MessageQueue(JNIEnv* env);
+extern int register_android_os_Parcel(JNIEnv* env);
extern int register_android_os_SystemClock(JNIEnv* env);
extern int register_android_os_SystemProperties(JNIEnv* env);
extern int register_android_os_Trace(JNIEnv* env);
@@ -54,6 +98,11 @@
extern int register_android_util_EventLog(JNIEnv* env);
extern int register_android_util_Log(JNIEnv* env);
extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
+extern int register_android_view_KeyCharacterMap(JNIEnv* env);
+extern int register_android_view_KeyEvent(JNIEnv* env);
+extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
+extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
#define REG_JNI(name) { name }
@@ -78,8 +127,10 @@
{"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
{"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
#ifdef __linux__
+ {"android.os.Binder", REG_JNI(register_android_os_Binder)},
{"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
{"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
+ {"android.os.Parcel", REG_JNI(register_android_os_Parcel)},
#endif
{"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
{"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
@@ -88,11 +139,15 @@
{"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
{"android.util.Log", REG_JNI(register_android_util_Log)},
{"android.util.jar.StrictJarFile", REG_JNI(register_android_util_jar_StrictJarFile)},
+ {"android.view.KeyCharacterMap", REG_JNI(register_android_view_KeyCharacterMap)},
+ {"android.view.KeyEvent", REG_JNI(register_android_view_KeyEvent)},
+ {"android.view.MotionEvent", REG_JNI(register_android_view_MotionEvent)},
+ {"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
+ {"libcore.util.NativeAllocationRegistry_Delegate",
+ REG_JNI(register_libcore_util_NativeAllocationRegistry_Delegate)},
};
-// Vector to store the names of classes that need delegates of their native methods
-static vector<string> classesToDelegate;
static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
const vector<string>& classesToRegister, JNIEnv* env) {
@@ -102,36 +157,17 @@
return -1;
}
}
+
+ if (register_android_graphics_classes(env) < 0) {
+ return -1;
+ }
+
return 0;
}
int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods) {
- string classNameString = string(className);
- if (find(classesToDelegate.begin(), classesToDelegate.end(), classNameString)
- != classesToDelegate.end()) {
- // Register native methods to the delegate class <classNameString>_NativeDelegate
- // by adding _Original to the name of each method.
- replace(classNameString.begin(), classNameString.end(), '$', '_');
- string delegateClassName = classNameString + "_NativeDelegate";
- jclass clazz = env->FindClass(delegateClassName.c_str());
- JNINativeMethod gTypefaceDelegateMethods[numMethods];
- for (int i = 0; i < numMethods; i++) {
- JNINativeMethod gTypefaceMethod = gMethods[i];
- string newName = string(gTypefaceMethod.name) + "_Original";
- gTypefaceDelegateMethods[i].name = strdup(newName.c_str());
- gTypefaceDelegateMethods[i].signature = gTypefaceMethod.signature;
- gTypefaceDelegateMethods[i].fnPtr = gTypefaceMethod.fnPtr;
- }
- int result = env->RegisterNatives(clazz, gTypefaceDelegateMethods, numMethods);
- for (int i = 0; i < numMethods; i++) {
- free((char*)gTypefaceDelegateMethods[i].name);
- }
- return result;
- }
-
- jclass clazz = env->FindClass(className);
- return env->RegisterNatives(clazz, gMethods, numMethods);
+ return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
JNIEnv* AndroidRuntime::getJNIEnv() {
@@ -164,6 +200,125 @@
return result;
}
+void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
+ unsigned int line, const char* message) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jint logPrio = severity;
+ jstring tagString = env->NewStringUTF(tag);
+ jstring messageString = env->NewStringUTF(message);
+
+ jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId);
+
+ env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString);
+
+ env->DeleteLocalRef(tagString);
+ env->DeleteLocalRef(messageString);
+ env->DeleteLocalRef(bridgeLog);
+}
+
+void LayoutlibAborter(const char* abort_message) {
+ // Layoutlib should not call abort() as it would terminate Studio.
+ // Throw an exception back to Java instead.
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jniThrowRuntimeException(env, "The Android framework has encountered a fatal error");
+}
+
+// This method has been copied/adapted from system/core/init/property_service.cpp
+// If the ro.product.cpu.abilist* properties have not been explicitly
+// set, derive them from ro.system.product.cpu.abilist* properties.
+static void property_initialize_ro_cpu_abilist() {
+ const std::string EMPTY = "";
+ const char* kAbilistProp = "ro.product.cpu.abilist";
+ const char* kAbilist32Prop = "ro.product.cpu.abilist32";
+ const char* kAbilist64Prop = "ro.product.cpu.abilist64";
+
+ // If the properties are defined explicitly, just use them.
+ if (base::GetProperty(kAbilistProp, EMPTY) != EMPTY) {
+ return;
+ }
+
+ std::string abilist32_prop_val;
+ std::string abilist64_prop_val;
+ const auto abilist32_prop = "ro.system.product.cpu.abilist32";
+ const auto abilist64_prop = "ro.system.product.cpu.abilist64";
+ abilist32_prop_val = base::GetProperty(abilist32_prop, EMPTY);
+ abilist64_prop_val = base::GetProperty(abilist64_prop, EMPTY);
+
+ // Merge ABI lists for ro.product.cpu.abilist
+ auto abilist_prop_val = abilist64_prop_val;
+ if (abilist32_prop_val != EMPTY) {
+ if (abilist_prop_val != EMPTY) {
+ abilist_prop_val += ",";
+ }
+ abilist_prop_val += abilist32_prop_val;
+ }
+
+ // Set these properties
+ const std::pair<const char*, const std::string&> set_prop_list[] = {
+ {kAbilistProp, abilist_prop_val},
+ {kAbilist32Prop, abilist32_prop_val},
+ {kAbilist64Prop, abilist64_prop_val},
+ };
+ for (const auto& [prop, prop_val] : set_prop_list) {
+ base::SetProperty(prop, prop_val);
+ }
+}
+
+static void* mmapFile(const char* dataFilePath) {
+#ifdef _WIN32
+ // Windows needs file path in wide chars to handle unicode file paths
+ int size = MultiByteToWideChar(CP_UTF8, 0, dataFilePath, -1, NULL, 0);
+ std::vector<wchar_t> wideDataFilePath(size);
+ MultiByteToWideChar(CP_UTF8, 0, dataFilePath, -1, wideDataFilePath.data(), size);
+ HANDLE file =
+ CreateFileW(wideDataFilePath.data(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, nullptr);
+ if ((HANDLE)INVALID_HANDLE_VALUE == file) {
+ return nullptr;
+ }
+
+ struct CloseHandleWrapper {
+ void operator()(HANDLE h) { CloseHandle(h); }
+ };
+ std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
+ CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
+ if (!mmapHandle) {
+ return nullptr;
+ }
+ return MapViewOfFile(mmapHandle.get(), FILE_MAP_READ, 0, 0, 0);
+#else
+ int fd = open(dataFilePath, O_RDONLY);
+ if (fd == -1) {
+ return nullptr;
+ }
+
+ struct stat sb;
+ if (fstat(fd, &sb) == -1) {
+ close(fd);
+ return nullptr;
+ }
+
+ void* addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (addr == MAP_FAILED) {
+ close(fd);
+ return nullptr;
+ }
+
+ close(fd);
+ return addr;
+#endif
+}
+
+static bool init_icu(const char* dataPath) {
+ void* addr = mmapFile(dataPath);
+ UErrorCode err = U_ZERO_ERROR;
+ udata_setCommonData(addr, &err);
+ if (err != U_ZERO_ERROR) {
+ return false;
+ }
+ return true;
+}
+
} // namespace android
using namespace android;
@@ -175,37 +330,82 @@
return JNI_ERR;
}
+ init_android_graphics();
+
// Configuration is stored as java System properties.
// Get a reference to System.getProperty
jclass system = FindClassOrDie(env, "java/lang/System");
jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
- // Get the names of classes that have to delegate their native methods
- auto delegateNativesToNativesString =
- (jstring) env->CallStaticObjectMethod(system,
- getPropertyMethod, env->NewStringUTF("delegate_natives_to_natives"),
- env->NewStringUTF(""));
- classesToDelegate = parseCsv(env, delegateNativesToNativesString);
-
// Get the names of classes that need to register their native methods
auto nativesClassesJString =
- (jstring) env->CallStaticObjectMethod(system,
- getPropertyMethod, env->NewStringUTF("native_classes"),
- env->NewStringUTF(""));
+ (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF("core_native_classes"),
+ env->NewStringUTF(""));
vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
+ jstring registerProperty =
+ (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF(
+ "register_properties_during_load"),
+ env->NewStringUTF(""));
+ const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0);
+ if (strcmp(registerPropertyString, "true") == 0) {
+ // Set the system properties first as they could be used in the static initialization of
+ // other classes
+ if (register_android_os_SystemProperties(env) < 0) {
+ return JNI_ERR;
+ }
+ classesToRegister.erase(find(classesToRegister.begin(), classesToRegister.end(),
+ "android.os.SystemProperties"));
+ bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
+ bridge = MakeGlobalRefOrDie(env, bridge);
+ jmethodID setSystemPropertiesMethod =
+ GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V");
+ env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
+ property_initialize_ro_cpu_abilist();
+ }
+ env->ReleaseStringUTFChars(registerProperty, registerPropertyString);
+
if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
return JNI_ERR;
}
// Set the location of ICU data
- auto stringPath = (jstring) env->CallStaticObjectMethod(system,
- getPropertyMethod, env->NewStringUTF("icu.dir"),
- env->NewStringUTF(""));
+ auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF("icu.data.path"),
+ env->NewStringUTF(""));
const char* path = env->GetStringUTFChars(stringPath, 0);
- u_setDataDirectory(path);
+ bool icuInitialized = init_icu(path);
env->ReleaseStringUTFChars(stringPath, path);
+ if (!icuInitialized) {
+ return JNI_ERR;
+ }
+
+ jstring useJniProperty =
+ (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF("use_bridge_for_logging"),
+ env->NewStringUTF(""));
+ const char* useJniString = env->GetStringUTFChars(useJniProperty, 0);
+ if (strcmp(useJniString, "true") == 0) {
+ layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
+ layoutLog = MakeGlobalRefOrDie(env, layoutLog);
+ logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
+ "(ILjava/lang/String;Ljava/lang/String;)V");
+ if (bridge == nullptr) {
+ bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
+ bridge = MakeGlobalRefOrDie(env, bridge);
+ }
+ getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog",
+ "()Lcom/android/ide/common/rendering/api/ILayoutLog;");
+ android::base::SetLogger(LayoutlibLogger);
+ android::base::SetAborter(LayoutlibAborter);
+ } else {
+ // initialize logging, so ANDROD_LOG_TAGS env variable is respected
+ android::base::InitLogging(nullptr, android::base::StderrLogger);
+ }
+ env->ReleaseStringUTFChars(useJniProperty, useJniString);
// Use English locale for number format to ensure correct parsing of floats when using strtof
setlocale(LC_NUMERIC, "en_US.UTF-8");
@@ -213,3 +413,9 @@
return JNI_VERSION_1_6;
}
+JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) {
+ JNIEnv* env = nullptr;
+ vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ env->DeleteGlobalRef(bridge);
+ env->DeleteGlobalRef(layoutLog);
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 68c8143..3b2a248 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1624,7 +1624,7 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_postNotification"
android:description="@string/permdesc_postNotification"
- android:protectionLevel="dangerous" />
+ android:protectionLevel="dangerous|instant" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- ====================================================================== -->
diff --git a/core/res/res/anim/popup_enter_material.xml b/core/res/res/anim/popup_enter_material.xml
index ef5b7c0..9f771b9 100644
--- a/core/res/res/anim/popup_enter_material.xml
+++ b/core/res/res/anim/popup_enter_material.xml
@@ -22,7 +22,7 @@
android:interpolator="@interpolator/standard"
android:duration="@android:integer/config_activityDefaultDur" />
<translate
- android:fromYDelta="20dp"
+ android:fromYDelta="@android:dimen/popup_enter_animation_from_y_delta"
android:toYDelta="0"
android:interpolator="@interpolator/standard"
android:duration="@android:integer/config_activityDefaultDur" />
diff --git a/core/res/res/anim/popup_exit_material.xml b/core/res/res/anim/popup_exit_material.xml
index 1efa702..2b79ddf 100644
--- a/core/res/res/anim/popup_exit_material.xml
+++ b/core/res/res/anim/popup_exit_material.xml
@@ -23,7 +23,7 @@
android:duration="@android:integer/config_activityShortDur" />
<translate
android:fromYDelta="0"
- android:toYDelta="-10dp"
+ android:toYDelta="@android:dimen/popup_exit_animation_to_y_delta"
android:interpolator="@interpolator/standard_accelerate"
android:duration="@android:integer/config_activityShortDur" />
</set>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 17ec02c..0db08fb 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -45,6 +45,10 @@
<item name="config_pictureInPictureExpandedVerticalWidth"
format="dimension" type="dimen">110dp</item>
+ <!-- The behavior when an activity has not specified a preference to dock big overlays or not.
+ Docking puts the activity side-by-side next to the big overlay windows. -->
+ <bool name="config_dockBigOverlayWindows">true</bool>
+
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">false</bool>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index fca2bd1..7150fca 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3364,7 +3364,7 @@
area for the user and that ideally it should not be covered. Setting this is only
appropriate for UI where the user would likely take action to uncover it.
<p>The system will try to respect this, but when not possible will ignore it.
- See {@link android.view.View#setPreferKeepClear}. -->
+ <p>This is equivalent to {@link android.view.View#setPreferKeepClear}.-->
<attr name="preferKeepClear" format="boolean" />
<!-- <p>Whether or not the auto handwriting initiation is enabled in this View.
@@ -3644,6 +3644,14 @@
<attr name="__removed2" format="boolean" />
<!-- Specifies whether the IME supports showing inline suggestions. -->
<attr name="supportsInlineSuggestions" format="boolean" />
+ <!-- Specifies whether the IME supports showing inline suggestions when touch
+ exploration is enabled. This does nothing if supportsInlineSuggestions is false.
+ The default value is false and most IMEs should not set this
+ to true since the older menu-style Autofill works better with touch exploration.
+ This attribute should be set to true in special situations, such as if this is an
+ accessibility-focused IME which blocks user interaction with the app window while the
+ IME is displayed. -->
+ <attr name="supportsInlineSuggestionsWithTouchExploration" format="boolean" />
<!-- Specifies whether the IME suppresses system spell checker.
The default value is false. If an IME sets this attribute to true,
the system spell checker will be disabled while the IME has an
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 80d84d7..05894d5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3738,6 +3738,10 @@
must be no less than 3 for back compatibility. -->
<integer name="config_pictureInPictureMaxNumberOfActions">3</integer>
+ <!-- The behavior when an activity has not specified a preference to dock big overlays or not.
+ Docking puts the activity side-by-side next to the big overlay windows. -->
+ <bool name="config_dockBigOverlayWindows">false</bool>
+
<!-- Controls the snap mode for the docked stack divider
0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 4874e65..744c3dab 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -991,4 +991,11 @@
<dimen name="secondary_rounded_corner_radius_adjustment">0px</dimen>
<dimen name="secondary_rounded_corner_radius_top_adjustment">0px</dimen>
<dimen name="secondary_rounded_corner_radius_bottom_adjustment">0px</dimen>
+
+ <!-- Default size for user icons (a.k.a. avatar images) -->
+ <dimen name="user_icon_size">190dp</dimen>
+
+ <!-- Dimensions for the translations of the default dialog animation. -->
+ <dimen name="popup_enter_animation_from_y_delta">20dp</dimen>
+ <dimen name="popup_exit_animation_to_y_delta">-10dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9fa5f78..3beb4b2 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3280,6 +3280,7 @@
<public name="knownActivityEmbeddingCerts" />
<public name="intro" />
<public name="enableOnBackInvokedCallback" />
+ <public name="supportsInlineSuggestionsWithTouchExploration" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f16b156..e7eeecc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -418,6 +418,7 @@
<java-symbol type="integer" name="config_pictureInPictureMaxNumberOfActions" />
<java-symbol type="dimen" name="config_pictureInPictureExpandedHorizontalHeight" />
<java-symbol type="dimen" name="config_pictureInPictureExpandedVerticalWidth" />
+ <java-symbol type="bool" name="config_dockBigOverlayWindows" />
<java-symbol type="dimen" name="config_closeToSquareDisplayMaxAspectRatio" />
<java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
<java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />
@@ -544,6 +545,7 @@
<java-symbol type="dimen" name="immersive_mode_cling_width" />
<java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
<java-symbol type="dimen" name="circular_display_mask_thickness" />
+ <java-symbol type="dimen" name="user_icon_size" />
<java-symbol type="string" name="add_account_button_label" />
<java-symbol type="string" name="addToDictionary" />
@@ -2356,6 +2358,10 @@
<java-symbol type="string" name="nas_upgrade_notification_learn_more_action" />
<java-symbol type="string" name="nas_upgrade_notification_learn_more_content" />
<java-symbol type="bool" name="config_settingsHelpLinksEnabled" />
+ <java-symbol type="integer" name="config_activityDefaultDur" />
+ <java-symbol type="integer" name="config_activityShortDur" />
+ <java-symbol type="dimen" name="popup_enter_animation_from_y_delta" />
+ <java-symbol type="dimen" name="popup_exit_animation_to_y_delta" />
<!-- ImfTest -->
<java-symbol type="layout" name="auto_complete_list" />
diff --git a/packages/SystemUI/res/layout/idle_host_view.xml b/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
similarity index 64%
rename from packages/SystemUI/res/layout/idle_host_view.xml
rename to core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
index f407874..3440208 100644
--- a/packages/SystemUI/res/layout/idle_host_view.xml
+++ b/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
+
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,9 +16,13 @@
~ limitations under the License.
-->
-
-<com.android.systemui.idle.IdleHostView
+<input-method
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/idle_host_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+ android:supportsInlineSuggestionsWithTouchExploration="true"
+>
+ <subtype
+ android:label="subtype1"
+ android:imeSubtypeLocale="en_US"
+ android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
index 8718b95..e7d7d640 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -55,11 +55,13 @@
assertThat(imi.supportsSwitchingToNextInputMethod(), is(false));
assertThat(imi.isInlineSuggestionsEnabled(), is(false));
+ assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
final InputMethodInfo clone = cloneViaParcel(imi);
assertThat(clone.supportsSwitchingToNextInputMethod(), is(false));
assertThat(imi.isInlineSuggestionsEnabled(), is(false));
+ assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(false));
}
@Test
@@ -85,6 +87,18 @@
}
@Test
+ public void testInlineSuggestionsEnabledWithTouchExploration() throws Exception {
+ final InputMethodInfo imi =
+ buildInputMethodForTest(R.xml.ime_meta_inline_suggestions_with_touch_exploration);
+
+ assertThat(imi.supportsInlineSuggestionsWithTouchExploration(), is(true));
+
+ final InputMethodInfo clone = cloneViaParcel(imi);
+
+ assertThat(clone.supportsInlineSuggestionsWithTouchExploration(), is(true));
+ }
+
+ @Test
public void testIsVrOnly() throws Exception {
final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_vr_only);
diff --git a/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
new file mode 100644
index 0000000..c6f5924
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/FloatingToolbarUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import static com.android.internal.widget.floatingtoolbar.FloatingToolbar.FLOATING_TOOLBAR_TAG;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.res.Resources;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.R;
+
+final class FloatingToolbarUtils {
+
+ private final UiDevice mDevice;
+
+ FloatingToolbarUtils() {
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ void waitForFloatingToolbarPopup() {
+ mDevice.wait(Until.findObject(By.desc(FLOATING_TOOLBAR_TAG)), 500);
+ }
+
+ void assertFloatingToolbarIsDisplayed() {
+ waitForFloatingToolbarPopup();
+ assertThat(mDevice.hasObject(By.desc(FLOATING_TOOLBAR_TAG))).isTrue();
+ }
+
+ void assertFloatingToolbarContainsItem(String itemLabel) {
+ waitForFloatingToolbarPopup();
+ assertWithMessage("Expected to find item labelled [" + itemLabel + "]")
+ .that(mDevice.hasObject(
+ By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel))))
+ .isTrue();
+ }
+
+ void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
+ waitForFloatingToolbarPopup();
+ assertWithMessage("Expected to not find item labelled [" + itemLabel + "]")
+ .that(mDevice.hasObject(
+ By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(itemLabel))))
+ .isFalse();
+ }
+
+ void assertFloatingToolbarContainsItemAtIndex(String itemLabel, int index) {
+ waitForFloatingToolbarPopup();
+ assertWithMessage("Expected to find item labelled [" + itemLabel + "] at index " + index)
+ .that(mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+ .findObjects(By.clickable(true))
+ .get(index)
+ .getChildren()
+ .get(1)
+ .getText())
+ .isEqualTo(itemLabel);
+ }
+
+ void clickFloatingToolbarItem(String label) {
+ waitForFloatingToolbarPopup();
+ mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+ .findObject(By.text(label))
+ .click();
+ }
+
+ void clickFloatingToolbarOverflowItem(String label) {
+ // TODO: There might be a benefit to combining this with "clickFloatingToolbarItem" method.
+ waitForFloatingToolbarPopup();
+ mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+ .findObject(By.desc(str(R.string.floating_toolbar_open_overflow_description)))
+ .click();
+ mDevice.wait(
+ Until.findObject(By.desc(FLOATING_TOOLBAR_TAG).hasDescendant(By.text(label))),
+ 1000);
+ mDevice.findObject(By.desc(FLOATING_TOOLBAR_TAG))
+ .findObject(By.text(label))
+ .click();
+ }
+
+ private static String str(int id) {
+ return Resources.getSystem().getString(id);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
index 28f9ccc..90844ea 100644
--- a/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
+++ b/core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java
@@ -17,9 +17,6 @@
package android.widget;
import static android.widget.espresso.DragHandleUtils.onHandleView;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupContainsItem;
import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsDisplayed;
import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsNotDisplayed;
@@ -72,6 +69,7 @@
@Rule
public final ActivityTestRule<TextViewActivity> mActivityRule =
new ActivityTestRule<>(TextViewActivity.class);
+ private final FloatingToolbarUtils mToolbar = new FloatingToolbarUtils();
private TextViewActivity getActivity() {
return mActivityRule.getActivity();
@@ -118,22 +116,19 @@
setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarContainsItem(
- getActivity().getString(com.android.internal.R.string.replace));
- sleepForFloatingToolbarPopup();
- clickFloatingToolbarItem(
+ mToolbar.clickFloatingToolbarOverflowItem(
getActivity().getString(com.android.internal.R.string.replace));
assertSuggestionsPopupIsDisplayed();
}
@Test
- public void testInsertionActionMode() {
+ public void testInsertionActionMode() throws Throwable {
final String text = "abc def ghi";
onView(withId(R.id.textview)).perform(click());
onView(withId(R.id.textview)).perform(replaceText(text));
+ Thread.sleep(500);
final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
@@ -141,10 +136,7 @@
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarContainsItem(
- getActivity().getString(com.android.internal.R.string.replace));
- clickFloatingToolbarItem(
+ mToolbar.clickFloatingToolbarItem(
getActivity().getString(com.android.internal.R.string.replace));
assertSuggestionsPopupIsDisplayed();
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 152992c..659cd98 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -16,15 +16,10 @@
package android.widget;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
import static android.widget.espresso.TextViewActions.Handle;
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
@@ -64,10 +59,17 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
import android.text.InputType;
import android.text.Selection;
import android.text.Spannable;
@@ -79,6 +81,7 @@
import android.view.MenuItem;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
@@ -102,6 +105,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Tests the TextView widget from an Activity
@@ -116,11 +120,16 @@
private Activity mActivity;
private Instrumentation mInstrumentation;
+ private UiDevice mDevice;
+ private FloatingToolbarUtils mToolbar;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mDevice = UiDevice.getInstance(mInstrumentation);
+ mDevice.wakeUp();
+ mToolbar = new FloatingToolbarUtils();
TextClassificationManager tcm = mActivity.getSystemService(
TextClassificationManager.class);
tcm.setTextClassifier(TextClassifier.NO_OP);
@@ -132,14 +141,14 @@
final String helloWorld = "Hello world!";
// We use replaceText instead of typeTextIntoFocusedView to input text to avoid
// unintentional interactions with software keyboard.
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).check(matches(withText(helloWorld)));
}
@Test
public void testPositionCursorAtTextAtIndex() {
final String helloWorld = "Hello world!";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world")));
// Delete text at specified index and see if we got the right one.
@@ -152,7 +161,7 @@
// Arabic text. The expected cursorable boundary is
// | \u0623 \u064F | \u067A | \u0633 \u0652 |
final String text = "\u0623\u064F\u067A\u0633\u0652";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
@@ -172,7 +181,7 @@
public void testPositionCursorAtTextAtIndex_devanagari() {
// Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
final String text = "\u0915\u093E";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
@@ -186,7 +195,7 @@
public void testLongPressToSelect() {
final String helloWorld = "Hello Kirk!";
onView(withId(R.id.textview)).perform(click());
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(
longPressOnTextAtIndex(helloWorld.indexOf("Kirk")));
@@ -196,7 +205,7 @@
@Test
public void testLongPressEmptySpace() {
final String helloWorld = "Hello big round sun!";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
// Move cursor somewhere else
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big")));
// Long-press at end of line.
@@ -210,7 +219,7 @@
@Test
public void testLongPressAndDragToSelect() {
final String helloWorld = "Hello little handsome boy!";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(
longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!")));
@@ -220,7 +229,7 @@
@Test
public void testLongPressAndDragToSelect_emoji() {
final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6));
onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02"));
@@ -234,7 +243,7 @@
@Test
public void testDragAndDrop() {
final String text = "abc def ghi.";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e")));
onView(withId(R.id.textview)).perform(
@@ -254,7 +263,7 @@
@Test
public void testDoubleTapToSelect() {
final String helloWorld = "Hello SuetYi!";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(
doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi")));
@@ -265,7 +274,7 @@
@Test
public void testDoubleTapAndDragToSelect() {
final String helloWorld = "Hello young beautiful person!";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(doubleTapAndDragOnText(helloWorld.indexOf("young"),
helloWorld.indexOf(" person!")));
@@ -275,7 +284,7 @@
@Test
public void testDoubleTapAndDragToSelect_multiLine() {
final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(
doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a")));
onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm"));
@@ -284,7 +293,7 @@
@Test
public void testSelectBackwordsByTouch() {
final String helloWorld = "Hello king of the Jungle!";
- onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+ setText(helloWorld);
onView(withId(R.id.textview)).perform(
doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king")));
@@ -294,12 +303,11 @@
@Test
public void testToolbarAppearsAfterSelection() {
final String text = "Toolbar appears after selection.";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(
longPressOnTextAtIndex(text.indexOf("appears")));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
}
@Test
@@ -317,13 +325,12 @@
});
mInstrumentation.waitForIdleSync();
- onView(withId(R.id.textview)).perform(replaceText("test"));
+ setText("test");
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
- clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut));
+ mToolbar.clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut));
onView(withId(R.id.textview)).perform(longClick());
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
}
@Test
@@ -331,8 +338,7 @@
TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
int position = (textLink.getStart() + textLink.getEnd()) / 2;
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
}
@Test
@@ -342,23 +348,20 @@
final int position = (textLink.getStart() + textLink.getEnd()) / 2;
onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
assertTrue(textView.hasSelection());
// toggle
onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
- sleepForFloatingToolbarPopup();
+ mToolbar.waitForFloatingToolbarPopup();
assertFalse(textView.hasSelection());
onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
assertTrue(textView.hasSelection());
// click outside
onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(0));
- sleepForFloatingToolbarPopup();
assertFalse(textView.hasSelection());
}
@@ -372,8 +375,7 @@
});
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
}
@Test
@@ -385,7 +387,7 @@
final TextView textView = mActivity.findViewById(R.id.textview);
textView.setText(text);
textView.setCustomSelectionActionModeCallback(
- new ActionMode.Callback() {
+ new ActionModeCallbackAdapter() {
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu.clear();
@@ -398,29 +400,19 @@
clickedItem[0] = item;
return true;
}
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- return true;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {}
});
});
mInstrumentation.waitForIdleSync();
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
- sleepForFloatingToolbarPopup();
// Change the selection so that the menu items are refreshed.
final TextView textView = mActivity.findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, 0));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
- clickFloatingToolbarItem("Item");
+ mToolbar.clickFloatingToolbarItem("Item");
mInstrumentation.waitForIdleSync();
assertEquals(latestItem[0], clickedItem[0]);
@@ -469,13 +461,11 @@
mActivityRule.runOnUiThread(() -> textView.setFocusableInTouchMode(true));
onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
assertTrue(textView.hasSelection());
mActivityRule.runOnUiThread(() -> textView.clearFocus());
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
assertFalse(textView.hasSelection());
}
@@ -488,14 +478,12 @@
onView(withId(R.id.nonselectable_textview))
.perform(clickOnTextAtIndex(nonselectablePosition));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
assertTrue(nonselectableTextView.hasSelection());
- UiDevice device = UiDevice.getInstance(mInstrumentation);
- device.openNotification();
+ mDevice.openNotification();
Thread.sleep(2000);
- device.pressBack();
+ mDevice.pressBack();
Thread.sleep(2000);
assertFalse(nonselectableTextView.hasSelection());
@@ -528,62 +516,58 @@
}
@Test
- public void testToolbarAndInsertionHandle() {
+ public void testToolbarAndInsertionHandle() throws Throwable {
final String text = "text";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
+ Thread.sleep(500);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.selectAll));
- assertFloatingToolbarDoesNotContainItem(
+ mToolbar.assertFloatingToolbarDoesNotContainItem(
mActivity.getString(com.android.internal.R.string.copy));
- assertFloatingToolbarDoesNotContainItem(
+ mToolbar.assertFloatingToolbarDoesNotContainItem(
mActivity.getString(com.android.internal.R.string.cut));
}
@Test
public void testToolbarAndSelectionHandle() {
final String text = "abcd efg hijk";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.selectAll));
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.copy));
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.cut));
final TextView textView = mActivity.findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
onHandleView(com.android.internal.R.id.selection_end_handle)
.perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarDoesNotContainItem(
+ mToolbar.assertFloatingToolbarDoesNotContainItem(
mActivity.getString(com.android.internal.R.string.selectAll));
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.copy));
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.cut));
}
@Test
public void testInsertionHandle() {
final String text = "abcd efg hijk ";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
@@ -602,7 +586,7 @@
@Test
public void testInsertionHandle_multiLine() {
final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
@@ -640,7 +624,7 @@
final TextView textView = mActivity.findViewById(R.id.textview);
final String text = "hello the world";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
@@ -654,7 +638,7 @@
enableFlagsForInsertionHandleGestures();
final TextView textView = mActivity.findViewById(R.id.textview);
final String text = "hello the world";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
@@ -670,7 +654,7 @@
final TextView textView = mActivity.findViewById(R.id.textview);
final String text = "hello the world";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
@@ -685,7 +669,7 @@
final TextView textView = mActivity.findViewById(R.id.textview);
final String text = "hello the world";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
@@ -698,7 +682,7 @@
@Test
public void testSelectionHandles() {
final String text = "abcd efg hijk lmn";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
@@ -720,7 +704,7 @@
@Test
public void testSelectionHandles_bidi() {
final String text = "abc \u0621\u0622\u0623 def";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
@@ -762,7 +746,7 @@
@Test
public void testSelectionHandles_multiLine() {
final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
final TextView textView = mActivity.findViewById(R.id.textview);
@@ -790,7 +774,7 @@
final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
+ "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
+ "\u0639\u063A\u063B";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634')));
final TextView textView = mActivity.findViewById(R.id.textview);
@@ -817,7 +801,7 @@
@Test
public void testSelectionHandles_doesNotPassAnotherHandle() {
final String text = "abcd efg hijk lmn";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
final TextView textView = mActivity.findViewById(R.id.textview);
@@ -834,7 +818,7 @@
@Test
public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() {
final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
final TextView textView = mActivity.findViewById(R.id.textview);
@@ -851,7 +835,7 @@
@Test
public void testSelectionHandles_snapToWordBoundary() {
final String text = "abcd efg hijk lmn opqr";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
final TextView textView = mActivity.findViewById(R.id.textview);
@@ -904,7 +888,7 @@
@Test
public void testSelectionHandles_snapToWordBoundary_multiLine() {
final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m')));
final TextView textView = mActivity.findViewById(R.id.textview);
@@ -939,7 +923,7 @@
@Test
public void testSelectionHandles_visibleEvenWithEmptyMenu() {
((TextView) mActivity.findViewById(R.id.textview)).setCustomSelectionActionModeCallback(
- new ActionMode.Callback() {
+ new ActionModeCallbackAdapter() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
menu.clear();
@@ -951,17 +935,9 @@
menu.clear();
return true;
}
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {}
});
final String text = "abcd efg hijk lmn";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
@@ -982,7 +958,7 @@
textView.setCustomSelectionActionModeCallback(amCallback);
final String text = "abc def";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
mActivityRule.runOnUiThread(
() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
mInstrumentation.waitForIdleSync();
@@ -991,15 +967,13 @@
// Make sure that "Select All" is included in the selection action mode when the entire text
// is not selected.
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
// Changing the selection range by API should not interrupt the selection action mode.
mActivityRule.runOnUiThread(
() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.selectAll));
// Make sure that "Select All" is no longer included when the entire text is selected by
// API.
@@ -1007,9 +981,8 @@
() -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarDoesNotContainItem(
+ mToolbar.assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarDoesNotContainItem(
mActivity.getString(com.android.internal.R.string.selectAll));
// Make sure that shrinking the selection range to cursor (an empty range) by API
// terminates selection action mode and does not trigger the insertion action mode.
@@ -1020,17 +993,15 @@
// Make sure that user click can trigger the insertion action mode.
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarIsDisplayed();
// Make sure that an existing insertion action mode keeps alive after the insertion point is
// moved by API.
mActivityRule.runOnUiThread(
() -> Selection.setSelection((Spannable) textView.getText(), 0));
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarDoesNotContainItem(
+ mToolbar.assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarDoesNotContainItem(
mActivity.getString(com.android.internal.R.string.copy));
// Make sure that selection action mode is started after selection is created by API when
// insertion action mode is active.
@@ -1038,16 +1009,15 @@
() -> Selection.setSelection((Spannable) textView.getText(), 1, text.length()));
mInstrumentation.waitForIdleSync();
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarIsDisplayed();
- assertFloatingToolbarContainsItem(
+ mToolbar.assertFloatingToolbarIsDisplayed();
+ mToolbar.assertFloatingToolbarContainsItem(
mActivity.getString(com.android.internal.R.string.copy));
}
@Test
public void testTransientState() throws Throwable {
final String text = "abc def";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
final TextView textView = mActivity.findViewById(R.id.textview);
assertFalse(textView.hasTransientState());
@@ -1068,58 +1038,43 @@
@Test
public void testResetMenuItemTitle() throws Throwable {
- mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
+ mActivity.getSystemService(TextClassificationManager.class)
+ .setTextClassifier(TextClassifier.NO_OP);
final TextView textView = mActivity.findViewById(R.id.textview);
final int itemId = 1;
- final String title1 = " AFIGBO";
- final int index = title1.indexOf('I');
- final String title2 = title1.substring(index);
+ final String title1 = "@AFIGBO";
+ final int index = 3;
+ final String title2 = "IGBO";
final String[] title = new String[]{title1};
mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
- new ActionMode.Callback() {
- @Override
- public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
- return true;
- }
-
+ new ActionModeCallbackAdapter() {
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
- menu.removeItem(itemId);
+ menu.clear();
menu.add(Menu.NONE /* group */, itemId, 0 /* order */, title[0]);
return true;
}
-
- @Override
- public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode actionMode) {
- }
}));
mInstrumentation.waitForIdleSync();
- onView(withId(R.id.textview)).perform(replaceText(title1));
+ setText(title1);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(index));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarContainsItem(title1);
+ mToolbar.assertFloatingToolbarContainsItem(title1);
// Change the menu item title.
title[0] = title2;
// Change the selection to invalidate the action mode without restarting it.
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, index));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarContainsItem(title2);
+ mToolbar.assertFloatingToolbarContainsItem(title2);
}
@Test
public void testAssistItemIsAtIndexZero() throws Throwable {
- useSystemDefaultTextClassifier();
+ final SingleActionTextClassifier tc = useSingleActionTextClassifier();
final TextView textView = mActivity.findViewById(R.id.textview);
mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
- new ActionMode.Callback() {
+ new ActionModeCallbackAdapter() {
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
// Create another item at order position 0 to confirm that it will never be
@@ -1127,33 +1082,19 @@
menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test");
return true;
}
-
- @Override
- public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode actionMode) {
- }
}));
mInstrumentation.waitForIdleSync();
final String text = "droid@android.com";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
+ mToolbar.assertFloatingToolbarContainsItemAtIndex(tc.getActionLabel(), 0);
}
@Test
public void testNoAssistItemForPasswordField() throws Throwable {
- useSystemDefaultTextClassifier();
+ final SingleActionTextClassifier tc = useSingleActionTextClassifier();
+
final TextView textView = mActivity.findViewById(R.id.textview);
mActivityRule.runOnUiThread(() -> {
textView.setInputType(
@@ -1162,23 +1103,22 @@
mInstrumentation.waitForIdleSync();
final String password = "afigbo@android.com";
- onView(withId(R.id.textview)).perform(replaceText(password));
+ setText(password);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(password.indexOf('@')));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist);
+ mToolbar.assertFloatingToolbarDoesNotContainItem(tc.getActionLabel());
}
@Test
public void testNoAssistItemForTextFieldWithUnsupportedCharacters() throws Throwable {
- useSystemDefaultTextClassifier();
+ // NOTE: This test addresses a security bug.
+ final SingleActionTextClassifier tc = useSingleActionTextClassifier();
final String text = "\u202Emoc.diordna.com";
final TextView textView = mActivity.findViewById(R.id.textview);
mActivityRule.runOnUiThread(() -> textView.setText(text));
mInstrumentation.waitForIdleSync();
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('.')));
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist);
+ mToolbar.assertFloatingToolbarDoesNotContainItem(tc.getActionLabel());
}
@Test
@@ -1195,10 +1135,9 @@
mInstrumentation.waitForIdleSync();
final String text = "andyroid@android.com";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
- sleepForFloatingToolbarPopup();
- clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy));
+ mToolbar.clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy));
mInstrumentation.waitForIdleSync();
final SelectionEvent lastEvent = selectionEvents.get(selectionEvents.size() - 1);
@@ -1214,9 +1153,8 @@
final String text = "My number is 987654321";
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('9')));
- sleepForFloatingToolbarPopup();
onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
mInstrumentation.waitForIdleSync();
@@ -1247,9 +1185,8 @@
final String text = "My number is 987654321";
// Long press to trigger selection
- onView(withId(R.id.textview)).perform(replaceText(text));
+ setText(text);
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('9')));
- sleepForFloatingToolbarPopup();
// Type over the selection
onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_A));
@@ -1291,16 +1228,14 @@
});
// Long press to trigger selection
- onView(withId(R.id.textview)).perform(replaceText("android.com"));
+ setText("android.com");
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(0));
- sleepForFloatingToolbarPopup();
// Click "Copy" to dismiss the selection.
- clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy));
+ mToolbar.clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy));
// Long press to trigger another selection
- onView(withId(R.id.textview)).perform(replaceText("android@android.com"));
+ setText("android@android.com");
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(0));
- sleepForFloatingToolbarPopup();
// suggestSelection should be called in two different TextClassifier sessions.
assertEquals(2, testableTextClassifiers.size());
@@ -1312,10 +1247,9 @@
public void testPastePlainText_menuAction() {
initializeClipboardWithText(TextStyle.STYLED);
- onView(withId(R.id.textview)).perform(replaceText(""));
+ setText("");
onView(withId(R.id.textview)).perform(longClick());
- sleepForFloatingToolbarPopup();
- clickFloatingToolbarItem(
+ mToolbar.clickFloatingToolbarItem(
mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
mInstrumentation.waitForIdleSync();
@@ -1327,18 +1261,33 @@
public void testPastePlainText_noMenuItemForPlainText() {
initializeClipboardWithText(TextStyle.PLAIN);
- onView(withId(R.id.textview)).perform(replaceText(""));
+ setText("");
onView(withId(R.id.textview)).perform(longClick());
- sleepForFloatingToolbarPopup();
- assertFloatingToolbarDoesNotContainItem(
+ mToolbar.assertFloatingToolbarDoesNotContainItem(
mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
}
+ private void setText(String text) {
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ mDevice.wait(Until.findObject(By.text(text)), 1000);
+ mInstrumentation.waitForIdleSync();
+ }
+
private void useSystemDefaultTextClassifier() {
mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
}
+ private SingleActionTextClassifier useSingleActionTextClassifier() {
+ useSystemDefaultTextClassifier();
+ final TextClassificationManager tcm =
+ mActivity.getSystemService(TextClassificationManager.class);
+ final SingleActionTextClassifier oneActionTC =
+ new SingleActionTextClassifier(mActivity, tcm.getTextClassifier());
+ tcm.setTextClassifier(oneActionTC);
+ return oneActionTC;
+ }
+
private void initializeClipboardWithText(TextStyle textStyle) {
final ClipData clip;
switch (textStyle) {
@@ -1360,7 +1309,7 @@
PLAIN, STYLED
}
- private final class TestableTextClassifier implements TextClassifier {
+ private static final class TestableTextClassifier implements TextClassifier {
final List<SelectionEvent> mSelectionEvents = new ArrayList<>();
final List<TextSelection.Request> mTextSelectionRequests = new ArrayList<>();
@@ -1385,4 +1334,54 @@
return mTextSelectionRequests;
}
}
+
+ private static final class SingleActionTextClassifier implements TextClassifier {
+
+ private final RemoteAction mAction;
+ private final TextClassifier mOriginal;
+ private final TextClassification mClassificationResult;
+
+ SingleActionTextClassifier(Context context, TextClassifier original) {
+ mAction = new RemoteAction(
+ Icon.createWithResource(context, android.R.drawable.btn_star),
+ "assist",
+ "assist",
+ PendingIntent.getActivity(context, 0, new Intent(), FLAG_IMMUTABLE));
+ mClassificationResult = new TextClassification.Builder().addAction(mAction).build();
+ mOriginal = Objects.requireNonNull(original);
+ }
+
+ public String getActionLabel() {
+ return mAction.getTitle().toString();
+ }
+
+ @Override
+ public TextSelection suggestSelection(TextSelection.Request request) {
+ final TextSelection sel = mOriginal.suggestSelection(request);
+ return new TextSelection.Builder(
+ sel.getSelectionStartIndex(), sel.getSelectionEndIndex())
+ .setTextClassification(mClassificationResult)
+ .build();
+ }
+ }
+
+ private static class ActionModeCallbackAdapter implements ActionMode.Callback {
+ @Override
+ public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode actionMode) {}
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
deleted file mode 100644
index 4f95cb8..0000000
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2015 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.widget.espresso;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
-import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
-import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-
-import static com.android.internal.widget.floatingtoolbar.LocalFloatingToolbarPopup.MenuItemRepr;
-
-import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.is;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.test.espresso.NoMatchingRootException;
-import androidx.test.espresso.NoMatchingViewException;
-import androidx.test.espresso.UiController;
-import androidx.test.espresso.ViewAction;
-import androidx.test.espresso.ViewInteraction;
-
-import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeMatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Predicate;
-
-/**
- * Espresso utility methods for the floating toolbar.
- */
-public class FloatingToolbarEspressoUtils {
- private final static Object TAG = FloatingToolbar.FLOATING_TOOLBAR_TAG;
-
- private FloatingToolbarEspressoUtils() {}
-
- private static ViewInteraction onFloatingToolBar() {
- return onView(withTagValue(is(TAG)))
- .inRoot(allOf(
- isPlatformPopup(),
- withDecorView(hasDescendant(withTagValue(is(TAG))))));
- }
-
- /**
- * Creates a {@link ViewInteraction} for the floating bar menu item with the given matcher.
- *
- * @param matcher The matcher for the menu item.
- */
- public static ViewInteraction onFloatingToolBarItem(Matcher<View> matcher) {
- return onView(matcher)
- .inRoot(withDecorView(hasDescendant(withTagValue(is(TAG)))));
- }
-
- /**
- * Asserts that the floating toolbar is displayed on screen.
- *
- * @throws AssertionError if the assertion fails
- */
- public static void assertFloatingToolbarIsDisplayed() {
- onFloatingToolBar().check(matches(isDisplayed()));
- }
-
- /**
- * Asserts that the floating toolbar is not displayed on screen.
- *
- * @throws AssertionError if the assertion fails
- * @deprecated Negative assertions are taking too long to timeout in Espresso.
- */
- @Deprecated
- public static void assertFloatingToolbarIsNotDisplayed() {
- try {
- onFloatingToolBar().check(matches(isDisplayed()));
- } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
- return;
- }
- throw new AssertionError("Floating toolbar is displayed");
- }
-
- private static void toggleOverflow() {
- final int id = com.android.internal.R.id.overflow;
- onView(allOf(withId(id), isDisplayed()))
- .inRoot(withDecorView(hasDescendant(withId(id))))
- .perform(click());
- onView(isRoot()).perform(SLEEP);
- }
-
- public static void sleepForFloatingToolbarPopup() {
- onView(isRoot()).perform(SLEEP);
- }
-
- /**
- * Asserts that the floating toolbar contains the specified item.
- *
- * @param itemLabel label of the item.
- * @throws AssertionError if the assertion fails
- */
- public static void assertFloatingToolbarContainsItem(String itemLabel) {
- try{
- onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
- } catch (AssertionError e) {
- try{
- toggleOverflow();
- } catch (NoMatchingViewException | NoMatchingRootException e2) {
- // No overflow items.
- throw e;
- }
- try{
- onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
- } finally {
- toggleOverflow();
- }
- }
- }
-
- /**
- * Asserts that the floating toolbar contains a specified item at a specified index.
- *
- * @param menuItemId id of the menu item
- * @param index expected index of the menu item in the floating toolbar
- * @throws AssertionError if the assertion fails
- */
- public static void assertFloatingToolbarItemIndex(final int menuItemId, final int index) {
- onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
- private List<Integer> menuItemIds = new ArrayList<>();
-
- @Override
- public boolean matchesSafely(View view) {
- collectMenuItemIds(view);
- return menuItemIds.size() > index && menuItemIds.get(index) == menuItemId;
- }
-
- @Override
- public void describeTo(Description description) {}
-
- private void collectMenuItemIds(View view) {
- if (view.getTag() instanceof MenuItemRepr) {
- menuItemIds.add(((MenuItemRepr) view.getTag()).itemId);
- } else if (view instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- collectMenuItemIds(viewGroup.getChildAt(i));
- }
- }
- }
- }));
- }
-
- /**
- * Asserts that the floating toolbar doesn't contain the specified item.
- *
- * @param itemLabel label of the item.
- * @throws AssertionError if the assertion fails
- */
- public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
- final Predicate<View> hasMenuItemLabel = view ->
- view.getTag() instanceof MenuItemRepr
- && itemLabel.equals(((MenuItemRepr) view.getTag()).title);
- assertFloatingToolbarMenuItem(hasMenuItemLabel, false);
- }
-
- /**
- * Asserts that the floating toolbar does not contain a menu item with the specified id.
- *
- * @param menuItemId id of the menu item
- * @throws AssertionError if the assertion fails
- */
- public static void assertFloatingToolbarDoesNotContainItem(final int menuItemId) {
- final Predicate<View> hasMenuItemId = view ->
- view.getTag() instanceof MenuItemRepr
- && ((MenuItemRepr) view.getTag()).itemId == menuItemId;
- assertFloatingToolbarMenuItem(hasMenuItemId, false);
- }
-
- private static void assertFloatingToolbarMenuItem(
- final Predicate<View> predicate, final boolean positiveAssertion) {
- onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
- @Override
- public boolean matchesSafely(View view) {
- return positiveAssertion == containsItem(view);
- }
-
- @Override
- public void describeTo(Description description) {}
-
- private boolean containsItem(View view) {
- if (predicate.test(view)) {
- return true;
- } else if (view instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) view;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- if (containsItem(viewGroup.getChildAt(i))) {
- return true;
- }
- }
- }
- return false;
- }
- }));
- }
-
- /**
- * Click specified item on the floating tool bar.
- *
- * @param itemLabel label of the item.
- */
- public static void clickFloatingToolbarItem(String itemLabel) {
- try{
- onFloatingToolBarItem(withText(itemLabel)).check(matches(isDisplayed()));
- } catch (AssertionError e) {
- // Try to find the item in the overflow menu.
- toggleOverflow();
- }
- onFloatingToolBarItem(withText(itemLabel)).perform(click());
- }
-
- /**
- * ViewAction to sleep to wait floating toolbar's animation.
- */
- private static final ViewAction SLEEP = new ViewAction() {
- private static final long SLEEP_DURATION = 400;
-
- @Override
- public Matcher<View> getConstraints() {
- return isDisplayed();
- }
-
- @Override
- public String getDescription() {
- return "Sleep " + SLEEP_DURATION + " ms.";
- }
-
- @Override
- public void perform(UiController uiController, View view) {
- uiController.loopMainThreadForAtLeast(SLEEP_DURATION);
- }
- };
-}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 52cb9f3..a52d2e8 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -86,11 +86,13 @@
mController.attachToDisplayArea(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
null /* options */);
- assertThat(mController.mAttachedToDisplayArea).isTrue();
+ assertThat(mController.mAttachedToDisplayArea).isEqualTo(
+ WindowContextController.AttachStatus.STATUS_ATTACHED);
mController.detachIfNeeded();
- assertThat(mController.mAttachedToDisplayArea).isFalse();
+ assertThat(mController.mAttachedToDisplayArea).isEqualTo(
+ WindowContextController.AttachStatus.STATUS_DETACHED);
}
@Test(expected = IllegalStateException.class)
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index cd42a34..23ec3ea 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -2020,6 +2020,7 @@
.check(matches(isDisplayed()));
}
+ @Ignore // b/220067877
@Test
public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
// enable the work tab feature flag
@@ -2304,6 +2305,7 @@
assertThat(logger.numCalls(), is(5));
}
+ @Ignore // b/220067877
@Test
public void testCopyTextToClipboardLogging() throws Exception {
Intent sendIntent = createSendTextIntent();
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index a2bc77a..3a27225 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -29,6 +29,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
/**
* Unit test for {@link AndroidFuture}.
@@ -154,4 +155,35 @@
expectThrows(ExecutionException.class, future1::get);
assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
}
+
+ @Test
+ public void testThenCombine() throws Exception {
+ String nearFutureString = "near future comes";
+ AndroidFuture<String> nearFuture = AndroidFuture.supply(() -> nearFutureString);
+ String farFutureString = " before far future.";
+ AndroidFuture<String> farFuture = AndroidFuture.supply(() -> farFutureString);
+ AndroidFuture<String> combinedFuture =
+ nearFuture.thenCombine(farFuture, ((s1, s2) -> s1 + s2));
+
+ assertThat(combinedFuture.get()).isEqualTo(nearFutureString + farFutureString);
+ }
+
+ @Test
+ public void testThenCombine_functionThrowingException() throws Exception {
+ String nearFutureString = "near future comes";
+ AndroidFuture<String> nearFuture = AndroidFuture.supply(() -> nearFutureString);
+ String farFutureString = " before far future.";
+ AndroidFuture<String> farFuture = AndroidFuture.supply(() -> farFutureString);
+ UnsupportedOperationException exception = new UnsupportedOperationException(
+ "Unsupported operation exception thrown!");
+ BiFunction<String, String, String> throwingFunction = (s1, s2) -> {
+ throw exception;
+ };
+ AndroidFuture<String> combinedFuture = nearFuture.thenCombine(farFuture, throwingFunction);
+
+ ExecutionException thrown = expectThrows(ExecutionException.class,
+ () -> combinedFuture.get());
+
+ assertThat(thrown.getCause()).isSameInstanceAs(exception);
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ac5daf0..2d2c03b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -481,6 +481,9 @@
<permission name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
<permission name="android.permission.NEARBY_WIFI_DEVICES" />
<permission name="android.permission.OVERRIDE_WIFI_CONFIG" />
+ <!-- Permission needed for CTS test - ConcurrencyTest#testP2pExternalApprover
+ P2P external approver API sets require MANAGE_WIFI_AUTO_JOIN permission. -->
+ <permission name="android.permission.MANAGE_WIFI_AUTO_JOIN" />
<!-- Permission required for CTS test CarrierMessagingServiceWrapperTest -->
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<!-- Permission required for CTS test - MusicRecognitionManagerTest -->
@@ -541,6 +544,8 @@
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<permission name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
+ <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+ <permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 6d691c1..3f7f088 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -34,6 +34,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* Result of text shaping of the single paragraph string.
@@ -56,18 +57,22 @@
public class MeasuredText {
private static final String TAG = "MeasuredText";
- private long mNativePtr;
- private boolean mComputeHyphenation;
- private boolean mComputeLayout;
- private @NonNull char[] mChars;
+ private final long mNativePtr;
+ private final boolean mComputeHyphenation;
+ private final boolean mComputeLayout;
+ @NonNull private final char[] mChars;
+ private final int mTop;
+ private final int mBottom;
// Use builder instead.
private MeasuredText(long ptr, @NonNull char[] chars, boolean computeHyphenation,
- boolean computeLayout) {
+ boolean computeLayout, int top, int bottom) {
mNativePtr = ptr;
mChars = chars;
mComputeHyphenation = computeHyphenation;
mComputeLayout = computeLayout;
+ mTop = top;
+ mBottom = bottom;
}
/**
@@ -124,6 +129,30 @@
}
/**
+ * Retrieves the font metrics of the given range
+ *
+ * @param start an inclusive start index of the range
+ * @param end an exclusive end index of the range
+ * @param outMetrics an output metrics object
+ */
+ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+ @NonNull Paint.FontMetricsInt outMetrics) {
+ Preconditions.checkArgument(0 <= start && start <= mChars.length,
+ "start(%d) must be 0 <= start <= %d", start, mChars.length);
+ Preconditions.checkArgument(0 <= end && end <= mChars.length,
+ "end(%d) must be 0 <= end <= %d", end, mChars.length);
+ Preconditions.checkArgument(start <= end,
+ "start(%d) is larger than end(%d)", start, end);
+ Objects.requireNonNull(outMetrics);
+
+ long packed = nGetExtent(mNativePtr, mChars, start, end);
+ outMetrics.ascent = (int) (packed >> 32);
+ outMetrics.descent = (int) (packed & 0xFFFFFFFF);
+ outMetrics.top = Math.min(outMetrics.ascent, mTop);
+ outMetrics.bottom = Math.max(outMetrics.descent, mBottom);
+ }
+
+ /**
* Returns the width of the character at the given offset.
*
* @param offset an offset of the character.
@@ -160,6 +189,8 @@
@CriticalNative
private static native float nGetCharWidthAt(long nativePtr, int offset);
+ private static native long nGetExtent(long nativePtr, char[] buf, int start, int end);
+
/**
* Helper class for creating a {@link MeasuredText}.
* <p>
@@ -189,6 +220,9 @@
private boolean mFastHyphenation = false;
private int mCurrentOffset = 0;
private @Nullable MeasuredText mHintMt = null;
+ private int mTop = 0;
+ private int mBottom = 0;
+ private Paint.FontMetricsInt mCachedMetrics = new Paint.FontMetricsInt();
/**
* Construct a builder.
@@ -269,6 +303,10 @@
nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
mCurrentOffset, end, isRtl);
mCurrentOffset = end;
+
+ paint.getFontMetricsInt(mCachedMetrics);
+ mTop = Math.min(mTop, mCachedMetrics.top);
+ mBottom = Math.max(mBottom, mCachedMetrics.bottom);
return this;
}
@@ -419,7 +457,7 @@
long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation,
mComputeLayout, mFastHyphenation);
final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation,
- mComputeLayout);
+ mComputeLayout, mTop, mBottom);
sRegistry.registerNativeAllocation(res, ptr);
return res;
} finally {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index e50ad38..9b41468 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -52,9 +52,6 @@
*/
public class BackAnimationController implements RemoteCallable<BackAnimationController> {
- private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
- public static final boolean IS_ENABLED = SystemProperties
- .getInt(BACK_PREDICTABILITY_PROP, 1) > 0;
private static final String BACK_PREDICTABILITY_PROGRESS_THRESHOLD_PROP =
"persist.debug.back_predictability_progress_threshold";
private static final int PROGRESS_THRESHOLD = SystemProperties
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 6ffcf10..241f1a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -423,7 +423,6 @@
WindowContainerTransaction t) {
// This is triggered right before the rotation is applied
if (fromRotation != toRotation) {
- mBubblePositioner.setRotation(toRotation);
if (mStackView != null) {
// Layout listener set on stackView will update the positioner
// once the rotation is applied
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 127d5a8..75b19fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
@@ -112,10 +113,6 @@
update();
}
- public void setRotation(int rotation) {
- mRotation = rotation;
- }
-
/**
* Available space and inset information. Call this when config changes
* occur or when added to a window.
@@ -273,7 +270,8 @@
/** @return whether the device is in landscape orientation. */
public boolean isLandscape() {
- return mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
+ return mContext.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
}
/** @return whether the screen is considered large. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 9219baa..c6a68dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1237,7 +1237,7 @@
b.getExpandedView().updateFontSize();
}
}
- if (mBubbleOverflow != null) {
+ if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
mBubbleOverflow.getExpandedView().updateFontSize();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 656dae3..ee4d5ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -40,6 +40,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import java.lang.ref.WeakReference;
@@ -105,9 +106,8 @@
private CompatUICallback mCallback;
- // Only show once automatically in the process life.
- private boolean mHasShownSizeCompatHint;
- private boolean mHasShownCameraCompatHint;
+ // Only show each hint once automatically in the process life.
+ private final CompatUIHintsState mCompatUIHintsState;
// Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
// be shown.
@@ -127,6 +127,7 @@
mMainExecutor = mainExecutor;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
+ mCompatUIHintsState = new CompatUIHintsState();
}
/** Returns implementation of {@link CompatUI}. */
@@ -259,19 +260,9 @@
@VisibleForTesting
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
- final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
+ return new CompatUIWindowManager(context,
taskInfo, mSyncQueue, mCallback, taskListener,
- mDisplayController.getDisplayLayout(taskInfo.displayId), mHasShownSizeCompatHint,
- mHasShownCameraCompatHint);
- // TODO(b/218304113): updates values only if hints are actually shown to the user.
- // Only show hints for the first time.
- if (taskInfo.topActivityInSizeCompat) {
- mHasShownSizeCompatHint = true;
- }
- if (taskInfo.hasCameraCompatControl()) {
- mHasShownCameraCompatHint = true;
- }
- return compatUIWindowManager;
+ mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState);
}
private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
@@ -292,9 +283,8 @@
if (context == null) {
return;
}
- LetterboxEduWindowManager newLayout = new LetterboxEduWindowManager(context, taskInfo,
- mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
- this::onLetterboxEduDismissed);
+ LetterboxEduWindowManager newLayout = createLetterboxEduWindowManager(context, taskInfo,
+ taskListener);
if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
// The new layout is eligible to be shown, make it the active layout.
if (mActiveLetterboxEduLayout != null) {
@@ -307,6 +297,14 @@
}
}
+ @VisibleForTesting
+ LetterboxEduWindowManager createLetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener taskListener) {
+ return new LetterboxEduWindowManager(context, taskInfo,
+ mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+ this::onLetterboxEduDismissed);
+ }
+
private void onLetterboxEduDismissed() {
mActiveLetterboxEduLayout = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 7c6780a..bce3ec4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -59,9 +59,7 @@
int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
@VisibleForTesting
- boolean mShouldShowSizeCompatHint;
- @VisibleForTesting
- boolean mShouldShowCameraCompatHint;
+ CompatUIHintsState mCompatUIHintsState;
@Nullable
@VisibleForTesting
@@ -70,13 +68,12 @@
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
- boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) {
+ CompatUIHintsState compatUIHintsState) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
mHasSizeCompat = taskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.cameraCompatControlState;
- mShouldShowSizeCompatHint = !hasShownSizeCompatHint;
- mShouldShowCameraCompatHint = !hasShownCameraCompatHint;
+ mCompatUIHintsState = compatUIHintsState;
}
@Override
@@ -212,18 +209,18 @@
}
// Size Compat mode restart button.
mLayout.setRestartButtonVisibility(mHasSizeCompat);
- if (mHasSizeCompat && mShouldShowSizeCompatHint) {
+ // Only show by default for the first time.
+ if (mHasSizeCompat && !mCompatUIHintsState.mHasShownSizeCompatHint) {
mLayout.setSizeCompatHintVisibility(/* show= */ true);
- // Only show by default for the first time.
- mShouldShowSizeCompatHint = false;
+ mCompatUIHintsState.mHasShownSizeCompatHint = true;
}
// Camera control for stretched issues.
mLayout.setCameraControlVisibility(shouldShowCameraControl());
- if (shouldShowCameraControl() && mShouldShowCameraCompatHint) {
+ // Only show by default for the first time.
+ if (shouldShowCameraControl() && !mCompatUIHintsState.mHasShownCameraCompatHint) {
mLayout.setCameraCompatHintVisibility(/* show= */ true);
- // Only show by default for the first time.
- mShouldShowCameraCompatHint = false;
+ mCompatUIHintsState.mHasShownCameraCompatHint = true;
}
if (shouldShowCameraControl()) {
mLayout.updateCameraTreatmentButton(mCameraCompatControlState);
@@ -234,4 +231,15 @@
return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
&& mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
}
+
+ /**
+ * A class holding the state of the compat UI hints, which is shared between all compat UI
+ * window managers.
+ */
+ static class CompatUIHintsState {
+ @VisibleForTesting
+ boolean mHasShownSizeCompatHint;
+ @VisibleForTesting
+ boolean mHasShownCameraCompatHint;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 5679bc4..face243 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -22,6 +22,10 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
+
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.content.Context;
@@ -110,7 +114,8 @@
* @param canShow whether the layout is allowed to be shown by the parent controller.
* @return whether the layout is eligible to be shown.
*/
- protected boolean createLayout(boolean canShow) {
+ @VisibleForTesting(visibility = PROTECTED)
+ public boolean createLayout(boolean canShow) {
if (!eligibleToShowLayout()) {
return false;
}
@@ -184,7 +189,8 @@
* @param canShow whether the layout is allowed to be shown by the parent controller.
* @return whether the layout is eligible to be shown.
*/
- protected boolean updateCompatInfo(TaskInfo taskInfo,
+ @VisibleForTesting(visibility = PROTECTED)
+ public boolean updateCompatInfo(TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
final Configuration prevTaskConfig = mTaskConfig;
final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
@@ -201,7 +207,8 @@
View layout = getLayout();
if (layout == null || prevTaskListener != taskListener) {
- // TaskListener changed, recreate the layout for new surface parent.
+ // Layout wasn't created yet or TaskListener changed, recreate the layout for new
+ // surface parent.
release();
return createLayout(canShow);
}
@@ -227,7 +234,8 @@
*
* @param canShow whether the layout is allowed to be shown by the parent controller.
*/
- void updateVisibility(boolean canShow) {
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateVisibility(boolean canShow) {
View layout = getLayout();
if (layout == null) {
// Layout may not have been created because it was hidden previously.
@@ -242,7 +250,8 @@
}
/** Called when display layout changed. */
- void updateDisplayLayout(DisplayLayout displayLayout) {
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateDisplayLayout(DisplayLayout displayLayout) {
final Rect prevStableBounds = mStableBounds;
final Rect curStableBounds = new Rect();
displayLayout.getStableBounds(curStableBounds);
@@ -255,7 +264,7 @@
}
/** Called when the surface is ready to be placed under the task surface. */
- @VisibleForTesting
+ @VisibleForTesting(visibility = PRIVATE)
void attachToParentSurface(SurfaceControl.Builder b) {
mTaskListener.attachChildSurfaceToTask(mTaskId, b);
}
@@ -347,8 +356,9 @@
return result;
}
- @VisibleForTesting
- SurfaceControlViewHost createSurfaceViewHost() {
+ /** Creates a {@link SurfaceControlViewHost} for this window manager. */
+ @VisibleForTesting(visibility = PRIVATE)
+ public SurfaceControlViewHost createSurfaceViewHost() {
return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
index 3810eca..03986ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
@@ -39,7 +39,6 @@
/**
* Controls the enter/exit animations of the letterbox education.
*/
-// TODO(b/215316431): Add tests
class LetterboxEduAnimationController {
private static final String TAG = "LetterboxEduAnimation";
@@ -99,15 +98,10 @@
/**
* Starts both the background dim fade-out animation and the dialog exit animation.
*/
- void startExitAnimation(@Nullable LetterboxEduDialogLayout layout, Runnable endCallback) {
+ void startExitAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
// Cancel any previous animation if it's still running.
cancelAnimation();
- if (layout == null) {
- endCallback.run();
- return;
- }
-
final View dialogContainer = layout.getDialogContainer();
mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
if (mDialogAnimation == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
index fc6fd3f..02197f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
@@ -29,18 +29,28 @@
/**
* Custom layout for Letterbox Education dialog action.
*/
-// TODO(b/215316431): Add tests
class LetterboxEduDialogActionLayout extends FrameLayout {
- LetterboxEduDialogActionLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
+ public LetterboxEduDialogActionLayout(Context context) {
+ this(context, null);
+ }
+
+ public LetterboxEduDialogActionLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LetterboxEduDialogActionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LetterboxEduDialogActionLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
TypedArray styledAttributes =
context.getTheme().obtainStyledAttributes(
- attrs,
- R.styleable.LetterboxEduDialogActionLayout,
- /* defStyleAttr= */ 0,
- /* defStyleRes= */ 0);
+ attrs, R.styleable.LetterboxEduDialogActionLayout, defStyleAttr,
+ defStyleRes);
int iconId = styledAttributes.getResourceId(
R.styleable.LetterboxEduDialogActionLayout_icon, 0);
String text = styledAttributes.getString(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index bb6fe98..2da6a6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.compatui.letterboxedu;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
@@ -31,7 +32,6 @@
* <p>This layout should fill the entire task and the background around the dialog acts as the
* background dim which dismisses the dialog when clicked.
*/
-// TODO(b/215316431): Add tests
class LetterboxEduDialogLayout extends ConstraintLayout {
// The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
@@ -68,16 +68,16 @@
/**
* Register a callback for the dismiss button and background dim.
*
- * @param callback The callback to register
+ * @param callback The callback to register or null if all on click listeners should be removed.
*/
- void setDismissOnClickListener(Runnable callback) {
- findViewById(R.id.letterbox_education_dialog_dismiss_button).setOnClickListener(
- view -> callback.run());
+ void setDismissOnClickListener(@Nullable Runnable callback) {
+ final OnClickListener listener = callback == null ? null : view -> callback.run();
+ findViewById(R.id.letterbox_education_dialog_dismiss_button).setOnClickListener(listener);
// Clicks on the background dim should also dismiss the dialog.
- setOnClickListener(view -> callback.run());
+ setOnClickListener(listener);
// We add a no-op on-click listener to the dialog container so that clicks on it won't
// propagate to the listener of the layout (which represents the background dim).
- mDialogContainer.setOnClickListener(view -> {});
+ mDialogContainer.setOnClickListener(callback == null ? null : view -> {});
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index bb4d427..30b9f08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -29,6 +29,7 @@
import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
@@ -38,7 +39,6 @@
/**
* Window manager for the Letterbox Education.
*/
-// TODO(b/215316431): Add tests
public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
/**
@@ -51,7 +51,8 @@
* The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
* Education for specific packages and which user has seen the full dialog for any package.
*/
- private static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME =
+ @VisibleForTesting
+ static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME =
"has_seen_letterbox_education";
/**
@@ -65,19 +66,37 @@
private boolean mEligibleForLetterboxEducation;
@Nullable
- private LetterboxEduDialogLayout mLayout;
+ @VisibleForTesting
+ LetterboxEduDialogLayout mLayout;
private final Runnable mOnDismissCallback;
+ /**
+ * The vertical margin between the dialog container and the task stable bounds (excluding
+ * insets).
+ */
+ private final int mDialogVerticalMargin;
+
public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Runnable onDismissCallback) {
+ this(context, taskInfo, syncQueue, taskListener, displayLayout, onDismissCallback,
+ new LetterboxEduAnimationController(context));
+ }
+
+ @VisibleForTesting
+ LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+ SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+ DisplayLayout displayLayout, Runnable onDismissCallback,
+ LetterboxEduAnimationController animationController) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mOnDismissCallback = onDismissCallback;
+ mAnimationController = animationController;
mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
- mAnimationController = new LetterboxEduAnimationController(context);
mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
Context.MODE_PRIVATE);
+ mDialogVerticalMargin = (int) mContext.getResources().getDimension(
+ R.dimen.letterbox_education_dialog_margin);
}
@Override
@@ -124,13 +143,12 @@
}
final View dialogContainer = mLayout.getDialogContainer();
MarginLayoutParams marginParams = (MarginLayoutParams) dialogContainer.getLayoutParams();
- int verticalMargin = (int) mContext.getResources().getDimension(
- R.dimen.letterbox_education_dialog_margin);
final Rect taskBounds = getTaskBounds();
final Rect taskStableBounds = getTaskStableBounds();
- marginParams.topMargin = taskStableBounds.top - taskBounds.top + verticalMargin;
- marginParams.bottomMargin = taskBounds.bottom - taskStableBounds.bottom + verticalMargin;
+ marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
+ marginParams.bottomMargin =
+ taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
dialogContainer.setLayoutParams(marginParams);
}
@@ -147,6 +165,10 @@
}
private void onDismiss() {
+ if (mLayout == null) {
+ return;
+ }
+ mLayout.setDismissOnClickListener(null);
mAnimationController.startExitAnimation(mLayout, () -> {
release();
mOnDismissCallback.run();
@@ -204,7 +226,8 @@
return String.valueOf(mContext.getUserId());
}
- private boolean isTaskbarEduShowing() {
+ @VisibleForTesting
+ boolean isTaskbarEduShowing() {
return Settings.Secure.getInt(mContext.getContentResolver(),
LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2e54c79..c94f3d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -168,8 +168,8 @@
@WMSingleton
@Provides
- static DragAndDrop provideDragAndDrop(DragAndDropController dragAndDropController) {
- return dragAndDropController.asDragAndDrop();
+ static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) {
+ return Optional.of(dragAndDropController.asDragAndDrop());
}
@WMSingleton
@@ -184,8 +184,8 @@
@WMSingleton
@Provides
- static CompatUI provideCompatUI(CompatUIController compatUIController) {
- return compatUIController.asCompatUI();
+ static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
+ return Optional.of(compatUIController.asCompatUI());
}
@WMSingleton
@@ -699,10 +699,7 @@
Context context,
@ShellMainThread ShellExecutor shellExecutor
) {
- if (BackAnimationController.IS_ENABLED) {
- return Optional.of(
- new BackAnimationController(shellExecutor, context));
- }
- return Optional.empty();
+ return Optional.of(
+ new BackAnimationController(shellExecutor, context));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 5c205f9..8f9636c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -27,7 +27,10 @@
import android.os.Looper;
import android.os.Trace;
+import androidx.annotation.Nullable;
+
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
@@ -35,7 +38,6 @@
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
-import com.android.wm.shell.R;
import dagger.Module;
import dagger.Provides;
@@ -53,7 +55,7 @@
/**
* Returns whether to enable a separate shell thread for the shell features.
*/
- private static boolean enableShellMainThread(Context context) {
+ public static boolean enableShellMainThread(Context context) {
return context.getResources().getBoolean(R.bool.config_enableShellMainThread);
}
@@ -85,23 +87,41 @@
}
/**
+ * Creates a shell main thread to be injected into the shell components. This does not provide
+ * the {@param HandleThread}, but is used to create the thread prior to initializing the
+ * WM component, and is explicitly bound.
+ *
+ * See {@link com.android.systemui.SystemUIFactory#init(Context, boolean)}.
+ */
+ public static HandlerThread createShellMainThread() {
+ HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY);
+ return mainThread;
+ }
+
+ /**
* Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe
* multiple types of messages, etc.)
+ *
+ * @param mainThread If non-null, this thread is expected to be started already
*/
@WMSingleton
@Provides
@ShellMainThread
public static Handler provideShellMainHandler(Context context,
+ @Nullable @ShellMainThread HandlerThread mainThread,
@ExternalMainThread Handler sysuiMainHandler) {
if (enableShellMainThread(context)) {
- HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY);
- mainThread.start();
- if (Build.IS_DEBUGGABLE) {
- mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
- mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
- MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
- }
- return Handler.createAsync(mainThread.getLooper());
+ if (mainThread == null) {
+ // If this thread wasn't pre-emptively started, then create and start it
+ mainThread = createShellMainThread();
+ mainThread.start();
+ }
+ if (Build.IS_DEBUGGABLE) {
+ mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+ mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+ MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+ }
+ return Handler.createAsync(mainThread.getLooper());
}
return sysuiMainHandler;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index e616172..77fd228 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -620,7 +620,7 @@
setCurrentValue(bounds);
final Rect insets = computeInsets(fraction);
final float degree, x, y;
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (Transitions.SHELL_TRANSITIONS_ROTATION) {
if (rotationDelta == ROTATION_90) {
degree = 90 * (1 - fraction);
x = fraction * (end.left - start.left)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 67b3983..1eb9501 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -310,6 +310,10 @@
return mPipTransitionState.isInPip();
}
+ private boolean isLaunchIntoPipTask() {
+ return mPictureInPictureParams != null && mPictureInPictureParams.isLaunchIntoPip();
+ }
+
/**
* Returns whether the entry animation is waiting to be started.
*/
@@ -397,6 +401,10 @@
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (isLaunchIntoPipTask()) {
+ exitLaunchIntoPipTask(wct);
+ return;
+ }
if (ENABLE_SHELL_TRANSITIONS) {
if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
@@ -468,6 +476,14 @@
});
}
+ private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
+ wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
+ mTaskOrganizer.applyTransaction(wct);
+
+ // Remove the PiP with fade-out animation right after the host Task is brought to front.
+ removePip();
+ }
+
private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
// Reset the final windowing mode.
wct.setWindowingMode(mToken, getOutPipWindowingMode());
@@ -563,6 +579,13 @@
Log.d(TAG, "Alpha animation is expired. Use bounds animation.");
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
}
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // For Shell transition, we will animate the window in PipTransition#startAnimation
+ // instead of #onTaskAppeared.
+ return;
+ }
+
if (mWaitForFixedRotation) {
onTaskAppearedWithFixedRotation();
return;
@@ -572,15 +595,6 @@
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- mPipMenuController.attach(mLeash);
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- }
- return;
- }
-
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
mPipMenuController.attach(mLeash);
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
@@ -729,7 +743,7 @@
}
/**
- * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
+ * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int, boolean)}.
* Meanwhile this callback is invoked whenever the task is removed. For instance:
* - as a result of removeRootTasksInWindowingModes from WM
* - activity itself is died
@@ -813,6 +827,16 @@
mNextRotation = newRotation;
mWaitForFixedRotation = true;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // The fixed rotation will also be included in the transition info. However, if it is
+ // not a PIP transition (such as open another app to different orientation),
+ // PIP transition handler may not be aware of the fixed rotation start.
+ // Notify the PIP transition handler so that it can fade out the PIP window early for
+ // fixed transition of other windows.
+ mPipTransitionController.onFixedRotationStarted();
+ return;
+ }
+
if (mPipTransitionState.isInPip()) {
// Fade out the existing PiP to avoid jump cut during seamless rotation.
fadeExistingPip(false /* show */);
@@ -824,6 +848,10 @@
if (!mWaitForFixedRotation) {
return;
}
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ clearWaitForFixedRotation();
+ return;
+ }
if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) {
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
onEndOfSwipePipToHomeTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 3e5d5f6..60aac68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
@@ -32,9 +33,11 @@
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
@@ -47,6 +50,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.Log;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -86,6 +90,16 @@
/** The Task window that is currently in PIP windowing mode. */
@Nullable
private WindowContainerToken mCurrentPipTaskToken;
+ /** Whether display is in fixed rotation. */
+ private boolean mInFixedRotation;
+ /**
+ * The rotation that the display will apply after expanding PiP to fullscreen. This is only
+ * meaningful if {@link #mInFixedRotation} is true.
+ */
+ @Surface.Rotation
+ private int mFixedRotation;
+ /** Whether the PIP window has fade out for fixed rotation. */
+ private boolean mHasFadeOut;
public PipTransition(Context context,
PipBoundsState pipBoundsState,
@@ -136,35 +150,41 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ final TransitionInfo.Change currentPipChange = findCurrentPipChange(info);
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ mInFixedRotation = fixedRotationChange != null;
+ mFixedRotation = mInFixedRotation
+ ? fixedRotationChange.getEndFixedRotation()
+ : ROTATION_UNDEFINED;
+
// Exiting PIP.
final int type = info.getType();
if (transition.equals(mExitTransition)) {
mExitDestinationBounds.setEmpty();
mExitTransition = null;
-
+ mHasFadeOut = false;
if (mFinishCallback != null) {
mFinishCallback.onTransitionFinished(null, null);
mFinishCallback = null;
throw new RuntimeException("Previous callback not called, aborting exit PIP.");
}
- final TransitionInfo.Change exitPipChange = findCurrentPipChange(info);
- if (exitPipChange == null) {
+ if (currentPipChange == null) {
throw new RuntimeException("Cannot find the pip window for exit-pip transition.");
}
switch (type) {
case TRANSIT_EXIT_PIP:
startExitAnimation(info, startTransaction, finishTransaction, finishCallback,
- exitPipChange);
+ currentPipChange);
break;
case TRANSIT_EXIT_PIP_TO_SPLIT:
startExitToSplitAnimation(info, startTransaction, finishTransaction,
- finishCallback, exitPipChange);
+ finishCallback, currentPipChange);
break;
case TRANSIT_REMOVE_PIP:
removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
- exitPipChange);
+ currentPipChange);
break;
default:
throw new IllegalStateException("mExitTransition with unexpected transit type="
@@ -177,7 +197,6 @@
// The previous PIP Task is no longer in PIP, but this is not an exit transition (This can
// happen when a new activity requests enter PIP). In this case, we just show this Task in
// its end state, and play other animation as normal.
- final TransitionInfo.Change currentPipChange = findCurrentPipChange(info);
if (currentPipChange != null
&& currentPipChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) {
resetPrevPip(currentPipChange, startTransaction);
@@ -193,6 +212,12 @@
if (currentPipChange != null) {
updatePipForUnhandledTransition(currentPipChange, startTransaction, finishTransaction);
}
+
+ // Fade in the fadeout PIP when the fixed rotation is finished.
+ if (mPipTransitionState.isInPip() && !mInFixedRotation && mHasFadeOut) {
+ fadeExistingPip(true /* show */);
+ }
+
return false;
}
@@ -242,9 +267,8 @@
public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
@Nullable SurfaceControl.Transaction tx) {
-
if (isInPipDirection(direction)) {
- mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
+ mPipTransitionState.setTransitionState(ENTERED_PIP);
}
// If there is an expected exit transition, then the exit will be "merged" into this
// transition so don't fire the finish-callback in that case.
@@ -268,6 +292,16 @@
mFinishCallback = null;
}
+ @Override
+ public void onFixedRotationStarted() {
+ // The transition with this fixed rotation may be handled by other handler before reaching
+ // PipTransition, so we cannot do this in #startAnimation.
+ if (mPipTransitionState.getTransitionState() == ENTERED_PIP && !mHasFadeOut) {
+ // Fade out the existing PiP to avoid jump cut during seamless rotation.
+ fadeExistingPip(false /* show */);
+ }
+ }
+
@Nullable
private TransitionInfo.Change findCurrentPipChange(@NonNull TransitionInfo info) {
if (mCurrentPipTaskToken == null) {
@@ -282,6 +316,17 @@
return null;
}
+ @Nullable
+ private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
private void startExitAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -453,6 +498,7 @@
}
// Keep track of the PIP task.
mCurrentPipTaskToken = enterPip.getContainer();
+ mHasFadeOut = false;
if (mFinishCallback != null) {
mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
@@ -465,12 +511,25 @@
startTransaction.show(wallpaper.getLeash());
startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
}
+ // Make sure other open changes are visible as entering PIP. Some may be hidden in
+ // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == enterPip || change == wallpaper) {
+ continue;
+ }
+ if (isOpeningType(change.getMode())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.show(leash).setAlpha(leash, 1.f);
+ }
+ }
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
mFinishCallback = finishCallback;
+ final int endRotation = mInFixedRotation ? mFixedRotation : enterPip.getEndRotation();
return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
startTransaction, finishTransaction, enterPip.getStartRotation(),
- enterPip.getEndRotation());
+ endRotation);
}
private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
@@ -481,25 +540,36 @@
taskInfo.topActivityInfo);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ int rotationDelta = deltaRotation(startRotation, endRotation);
+ Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+ taskInfo.pictureInPictureParams, currentBounds);
+ if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ // Need to get the bounds of new rotation in old rotation for fixed rotation,
+ sourceHintRect = computeRotatedBounds(rotationDelta, startRotation, endRotation,
+ taskInfo, TRANSITION_DIRECTION_TO_PIP, destinationBounds, sourceHintRect);
+ }
PipAnimationController.PipTransitionAnimator animator;
// Set corner radius for entering pip.
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
+ mPipMenuController.attach(leash);
+
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
-
- // PiP menu is attached late in the process here to avoid any artifacts on the leash
- // caused by addShellRoot when in gesture navigation mode.
- mPipMenuController.attach(leash);
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
.setPosition(leash, destinationBounds.left, destinationBounds.top)
.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
startTransaction.merge(tx);
startTransaction.apply();
+ if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ // For fixed rotation, set the destination bounds to the new rotation coordinates
+ // at the end.
+ destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
+ }
mPipBoundsState.setBounds(destinationBounds);
onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */);
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
@@ -507,17 +577,14 @@
return true;
}
- int rotationDelta = deltaRotation(endRotation, startRotation);
if (rotationDelta != Surface.ROTATION_0) {
Matrix tmpTransform = new Matrix();
- tmpTransform.postRotate(rotationDelta == Surface.ROTATION_90
- ? Surface.ROTATION_270 : Surface.ROTATION_90);
+ tmpTransform.postRotate(rotationDelta);
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- final Rect sourceHintRect =
- PipBoundsAlgorithm.getValidSourceHintRect(
- taskInfo.pictureInPictureParams, currentBounds);
+ // Reverse the rotation for Shell transition animation.
+ rotationDelta = deltaRotation(rotationDelta, 0);
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
0 /* startingAngle */, rotationDelta);
@@ -528,9 +595,6 @@
}
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
startTransaction.setAlpha(leash, 0f);
- // PiP menu is attached late in the process here to avoid any artifacts on the leash
- // caused by addShellRoot when in gesture navigation mode.
- mPipMenuController.attach(leash);
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -541,12 +605,47 @@
startTransaction.apply();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(mEnterExitAnimationDuration)
- .start();
+ .setDuration(mEnterExitAnimationDuration);
+ if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
+ // For fixed rotation, the animation destination bounds is in old rotation coordinates.
+ // Set the destination bounds to new coordinates after the animation is finished.
+ // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
+ animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
+ }
+ animator.start();
return true;
}
+ /** Computes destination bounds in old rotation and returns source hint rect if available. */
+ @Nullable
+ private Rect computeRotatedBounds(int rotationDelta, int startRotation, int endRotation,
+ TaskInfo taskInfo, int direction, Rect outDestinationBounds,
+ @Nullable Rect sourceHintRect) {
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
+ // Transform the destination bounds to current display coordinates.
+ rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
+ // When entering PiP (from button navigation mode), adjust the source rect hint by
+ // display cutout if applicable.
+ if (sourceHintRect != null && taskInfo.displayCutoutInsets != null) {
+ if (rotationDelta == Surface.ROTATION_270) {
+ sourceHintRect.offset(taskInfo.displayCutoutInsets.left,
+ taskInfo.displayCutoutInsets.top);
+ }
+ }
+ } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+ final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
+ rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
+ rotationDelta);
+ return PipBoundsAlgorithm.getValidSourceHintRect(taskInfo.pictureInPictureParams,
+ rotatedDestinationBounds);
+ }
+ return sourceHintRect;
+ }
+
private void startExitToSplitAnimation(TransitionInfo info,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
@@ -595,6 +694,13 @@
startTransaction.setCornerRadius(leash, 0);
startTransaction.setPosition(leash, bounds.left, bounds.top);
+ if (mHasFadeOut && prevPipChange.getTaskInfo().isVisible()) {
+ if (mPipAnimationController.getCurrentAnimator() != null) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ }
+ startTransaction.setAlpha(leash, 1);
+ }
+ mHasFadeOut = false;
mCurrentPipTaskToken = null;
mPipOrganizer.onExitPipFinished(prevPipChange.getTaskInfo());
}
@@ -615,6 +721,25 @@
.round(finishTransaction, leash, isInPip);
}
+ /** Hides and shows the existing PIP during fixed rotation transition of other activities. */
+ private void fadeExistingPip(boolean show) {
+ final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
+ final TaskInfo taskInfo = mPipOrganizer.getTaskInfo();
+ if (leash == null || !leash.isValid() || taskInfo == null) {
+ Log.w(TAG, "Invalid leash on fadeExistingPip: " + leash);
+ return;
+ }
+ final float alphaStart = show ? 0 : 1;
+ final float alphaEnd = show ? 1 : 0;
+ mPipAnimationController
+ .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
+ .setTransitionDirection(TRANSITION_DIRECTION_SAME)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(mEnterExitAnimationDuration)
+ .start();
+ mHasFadeOut = !show;
+ }
+
private void finishResizeForMenu(Rect destinationBounds) {
mPipMenuController.movePipMenu(null, null, destinationBounds);
mPipMenuController.updateMenuBounds(destinationBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 3403fb5..02e713d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -123,6 +123,10 @@
public void forceFinishTransition() {
}
+ /** Called when the fixed rotation started. */
+ public void onFixedRotationStarted() {
+ }
+
public PipTransitionController(PipBoundsState pipBoundsState,
PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipAnimationController pipAnimationController, Transitions transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index d022ec1..13137fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -97,9 +97,8 @@
* Start a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, int taskId, boolean intentFirst, in Bundle mainOptions,
- in Bundle sideOptions, int sidePosition, float splitRatio,
- in RemoteAnimationAdapter adapter) = 12;
+ in Intent fillInIntent, int taskId, in Bundle mainOptions,in Bundle sideOptions,
+ int sidePosition, float splitRatio, in RemoteAnimationAdapter adapter) = 12;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 990b53a..e88eef9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -349,17 +349,20 @@
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
- mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
-
- if (apps != null) {
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
+ if (apps == null || apps.length == 0) {
+ // Do nothing when the animation was cancelled.
+ t.apply();
+ return;
}
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
t.apply();
+
if (finishedCallback != null) {
try {
finishedCallback.onAnimationFinished();
@@ -642,14 +645,13 @@
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, int taskId, boolean intentFirst, Bundle mainOptions,
- Bundle sideOptions, int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter) {
+ Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
+ int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, taskId, intentFirst, mainOptions,
- sideOptions, sidePosition, splitRatio, adapter));
+ pendingIntent, fillInIntent, taskId, mainOptions, sideOptions,
+ sidePosition, splitRatio, adapter));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 029d073..81dacdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -373,6 +373,23 @@
void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
+ startWithLegacyTransition(mainTaskId, sideTaskId, null /* pendingIntent */,
+ null /* fillInIntent */, mainOptions, sideOptions, sidePosition, splitRatio,
+ adapter);
+ }
+
+ /** Start an intent and a task ordered by {@code intentFirst}. */
+ void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
+ int taskId, @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
+ startWithLegacyTransition(taskId, INVALID_TASK_ID, pendingIntent, fillInIntent,
+ mainOptions, sideOptions, sidePosition, splitRatio, adapter);
+ }
+
+ private void startWithLegacyTransition(int mainTaskId, int sideTaskId,
+ @Nullable PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
// Set false to avoid record new bounds with old task still on top;
@@ -466,124 +483,19 @@
addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
+ if (pendingIntent != null && fillInIntent != null) {
+ wct.startTask(mainTaskId, mainOptions);
+ wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+ }
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
}
- /** Start an intent and a task ordered by {@code intentFirst}. */
- void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
- int taskId, boolean intentFirst, @Nullable Bundle mainOptions,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter) {
- // TODO: try pulling the first chunk of this method into a method so that it can be shared
- // with startTasksWithLegacyTransition. So far attempts to do so result in failure in split.
-
- // Init divider first to make divider leash for remote animation target.
- mSplitLayout.init();
- // Set false to avoid record new bounds with old task still on top;
- mShouldUpdateRecents = false;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
- prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);
- // Need to add another wrapper here in shell so that we can inject the divider bar
- // and also manage the process elevation via setRunningRemote
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- RemoteAnimationTarget[] augmentedNonApps =
- new RemoteAnimationTarget[nonApps.length + 1];
- for (int i = 0; i < nonApps.length; ++i) {
- augmentedNonApps[i] = nonApps[i];
- }
- augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
-
- IRemoteAnimationFinishedCallback wrapCallback =
- new IRemoteAnimationFinishedCallback.Stub() {
- @Override
- public void onAnimationFinished() throws RemoteException {
- mShouldUpdateRecents = true;
- mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
- finishedCallback.onAnimationFinished();
- }
- };
- try {
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- adapter.getCallingApplication());
- } catch (SecurityException e) {
- Slog.e(TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
- adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
- augmentedNonApps, wrapCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- mShouldUpdateRecents = true;
- mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> setDividerVisibility(true, t));
- try {
- adapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
- };
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
- wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
-
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- mainOptions = mainActivityOptions.toBundle();
- }
-
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
-
- mSplitLayout.setDivideRatio(splitRatio);
- if (mMainStage.isActive()) {
- mMainStage.moveToTop(getMainStageBounds(), wct);
- } else {
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
- }
- mSideStage.moveToTop(getSideStageBounds(), wct);
-
- // Make sure the launch options will put tasks in the corresponding split roots
- addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
-
- // Add task launch requests
- if (intentFirst) {
- wct.sendPendingIntent(pendingIntent, fillInIntent, mainOptions);
- wct.startTask(taskId, sideOptions);
- } else {
- wct.startTask(taskId, mainOptions);
- wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
- }
-
- // Using legacy transitions, so we can't use blast sync since it conflicts.
- mTaskOrganizer.applyTransaction(wct);
- }
-
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 86b73fc..efb52a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -74,6 +74,8 @@
/** Set to {@code true} to enable shell transitions. */
public static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.debug.shell_transit", false);
+ public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
+ && SystemProperties.getBoolean("persist.debug.shell_transit_rotate", false);
/** Transition type for exiting PIP via the Shell, via pressing the expand button. */
public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1;
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 556742e..574a9f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -26,16 +26,8 @@
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6000s" />
<option name="hidden-api-checks" value="false" />
- <option name="device-listeners"
- value="com.android.server.wm.flicker.TraceFileReadyListener" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="pull-pattern-keys" value="(\w)+\.winscope" />
- <option name="pull-pattern-keys" value="(\w)+\.mp4" />
- <option name="collect-on-run-ended-only" value="false" />
- <option name="clean-up" value="true" />
- </metrics_collector>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index a20a201..a57d3e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,8 +16,8 @@
package com.android.wm.shell.flicker.bubble
-import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
@@ -25,8 +25,8 @@
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import org.junit.Assume
import org.junit.Before
-import org.junit.runner.RunWith
import org.junit.Test
+import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
@@ -57,7 +57,7 @@
}
}
- @Presubmit
+ @FlakyTest(bugId = 218642026)
@Test
open fun testAppIsAlwaysVisible() {
testSpec.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 467cadc..f2c5093 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -167,7 +166,7 @@
* Checks that the focus changes between the [pipApp] window and the launcher when
* closing the pip window
*/
- @Postsubmit
+ @Presubmit
@Test
fun focusChanges() {
testSpec.assertEventLog {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 173140d..c22d3f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
@@ -98,11 +97,11 @@
/**
* Checks that the focus doesn't change between windows during the transition
*/
- @Postsubmit
+ @Presubmit
@Test
open fun focusDoesNotChange() {
testSpec.assertEventLog {
this.focusDoesNotChange()
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 931d060..4f98b70 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -54,6 +54,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
+@FlakyTest(bugId = 219750830)
class ExitPipViaExpandButtonClickTest(
testSpec: FlickerTestParameter
) : ExitPipToAppTransition(testSpec) {
@@ -101,4 +102,4 @@
supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3)
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 07c3b15..5fc80db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -16,10 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -149,7 +148,7 @@
/**
* Checks that the focus doesn't change between windows during the transition
*/
- @Postsubmit
+ @FlakyTest(bugId = 216306753)
@Test
fun focusDoesNotChange() {
testSpec.assertEventLog {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index e91bef1..87e927f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -24,7 +24,10 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.traces.region.RegionSubject
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,9 +58,14 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class MovePipDownShelfHeightChangeTest(
+open class MovePipDownShelfHeightChangeTest(
testSpec: FlickerTestParameter
) : MovePipShelfHeightTransition(testSpec) {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt
new file mode 100644
index 0000000..0ff260b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest_ShellTransit.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip movement with Launcher shelf height change (decrease).
+ *
+ * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ *
+ * Actions:
+ * Launch [pipApp] in pip mode
+ * Launch [testApp]
+ * Press home
+ * Check if pip window moves down (visually)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+@FlakyTest(bugId = 219693385)
+class MovePipDownShelfHeightChangeTest_ShellTransit(
+ testSpec: FlickerTestParameter
+) : MovePipDownShelfHeightChangeTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 7e66cd3..388b5e0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -16,15 +16,18 @@
package com.android.wm.shell.flicker.pip
-import android.view.Surface
import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
+import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.traces.region.RegionSubject
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -58,6 +61,11 @@
class MovePipUpShelfHeightChangeTest(
testSpec: FlickerTestParameter
) : MovePipShelfHeightTransition(testSpec) {
+ @Before
+ fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index c5be485..29e40be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import org.junit.Before;
import org.junit.Test;
@@ -81,7 +82,8 @@
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
private @Mock SyncTransactionQueue mMockSyncQueue;
private @Mock ShellExecutor mMockExecutor;
- private @Mock CompatUIWindowManager mMockLayout;
+ private @Mock CompatUIWindowManager mMockCompatLayout;
+ private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -91,16 +93,26 @@
MockitoAnnotations.initMocks(this);
doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
- doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
- doReturn(TASK_ID).when(mMockLayout).getTaskId();
- doReturn(true).when(mMockLayout).createLayout(anyBoolean());
- doReturn(true).when(mMockLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(DISPLAY_ID).when(mMockCompatLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockCompatLayout).getTaskId();
+ doReturn(true).when(mMockCompatLayout).createLayout(anyBoolean());
+ doReturn(true).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(DISPLAY_ID).when(mMockLetterboxEduLayout).getDisplayId();
+ doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
+ doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
+ doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
mController = new CompatUIController(mContext, mMockDisplayController,
mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
- return mMockLayout;
+ return mMockCompatLayout;
+ }
+
+ @Override
+ LetterboxEduWindowManager createLetterboxEduWindowManager(Context context,
+ TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+ return mMockLetterboxEduLayout;
}
};
spyOn(mController);
@@ -121,68 +133,95 @@
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
- // Verify that the compat controls are updated with new size compat info.
- clearInvocations(mMockLayout);
+ // Verify that the compat controls and letterbox education are updated with new size compat
+ // info.
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
clearInvocations(mController);
taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ true);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
- // Verify that compat controls are removed with null task listener.
- clearInvocations(mMockLayout);
+ // Verify that compat controls and letterbox education are removed with null task listener.
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
clearInvocations(mController);
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
/* taskListener= */ null);
- verify(mMockLayout).release();
+ verify(mMockCompatLayout).release();
+ verify(mMockLetterboxEduLayout).release();
}
@Test
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
- doReturn(false).when(mMockLayout).createLayout(anyBoolean());
+ doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
+ doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
// Verify that the layout is created again.
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
clearInvocations(mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
@Test
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
- doReturn(false).when(mMockLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
clearInvocations(mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ true);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ true);
// Verify that the layout is created again.
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
clearInvocations(mController);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+ verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+ verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
}
@@ -204,14 +243,16 @@
mController.onDisplayRemoved(DISPLAY_ID + 1);
- verify(mMockLayout, never()).release();
+ verify(mMockCompatLayout, never()).release();
+ verify(mMockLetterboxEduLayout, never()).release();
verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID),
any());
mController.onDisplayRemoved(DISPLAY_ID);
verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any());
- verify(mMockLayout).release();
+ verify(mMockCompatLayout).release();
+ verify(mMockLetterboxEduLayout).release();
}
@Test
@@ -221,11 +262,13 @@
mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
- verify(mMockLayout, never()).updateDisplayLayout(any());
+ verify(mMockCompatLayout, never()).updateDisplayLayout(any());
+ verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any());
mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
- verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
}
@Test
@@ -242,104 +285,125 @@
mOnInsetsChangedListenerCaptor.capture());
mOnInsetsChangedListenerCaptor.getValue().insetsChanged(insetsState);
- verify(mMockLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
// No update if the insets state is the same.
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState));
- verify(mMockLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+ verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout);
}
@Test
- public void testChangeButtonVisibilityOnImeShowHide() {
+ public void testChangeLayoutsVisibilityOnImeShowHide() {
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
// Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button remains hidden while IME is showing.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ false);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
// Verify button is shown after IME is hidden.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
@Test
- public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
+ public void testChangeLayoutsVisibilityOnKeyguardOccludedChanged() {
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
// Verify that the restart button is hidden after keyguard becomes occluded.
mController.onKeyguardOccludedChanged(true);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button remains hidden while keyguard is occluded.
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
CAMERA_COMPAT_CONTROL_HIDDEN);
mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ false);
+ verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
+ verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
+ false);
// Verify button is shown after keyguard becomes not occluded.
mController.onKeyguardOccludedChanged(false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
@Test
- public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
+ public void testLayoutsRemainHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardOccludedChanged(true);
- verify(mMockLayout, times(2)).updateVisibility(false);
+ verify(mMockCompatLayout, times(2)).updateVisibility(false);
+ verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
// Verify button remains hidden after keyguard becomes not occluded since IME is showing.
mController.onKeyguardOccludedChanged(false);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button is shown after IME is not showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
@Test
- public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
+ public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsOccluded() {
mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardOccludedChanged(true);
- verify(mMockLayout, times(2)).updateVisibility(false);
+ verify(mMockCompatLayout, times(2)).updateVisibility(false);
+ verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
- clearInvocations(mMockLayout);
+ clearInvocations(mMockCompatLayout);
+ clearInvocations(mMockLetterboxEduLayout);
// Verify button remains hidden after IME is hidden since keyguard is occluded.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
- verify(mMockLayout).updateVisibility(false);
+ verify(mMockCompatLayout).updateVisibility(false);
+ verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button is shown after keyguard becomes not occluded.
mController.onKeyguardOccludedChanged(false);
- verify(mMockLayout).updateVisibility(true);
+ verify(mMockCompatLayout).updateVisibility(true);
+ verify(mMockLetterboxEduLayout).updateVisibility(true);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 352805b..7d3e718 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import org.junit.Before;
import org.junit.Test;
@@ -77,8 +78,7 @@
mWindowManager = new CompatUIWindowManager(mContext,
createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
mSyncTransactionQueue, mCallback, mTaskListener,
- new DisplayLayout(), /* hasShownSizeCompatHint= */ false,
- /* hasShownCameraCompatHint= */ false);
+ new DisplayLayout(), new CompatUIHintsState());
mLayout = (CompatUILayout)
LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index f9cfd12..e79b803 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -51,6 +51,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import org.junit.Before;
import org.junit.Test;
@@ -85,8 +86,7 @@
mWindowManager = new CompatUIWindowManager(mContext,
createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
mSyncTransactionQueue, mCallback, mTaskListener,
- new DisplayLayout(), /* hasShownSizeCompatHint= */ false,
- /* hasShownCameraCompatHint= */ false);
+ new DisplayLayout(), new CompatUIHintsState());
spyOn(mWindowManager);
doReturn(mLayout).when(mWindowManager).inflateLayout();
@@ -102,7 +102,7 @@
verify(mWindowManager, never()).inflateLayout();
// Doesn't create hint popup.
- mWindowManager.mShouldShowSizeCompatHint = false;
+ mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
assertTrue(mWindowManager.createLayout(/* canShow= */ true));
verify(mWindowManager).inflateLayout();
@@ -113,14 +113,14 @@
clearInvocations(mWindowManager);
clearInvocations(mLayout);
mWindowManager.release();
- mWindowManager.mShouldShowSizeCompatHint = true;
+ mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = false;
assertTrue(mWindowManager.createLayout(/* canShow= */ true));
verify(mWindowManager).inflateLayout();
assertNotNull(mLayout);
verify(mLayout).setRestartButtonVisibility(/* show= */ true);
verify(mLayout).setSizeCompatHintVisibility(/* show= */ true);
- assertFalse(mWindowManager.mShouldShowSizeCompatHint);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint);
// Returns false and doesn't create layout if has Size Compat is false.
clearInvocations(mWindowManager);
@@ -140,7 +140,7 @@
verify(mWindowManager, never()).inflateLayout();
// Doesn't create hint popup.
- mWindowManager.mShouldShowCameraCompatHint = false;
+ mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true;
assertTrue(mWindowManager.createLayout(/* canShow= */ true));
verify(mWindowManager).inflateLayout();
@@ -151,14 +151,14 @@
clearInvocations(mWindowManager);
clearInvocations(mLayout);
mWindowManager.release();
- mWindowManager.mShouldShowCameraCompatHint = true;
+ mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = false;
assertTrue(mWindowManager.createLayout(/* canShow= */ true));
verify(mWindowManager).inflateLayout();
assertNotNull(mLayout);
verify(mLayout).setCameraControlVisibility(/* show= */ true);
verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
- assertFalse(mWindowManager.mShouldShowCameraCompatHint);
+ assertTrue(mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint);
// Returns false and doesn't create layout if Camera Compat state is hidden
clearInvocations(mWindowManager);
@@ -411,7 +411,7 @@
public void testOnRestartButtonLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mHasSizeCompat = true;
- mWindowManager.mShouldShowSizeCompatHint = false;
+ mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
mWindowManager.createLayout(/* canShow= */ true);
verify(mWindowManager).inflateLayout();
@@ -423,10 +423,10 @@
}
@Test
- public void testOnCamerControlLongClicked_showHint() {
+ public void testOnCameraControlLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.mShouldShowCameraCompatHint = false;
+ mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true;
mWindowManager.createLayout(/* canShow= */ true);
verify(mWindowManager).inflateLayout();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
new file mode 100644
index 0000000..00e4938
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxEduDialogLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxEduDialogLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class LetterboxEduDialogLayoutTest extends ShellTestCase {
+
+ @Mock
+ private Runnable mDismissCallback;
+
+ private LetterboxEduDialogLayout mLayout;
+ private View mDismissButton;
+ private View mDialogContainer;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mLayout = (LetterboxEduDialogLayout)
+ LayoutInflater.from(mContext).inflate(R.layout.letterbox_education_dialog_layout,
+ null);
+ mDismissButton = mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button);
+ mDialogContainer = mLayout.findViewById(R.id.letterbox_education_dialog_container);
+ mLayout.setDismissOnClickListener(mDismissCallback);
+ }
+
+ @Test
+ public void testOnFinishInflate() {
+ assertEquals(mLayout.getDialogContainer(),
+ mLayout.findViewById(R.id.letterbox_education_dialog_container));
+ assertEquals(mLayout.getBackgroundDim(), mLayout.getBackground());
+ assertEquals(mLayout.getBackground().getAlpha(), 0);
+ }
+
+ @Test
+ public void testOnDismissButtonClicked() {
+ assertTrue(mDismissButton.performClick());
+
+ verify(mDismissCallback).run();
+ }
+
+ @Test
+ public void testOnBackgroundClicked() {
+ assertTrue(mLayout.performClick());
+
+ verify(mDismissCallback).run();
+ }
+
+ @Test
+ public void testOnDialogContainerClicked() {
+ assertTrue(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+
+ @Test
+ public void testSetDismissOnClickListenerNull() {
+ mLayout.setDismissOnClickListener(null);
+
+ assertFalse(mDismissButton.performClick());
+ assertFalse(mLayout.performClick());
+ assertFalse(mDialogContainer.performClick());
+
+ verify(mDismissCallback, never()).run();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
new file mode 100644
index 0000000..0509dd3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.SurfaceControlViewHost;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link LetterboxEduWindowManager}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxEduWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class LetterboxEduWindowManagerTest extends ShellTestCase {
+
+ private static final int TASK_ID = 1;
+
+ private static final int TASK_WIDTH = 200;
+ private static final int TASK_HEIGHT = 100;
+ private static final int DISPLAY_CUTOUT_TOP = 5;
+ private static final int DISPLAY_CUTOUT_BOTTOM = 10;
+ private static final int DISPLAY_CUTOUT_HORIZONTAL = 20;
+
+ @Captor
+ private ArgumentCaptor<WindowManager.LayoutParams> mWindowAttrsCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mEndCallbackCaptor;
+
+ @Mock private LetterboxEduAnimationController mAnimationController;
+ @Mock private SyncTransactionQueue mSyncTransactionQueue;
+ @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+ @Mock private SurfaceControlViewHost mViewHost;
+ @Mock private Runnable mOnDismissCallback;
+
+ private SharedPreferences mSharedPreferences;
+ private String mPrefKey;
+ @Nullable
+ private Boolean mInitialPrefValue = null;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mSharedPreferences = mContext.getSharedPreferences(
+ LetterboxEduWindowManager.HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
+ Context.MODE_PRIVATE);
+ mPrefKey = String.valueOf(mContext.getUserId());
+ if (mSharedPreferences.contains(mPrefKey)) {
+ mInitialPrefValue = mSharedPreferences.getBoolean(mPrefKey, /* default= */ false);
+ mSharedPreferences.edit().remove(mPrefKey).apply();
+ }
+ }
+
+ @After
+ public void tearDown() {
+ SharedPreferences.Editor editor = mSharedPreferences.edit();
+ if (mInitialPrefValue == null) {
+ editor.remove(mPrefKey);
+ } else {
+ editor.putBoolean(mPrefKey, mInitialPrefValue);
+ }
+ editor.apply();
+ }
+
+ @Test
+ public void testCreateLayout_notEligible_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_alreadyShownToUser_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+ mSharedPreferences.edit().putBoolean(mPrefKey, true).apply();
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
+ true, /* isTaskbarEduShowing= */ true);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ false));
+
+ assertFalse(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false));
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testCreateLayout_canShowTrue_createsLayoutCorrectly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+
+ assertTrue(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+ verify(mViewHost).setView(eq(layout), mWindowAttrsCaptor.capture());
+ verifyLayout(layout, mWindowAttrsCaptor.getValue(), /* expectedWidth= */ TASK_WIDTH,
+ /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */ DISPLAY_CUTOUT_TOP,
+ /* expectedExtraBottomMargin= */ DISPLAY_CUTOUT_BOTTOM);
+
+ // Clicking the layout does nothing until enter animation is done.
+ layout.performClick();
+ verify(mAnimationController, never()).startExitAnimation(any(), any());
+
+ verifyAndFinishEnterAnimation(layout);
+
+ // Exit animation should start following a click on the layout.
+ layout.performClick();
+
+ // Window manager isn't released until exit animation is done.
+ verify(windowManager, never()).release();
+
+ // Verify multiple clicks are ignored.
+ layout.performClick();
+
+ verifyAndFinishExitAnimation(layout);
+
+ verify(windowManager).release();
+ verify(mOnDismissCallback).run();
+ }
+
+ @Test
+ public void testUpdateCompatInfo_updatesLayoutCorrectly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ assertTrue(windowManager.updateCompatInfo(
+ createTaskInfo(/* eligible= */ true, new Rect(50, 25, 150, 75)),
+ mTaskListener, /* canShow= */ true));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100,
+ /* expectedHeight= */ 50, /* expectedExtraTopMargin= */ 0,
+ /* expectedExtraBottomMargin= */ 0);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+
+ // Window manager should be released (without animation) when eligible becomes false.
+ assertFalse(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ false),
+ mTaskListener, /* canShow= */ true));
+
+ verify(windowManager).release();
+ verify(mOnDismissCallback, never()).run();
+ verify(mAnimationController, never()).startExitAnimation(any(), any());
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
+
+ assertFalse(windowManager.createLayout(/* canShow= */ true));
+ assertNull(windowManager.mLayout);
+
+ assertTrue(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ true),
+ mTaskListener, /* canShow= */ true));
+
+ assertNotNull(windowManager.mLayout);
+ }
+
+ @Test
+ public void testUpdateCompatInfo_canShowFalse_doesNothing() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ false));
+ assertNull(windowManager.mLayout);
+
+ assertTrue(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ true),
+ mTaskListener, /* canShow= */ false));
+
+ assertNull(windowManager.mLayout);
+ verify(mViewHost, never()).relayout(any());
+ }
+
+ @Test
+ public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ int newDisplayCutoutTop = DISPLAY_CUTOUT_TOP + 7;
+ int newDisplayCutoutBottom = DISPLAY_CUTOUT_BOTTOM + 9;
+ windowManager.updateDisplayLayout(createDisplayLayout(
+ Insets.of(DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutTop,
+ DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutBottom)));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ TASK_WIDTH,
+ /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */
+ newDisplayCutoutTop, /* expectedExtraBottomMargin= */ newDisplayCutoutBottom);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+ }
+
+ @Test
+ public void testRelease_animationIsCancelled() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ windowManager.release();
+
+ verify(mAnimationController).cancelAnimation();
+ }
+
+ private void verifyLayout(LetterboxEduDialogLayout layout, ViewGroup.LayoutParams params,
+ int expectedWidth, int expectedHeight, int expectedExtraTopMargin,
+ int expectedExtraBottomMargin) {
+ assertThat(params.width).isEqualTo(expectedWidth);
+ assertThat(params.height).isEqualTo(expectedHeight);
+ MarginLayoutParams dialogParams =
+ (MarginLayoutParams) layout.getDialogContainer().getLayoutParams();
+ int verticalMargin = (int) mContext.getResources().getDimension(
+ R.dimen.letterbox_education_dialog_margin);
+ assertThat(dialogParams.topMargin).isEqualTo(verticalMargin + expectedExtraTopMargin);
+ assertThat(dialogParams.bottomMargin).isEqualTo(verticalMargin + expectedExtraBottomMargin);
+ }
+
+ private void verifyAndFinishEnterAnimation(LetterboxEduDialogLayout layout) {
+ verify(mAnimationController).startEnterAnimation(eq(layout), mEndCallbackCaptor.capture());
+ mEndCallbackCaptor.getValue().run();
+ }
+
+ private void verifyAndFinishExitAnimation(LetterboxEduDialogLayout layout) {
+ verify(mAnimationController).startExitAnimation(eq(layout), mEndCallbackCaptor.capture());
+ mEndCallbackCaptor.getValue().run();
+ }
+
+ private LetterboxEduWindowManager createWindowManager(boolean eligible) {
+ return createWindowManager(eligible, /* isTaskbarEduShowing= */ false);
+ }
+
+ private LetterboxEduWindowManager createWindowManager(boolean eligible,
+ boolean isTaskbarEduShowing) {
+ LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
+ createTaskInfo(eligible), mSyncTransactionQueue, mTaskListener,
+ createDisplayLayout(), mOnDismissCallback, mAnimationController);
+
+ spyOn(windowManager);
+ doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
+ doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
+
+ return windowManager;
+ }
+
+ private DisplayLayout createDisplayLayout() {
+ return createDisplayLayout(
+ Insets.of(DISPLAY_CUTOUT_HORIZONTAL, DISPLAY_CUTOUT_TOP, DISPLAY_CUTOUT_HORIZONTAL,
+ DISPLAY_CUTOUT_BOTTOM));
+ }
+
+ private DisplayLayout createDisplayLayout(Insets insets) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = TASK_WIDTH;
+ displayInfo.logicalHeight = TASK_HEIGHT;
+ displayInfo.displayCutout = new DisplayCutout(
+ insets, null, null, null, null);
+ return new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligible) {
+ return createTaskInfo(eligible, new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+ }
+
+ private static TaskInfo createTaskInfo(boolean eligible, Rect bounds) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = TASK_ID;
+ taskInfo.topActivityEligibleForLetterboxEducation = eligible;
+ taskInfo.configuration.windowConfiguration.setBounds(bounds);
+ return taskInfo;
+ }
+}
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index 76ea2d5..c13c800 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -134,6 +134,21 @@
GraphicsJNI::irect_to_jrect(ir, env, bounds);
}
+// Regular JNI
+static jlong nGetExtent(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start,
+ jint end) {
+ ScopedCharArrayRO text(env, javaText);
+ const minikin::U16StringPiece textBuffer(text.get(), text.size());
+ const minikin::Range range(start, end);
+
+ minikin::MinikinExtent extent = toMeasuredParagraph(ptr)->getExtent(textBuffer, range);
+
+ int32_t ascent = SkScalarRoundToInt(extent.ascent);
+ int32_t descent = SkScalarRoundToInt(extent.descent);
+
+ return (((jlong)(ascent)) << 32) | ((jlong)descent);
+}
+
// CriticalNative
static jlong nGetReleaseFunc(CRITICAL_JNI_PARAMS) {
return toJLong(&releaseMeasuredParagraph);
@@ -153,12 +168,13 @@
};
static const JNINativeMethod gMTMethods[] = {
- // MeasuredParagraph native functions.
- {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives
- {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds}, // Regular JNI
- {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives
- {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native
- {"nGetCharWidthAt", "(JI)F", (void*) nGetCharWidthAt}, // Critical Native
+ // MeasuredParagraph native functions.
+ {"nGetWidth", "(JII)F", (void*)nGetWidth}, // Critical Natives
+ {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*)nGetBounds}, // Regular JNI
+ {"nGetExtent", "(J[CII)J", (void*)nGetExtent}, // Regular JNI
+ {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc}, // Critical Natives
+ {"nGetMemoryUsage", "(J)I", (void*)nGetMemoryUsage}, // Critical Native
+ {"nGetCharWidthAt", "(JI)F", (void*)nGetCharWidthAt}, // Critical Native
};
int register_android_graphics_text_MeasuredText(JNIEnv* env) {
diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index 29888e1..f140c68 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -144,8 +144,8 @@
private final ClockInfo mClockInfo;
private final double mIonoDelayMeters;
private final double mTropoDelayMeters;
- private final int mTimeOfClock;
- private final int mTimeOfEphemeris;
+ private final long mTimeOfClock;
+ private final long mTimeOfEphemeris;
private final int mIssueOfDataClock;
private final int mIssueOfDataEphemeris;
@EphemerisSource
@@ -457,8 +457,8 @@
@Nullable ClockInfo clockInfo,
double ionoDelayMeters,
double tropoDelayMeters,
- int timeOfClock,
- int timeOfEphemeris,
+ long timeOfClock,
+ long timeOfEphemeris,
int issueOfDataClock,
int issueOfDataEphemeris,
@EphemerisSource int ephemerisSource) {
@@ -547,26 +547,28 @@
/**
* Time of Clock.
*
- * <p>This is defined in GPS ICD200 documentation (e.g.,
- * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+ * <p>The value is in seconds since GPS epoch, regardless of the constellation.
+ *
+ * <p>The value is not encoded as in GPS ICD200 documentation.
*
* <p>This field is valid if {@link #hasTimeOfClock()} is true.
*/
- @IntRange(from = 0, to = 604784)
- public int getTimeOfClock() {
+ @IntRange(from = 0)
+ public long getTimeOfClock() {
return mTimeOfClock;
}
/**
* Time of ephemeris.
*
- * <p>This is defined in GPS ICD200 documentation (e.g.,
- * <a href="https://www.gps.gov/technical/icwg/IS-GPS-200H.pdf"></a>).
+ * <p>The value is in seconds since GPS epoch, regardless of the constellation.
+ *
+ * <p>The value is not encoded as in GPS ICD200 documentation.
*
* <p>This field is valid if {@link #hasTimeOfEphemeris()} is true.
*/
- @IntRange(from = 0, to = 604784)
- public int getTimeOfEphemeris() {
+ @IntRange(from = 0)
+ public long getTimeOfEphemeris() {
return mTimeOfEphemeris;
}
@@ -630,8 +632,8 @@
android.location.SatellitePvt.ClockInfo.class);
double ionoDelayMeters = in.readDouble();
double tropoDelayMeters = in.readDouble();
- int toc = in.readInt();
- int toe = in.readInt();
+ long toc = in.readLong();
+ long toe = in.readLong();
int iodc = in.readInt();
int iode = in.readInt();
int ephemerisSource = in.readInt();
@@ -669,8 +671,8 @@
parcel.writeParcelable(mClockInfo, flags);
parcel.writeDouble(mIonoDelayMeters);
parcel.writeDouble(mTropoDelayMeters);
- parcel.writeInt(mTimeOfClock);
- parcel.writeInt(mTimeOfEphemeris);
+ parcel.writeLong(mTimeOfClock);
+ parcel.writeLong(mTimeOfEphemeris);
parcel.writeInt(mIssueOfDataClock);
parcel.writeInt(mIssueOfDataEphemeris);
parcel.writeInt(mEphemerisSource);
@@ -707,8 +709,8 @@
@Nullable private ClockInfo mClockInfo;
private double mIonoDelayMeters;
private double mTropoDelayMeters;
- private int mTimeOfClock;
- private int mTimeOfEphemeris;
+ private long mTimeOfClock;
+ private long mTimeOfEphemeris;
private int mIssueOfDataClock;
private int mIssueOfDataEphemeris;
@EphemerisSource
@@ -721,8 +723,7 @@
* @return builder object
*/
@NonNull
- public Builder setPositionEcef(
- @NonNull PositionEcef positionEcef) {
+ public Builder setPositionEcef(@NonNull PositionEcef positionEcef) {
mPositionEcef = positionEcef;
updateFlags();
return this;
@@ -735,8 +736,7 @@
* @return builder object
*/
@NonNull
- public Builder setVelocityEcef(
- @NonNull VelocityEcef velocityEcef) {
+ public Builder setVelocityEcef(@NonNull VelocityEcef velocityEcef) {
mVelocityEcef = velocityEcef;
updateFlags();
return this;
@@ -749,8 +749,7 @@
* @return builder object
*/
@NonNull
- public Builder setClockInfo(
- @NonNull ClockInfo clockInfo) {
+ public Builder setClockInfo(@NonNull ClockInfo clockInfo) {
mClockInfo = clockInfo;
updateFlags();
return this;
@@ -793,12 +792,16 @@
/**
* Set time of clock in seconds.
*
+ * <p>The value is in seconds since GPS epoch, regardless of the constellation.
+ *
+ * <p>The value is not encoded as in GPS ICD200 documentation.
+ *
* @param timeOfClock time of clock (seconds)
* @return builder object
*/
@NonNull
- public Builder setTimeOfClock(@IntRange(from = 0, to = 604784) int timeOfClock) {
- Preconditions.checkArgumentInRange(timeOfClock, 0, 604784, "timeOfClock");
+ public Builder setTimeOfClock(@IntRange(from = 0) long timeOfClock) {
+ Preconditions.checkArgumentNonnegative(timeOfClock);
mTimeOfClock = timeOfClock;
mFlags = (byte) (mFlags | HAS_TIME_OF_CLOCK);
return this;
@@ -807,12 +810,16 @@
/**
* Set time of ephemeris in seconds.
*
+ * <p>The value is in seconds since GPS epoch, regardless of the constellation.
+ *
+ * <p>The value is not encoded as in GPS ICD200 documentation.
+ *
* @param timeOfEphemeris time of ephemeris (seconds)
* @return builder object
*/
@NonNull
- public Builder setTimeOfEphemeris(@IntRange(from = 0, to = 604784) int timeOfEphemeris) {
- Preconditions.checkArgumentInRange(timeOfEphemeris, 0, 604784, "timeOfEphemeris");
+ public Builder setTimeOfEphemeris(@IntRange(from = 0) int timeOfEphemeris) {
+ Preconditions.checkArgumentNonnegative(timeOfEphemeris);
mTimeOfEphemeris = timeOfEphemeris;
mFlags = (byte) (mFlags | HAS_TIME_OF_EPHEMERIS);
return this;
diff --git a/media/java/Android.bp b/media/java/Android.bp
index c7c1d54..6878f9d 100644
--- a/media/java/Android.bp
+++ b/media/java/Android.bp
@@ -16,7 +16,9 @@
exclude_srcs: [
":framework-media-tv-tunerresourcemanager-sources-aidl",
],
- visibility: ["//frameworks/base"],
+ visibility: [
+ "//frameworks/base",
+ ],
}
filegroup {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3887372..6e695e6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7294,8 +7294,13 @@
* Ultrasound playback and capture, false otherwise.
*/
@SystemApi
- public static boolean isUltrasoundSupported() {
- return AudioSystem.isUltrasoundSupported();
+ @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
+ public boolean isUltrasoundSupported() {
+ try {
+ return getService().isUltrasoundSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2c9f015..d702eb9 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -138,6 +138,8 @@
boolean isMicrophoneMuted();
+ boolean isUltrasoundSupported();
+
void setMicrophoneMute(boolean on, String callingPackage, int userId, in String attributionTag);
oneway void setMicrophoneMuteFromSwitch(boolean on);
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 5348d4e..74c5499 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -112,20 +112,11 @@
// Binder stub for receiving device notifications from MidiService
private class DeviceListener extends IMidiDeviceListener.Stub {
private final DeviceCallback mCallback;
- private final Handler mHandler;
private final Executor mExecutor;
private final int mTransport;
- DeviceListener(DeviceCallback callback, Handler handler, int transport) {
- mCallback = callback;
- mHandler = handler;
- mExecutor = null;
- mTransport = transport;
- }
-
DeviceListener(DeviceCallback callback, Executor executor, int transport) {
mCallback = callback;
- mHandler = null;
mExecutor = executor;
mTransport = transport;
}
@@ -136,13 +127,6 @@
if (mExecutor != null) {
mExecutor.execute(() ->
mCallback.onDeviceAdded(device));
- } else if (mHandler != null) {
- final MidiDeviceInfo deviceF = device;
- mHandler.post(new Runnable() {
- @Override public void run() {
- mCallback.onDeviceAdded(deviceF);
- }
- });
} else {
mCallback.onDeviceAdded(device);
}
@@ -155,13 +139,6 @@
if (mExecutor != null) {
mExecutor.execute(() ->
mCallback.onDeviceRemoved(device));
- } else if (mHandler != null) {
- final MidiDeviceInfo deviceF = device;
- mHandler.post(new Runnable() {
- @Override public void run() {
- mCallback.onDeviceRemoved(deviceF);
- }
- });
} else {
mCallback.onDeviceRemoved(device);
}
@@ -173,13 +150,6 @@
if (mExecutor != null) {
mExecutor.execute(() ->
mCallback.onDeviceStatusChanged(status));
- } else if (mHandler != null) {
- final MidiDeviceStatus statusF = status;
- mHandler.post(new Runnable() {
- @Override public void run() {
- mCallback.onDeviceStatusChanged(statusF);
- }
- });
} else {
mCallback.onDeviceStatusChanged(status);
}
@@ -275,7 +245,11 @@
*/
@Deprecated
public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
- DeviceListener deviceListener = new DeviceListener(callback, handler,
+ Executor executor = null;
+ if (handler != null) {
+ executor = handler::post;
+ }
+ DeviceListener deviceListener = new DeviceListener(callback, executor,
TRANSPORT_MIDI_BYTE_STREAM);
try {
mService.registerListener(mToken, deviceListener);
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
index 9039011..858f85f 100644
--- a/media/packages/BluetoothMidiService/AndroidManifest.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -20,7 +20,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.bluetoothmidiservice"
>
- <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true"/>
diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
index 5a900c7..99dcaaa 100644
--- a/media/packages/BluetoothMidiService/AndroidManifestBase.xml
+++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml
@@ -19,7 +19,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.bluetoothmidiservice"
>
- <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+ <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<application
android:label="BluetoothMidi"
android:defaultToDeviceProtectedStorage="true"
diff --git a/native/android/input.cpp b/native/android/input.cpp
index c06c81e..a231d8f 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -283,6 +283,21 @@
axis, pointer_index, history_index);
}
+int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event) {
+ return static_cast<const MotionEvent*>(motion_event)->getActionButton();
+}
+
+int32_t AMotionEvent_getClassification(const AInputEvent* motion_event) {
+ switch (static_cast<const MotionEvent*>(motion_event)->getClassification()) {
+ case android::MotionClassification::NONE:
+ return AMOTION_EVENT_CLASSIFICATION_NONE;
+ case android::MotionClassification::AMBIGUOUS_GESTURE:
+ return AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE;
+ case android::MotionClassification::DEEP_PRESS:
+ return AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS;
+ }
+}
+
const AInputEvent* AMotionEvent_fromJava(JNIEnv* env, jobject motionEvent) {
MotionEvent* eventSrc = android::android_view_MotionEvent_getNativePtr(env, motionEvent);
if (eventSrc == nullptr) {
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 700b756..4aa5bbf 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -114,8 +114,10 @@
ALooper_removeFd;
ALooper_wake;
AMotionEvent_getAction;
+ AMotionEvent_getActionButton; # introduced=Tiramisu
AMotionEvent_getAxisValue; # introduced-arm=13 introduced-arm64=21 introduced-mips=13 introduced-mips64=21 introduced-x86=13 introduced-x86_64=21
AMotionEvent_getButtonState; # introduced-arm=14 introduced-arm64=21 introduced-mips=14 introduced-mips64=21 introduced-x86=14 introduced-x86_64=21
+ AMotionEvent_getClassification; # introduced=Tiramisu
AMotionEvent_getDownTime;
AMotionEvent_getEdgeFlags;
AMotionEvent_getEventTime;
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index 0e60873..6ded1637 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -39,6 +39,7 @@
static_libs: [
"androidx.lifecycle_lifecycle-livedata",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.recyclerview_recyclerview",
"androidx.appcompat_appcompat",
],
diff --git a/packages/SystemUI/res/layout/idle_host_view.xml b/packages/CompanionDeviceManager/res/color/selector.xml
similarity index 64%
copy from packages/SystemUI/res/layout/idle_host_view.xml
copy to packages/CompanionDeviceManager/res/color/selector.xml
index f407874..fda827d 100644
--- a/packages/SystemUI/res/layout/idle_host_view.xml
+++ b/packages/CompanionDeviceManager/res/color/selector.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,9 +14,7 @@
~ limitations under the License.
-->
-
-<com.android.systemui.idle.IdleHostView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/idle_host_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@android:color/darker_gray"/> <!-- pressed -->
+ <item android:color="@android:color/white"/>
+</selector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 313e164..70cbfdf 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -49,8 +49,8 @@
android:layout_height="0dp"
android:layout_weight="1">
- <ListView
- android:id="@+id/device_list"
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/device_list"
style="@android:style/Widget.Material.ListView"
android:layout_width="match_parent"
android:layout_height="200dp" />
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index d79aea6..153fc1f 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -19,7 +19,8 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
- android:padding="12dp">
+ android:padding="12dp"
+ android:background="@color/selector">
<!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 16e851b..b51d310 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -46,10 +46,11 @@
import android.util.Log;
import android.view.View;
import android.widget.Button;
-import android.widget.ListView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
@@ -94,9 +95,9 @@
// regular.
private Button mButtonAllow;
- // The list is only shown for multiple-device regular association request, after at least one
- // matching device is found.
- private @Nullable ListView mListView;
+ // The recycler view is only shown for multiple-device regular association request, after
+ // at least one matching device is found.
+ private @Nullable RecyclerView mRecyclerView;
private @Nullable DeviceListAdapter mAdapter;
// The flag used to prevent double taps, that may lead to sending several requests for creating
@@ -195,14 +196,15 @@
mTitle = findViewById(R.id.title);
mSummary = findViewById(R.id.summary);
- mListView = findViewById(R.id.device_list);
- mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position));
+ mRecyclerView = findViewById(R.id.device_list);
+ mAdapter = new DeviceListAdapter(this, this::onListItemClick);
mButtonAllow = findViewById(R.id.btn_positive);
mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick);
final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
+
if (mRequest.isSelfManaged()) {
initUiForSelfManagedAssociation(appLabel);
} else if (mRequest.isSingleDevice()) {
@@ -333,7 +335,7 @@
mTitle.setText(title);
mSummary.setText(summary);
- mListView.setVisibility(View.GONE);
+ mRecyclerView.setVisibility(View.GONE);
}
private void initUiForSingleDevice(CharSequence appLabel) {
@@ -345,12 +347,12 @@
deviceFilterPairs -> updateSingleDeviceUi(
deviceFilterPairs, deviceProfile, appLabel));
- mListView.setVisibility(View.GONE);
+ mRecyclerView.setVisibility(View.GONE);
}
private void updateSingleDeviceUi(List<DeviceFilterPair<?>> deviceFilterPairs,
String deviceProfile, CharSequence appLabel) {
- // Ignore "empty" scan repots.
+ // Ignore "empty" scan reports.
if (deviceFilterPairs.isEmpty()) return;
mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
@@ -393,10 +395,12 @@
mTitle.setText(title);
mSummary.setText(summary);
- mAdapter = new DeviceListAdapter(this);
+ mAdapter = new DeviceListAdapter(this, this::onListItemClick);
// TODO: hide the list and show a spinner until a first device matching device is found.
- mListView.setAdapter(mAdapter);
+ mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+
CompanionDeviceDiscoveryService.getScanResult().observe(
/* lifecycleOwner */ this,
/* observer */ mAdapter);
@@ -414,6 +418,8 @@
if (DEBUG) Log.w(TAG, "Already selected.");
return;
}
+ // Notify the adapter to highlight the selected item.
+ mAdapter.setSelectedPosition(position);
mSelectedDevice = requireNonNull(selectedDevice);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index 198b778..e5513b0 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -15,73 +15,84 @@
*/
package com.android.companiondevicemanager;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
-
/**
* Adapter for the list of "found" devices.
*/
-class DeviceListAdapter extends BaseAdapter implements Observer<List<DeviceFilterPair<?>>> {
+class DeviceListAdapter extends RecyclerView.Adapter<DeviceListAdapter.ViewHolder> implements
+ Observer<List<DeviceFilterPair<?>>> {
+ public int mSelectedPosition = RecyclerView.NO_POSITION;
+
private final Context mContext;
// List if pairs (display name, address)
private List<DeviceFilterPair<?>> mDevices;
- DeviceListAdapter(Context context) {
+ private OnItemClickListener mListener;
+
+ private static final int TYPE_WIFI = 0;
+ private static final int TYPE_BT = 1;
+
+ DeviceListAdapter(Context context, OnItemClickListener listener) {
mContext = context;
+ mListener = listener;
}
- @Override
- public int getCount() {
- return mDevices != null ? mDevices.size() : 0;
- }
-
- @Override
public DeviceFilterPair<?> getItem(int position) {
return mDevices.get(position);
}
@Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.list_item_device, parent, false);
+ ViewHolder viewHolder = new ViewHolder(view);
+ if (viewType == TYPE_WIFI) {
+ viewHolder.mImageView.setImageDrawable(getIcon(
+ com.android.internal.R.drawable.ic_wifi_signal_3));
+ } else {
+ viewHolder.mImageView.setImageDrawable(getIcon(
+ android.R.drawable.stat_sys_data_bluetooth));
+ }
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ holder.itemView.setSelected(mSelectedPosition == position);
+ holder.mTextView.setText(mDevices.get(position).getDisplayName());
+ holder.itemView.setOnClickListener(v -> mListener.onItemClick(position));
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return isWifiDevice(position) ? TYPE_WIFI : TYPE_BT;
+ }
+
+ @Override
public long getItemId(int position) {
return position;
}
@Override
- public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
- final View view = convertView != null
- ? convertView
- : LayoutInflater.from(mContext).inflate(R.layout.list_item_device, parent, false);
-
- final DeviceFilterPair<?> item = getItem(position);
- bindView(view, item);
-
- return view;
+ public int getItemCount() {
+ return mDevices != null ? mDevices.size() : 0;
}
- private void bindView(@NonNull View view, DeviceFilterPair<?> item) {
- final TextView textView = view.findViewById(android.R.id.text1);
- textView.setText(item.getDisplayName());
-
- final ImageView iconView = view.findViewById(android.R.id.icon);
-
- // TODO(b/211417476): Set either Bluetooth or WiFi icon.
- iconView.setVisibility(View.GONE);
- // final int iconRes = isBt ? android.R.drawable.stat_sys_data_bluetooth
- // : com.android.internal.R.drawable.ic_wifi_signal_3;
- // final Drawable icon = getTintedIcon(mResources, iconRes);
- // iconView.setImageDrawable(icon);
+ public void setSelectedPosition(int position) {
+ mSelectedPosition = position;
}
@Override
@@ -89,4 +100,28 @@
mDevices = deviceFilterPairs;
notifyDataSetChanged();
}
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ private TextView mTextView;
+ private ImageView mImageView;
+ ViewHolder(View itemView) {
+ super(itemView);
+ mTextView = itemView.findViewById(android.R.id.text1);
+ mImageView = itemView.findViewById(android.R.id.icon);
+ }
+ }
+
+ private boolean isWifiDevice(int position) {
+ return mDevices.get(position).getDevice() instanceof android.net.wifi.ScanResult;
+ }
+
+ private Drawable getIcon(int resId) {
+ Drawable icon = mContext.getResources().getDrawable(resId, null);
+ icon.setTint(Color.DKGRAY);
+ return icon;
+ }
+
+ public interface OnItemClickListener {
+ void onItemClick(int position);
+ }
}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index 1a955c4..72243f9 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -146,7 +146,7 @@
* @param iface the name of the interface.
* @param state the current state of the interface, or {@link #STATE_ABSENT} if the
* interface was removed.
- * @param role whether the interface is in the client mode or server mode.
+ * @param role whether the interface is in client mode or server mode.
* @param configuration the current IP configuration of the interface.
* @hide
*/
diff --git a/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp b/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp
index f8a8168..39cbaf7 100644
--- a/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/packages/ConnectivityT/service/jni/com_android_server_net_NetworkStatsService.cpp
@@ -102,15 +102,10 @@
}
}
-static int deleteTagData(JNIEnv* /* env */, jclass /* clazz */, jint uid) {
- return qtaguid_deleteTagData(0, uid);
-}
-
static const JNINativeMethod gMethods[] = {
{"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
{"nativeGetUidStat", "(II)J", (void*)getUidStat},
- {"nativeDeleteTagData", "(I)I", (void*)deleteTagData},
};
int register_android_server_net_NetworkStatsService(JNIEnv* env) {
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index e8b3d4c..ef9ebb5 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -51,6 +51,7 @@
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.os.Trace.TRACE_TAG_NETWORK;
+import static android.system.OsConstants.ENOENT;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
@@ -122,7 +123,6 @@
import android.service.NetworkInterfaceProto;
import android.service.NetworkStatsServiceDumpProto;
import android.system.ErrnoException;
-import android.system.Os;
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionPlan;
import android.text.TextUtils;
@@ -221,6 +221,14 @@
// This is current path but may be changed soon.
private static final String UID_COUNTERSET_MAP_PATH =
"/sys/fs/bpf/map_netd_uid_counterset_map";
+ private static final String COOKIE_TAG_MAP_PATH =
+ "/sys/fs/bpf/map_netd_cookie_tag_map";
+ private static final String APP_UID_STATS_MAP_PATH =
+ "/sys/fs/bpf/map_netd_app_uid_stats_map";
+ private static final String STATS_MAP_A_PATH =
+ "/sys/fs/bpf/map_netd_stats_map_A";
+ private static final String STATS_MAP_B_PATH =
+ "/sys/fs/bpf/map_netd_stats_map_B";
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
@@ -348,6 +356,10 @@
*/
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
private final IBpfMap<U32, U8> mUidCounterSetMap;
+ private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
+ private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
+ private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
+ private final IBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap;
/** Data layer operation counters for splicing into other structures. */
private NetworkStats mUidOperations = new NetworkStats(0L, 10);
@@ -481,6 +493,10 @@
mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
mInterfaceMapUpdater.start();
mUidCounterSetMap = mDeps.getUidCounterSetMap();
+ mCookieTagMap = mDeps.getCookieTagMap();
+ mStatsMapA = mDeps.getStatsMapA();
+ mStatsMapB = mDeps.getStatsMapB();
+ mAppUidStatsMap = mDeps.getAppUidStatsMap();
}
/**
@@ -554,8 +570,48 @@
}
}
- public TagStatsDeleter getTagStatsDeleter() {
- return NetworkStatsService::nativeDeleteTagData;
+ /** Gets the cookie tag map */
+ public IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
+ try {
+ return new BpfMap<CookieTagMapKey, CookieTagMapValue>(COOKIE_TAG_MAP_PATH,
+ BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot create cookie tag map: " + e);
+ return null;
+ }
+ }
+
+ /** Gets stats map A */
+ public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapA() {
+ try {
+ return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_A_PATH,
+ BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot create stats map A: " + e);
+ return null;
+ }
+ }
+
+ /** Gets stats map B */
+ public IBpfMap<StatsMapKey, StatsMapValue> getStatsMapB() {
+ try {
+ return new BpfMap<StatsMapKey, StatsMapValue>(STATS_MAP_B_PATH,
+ BpfMap.BPF_F_RDWR, StatsMapKey.class, StatsMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot create stats map B: " + e);
+ return null;
+ }
+ }
+
+ /** Gets the uid stats map */
+ public IBpfMap<UidStatsMapKey, StatsMapValue> getAppUidStatsMap() {
+ try {
+ return new BpfMap<UidStatsMapKey, StatsMapValue>(APP_UID_STATS_MAP_PATH,
+ BpfMap.BPF_F_RDWR, UidStatsMapKey.class, StatsMapValue.class);
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Cannot create app uid stats map: " + e);
+ return null;
+ }
}
}
@@ -1802,6 +1858,63 @@
currentTime);
}
+ // deleteKernelTagData can ignore ENOENT; otherwise we should log an error
+ private void logErrorIfNotErrNoent(final ErrnoException e, final String msg) {
+ if (e.errno != ENOENT) Log.e(TAG, msg, e);
+ }
+
+ private <K extends StatsMapKey, V extends StatsMapValue> void deleteStatsMapTagData(
+ IBpfMap<K, V> statsMap, int uid) {
+ try {
+ statsMap.forEach((key, value) -> {
+ if (key.uid == uid) {
+ try {
+ statsMap.deleteEntry(key);
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete data(uid = " + key.uid + ")");
+ }
+ }
+ });
+ } catch (ErrnoException e) {
+ Log.e(TAG, "FAILED to delete tag data from stats map", e);
+ }
+ }
+
+ /**
+ * Deletes uid tag data from CookieTagMap, StatsMapA, StatsMapB, and UidStatsMap
+ * @param uid
+ */
+ private void deleteKernelTagData(int uid) {
+ try {
+ mCookieTagMap.forEach((key, value) -> {
+ if (value.uid == uid) {
+ try {
+ mCookieTagMap.deleteEntry(key);
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete data(cookie = " + key + ")");
+ }
+ }
+ });
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to delete tag data from cookie tag map", e);
+ }
+
+ deleteStatsMapTagData(mStatsMapA, uid);
+ deleteStatsMapTagData(mStatsMapB, uid);
+
+ try {
+ mUidCounterSetMap.deleteEntry(new U32(uid));
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete tag data from uid counter set map");
+ }
+
+ try {
+ mAppUidStatsMap.deleteEntry(new UidStatsMapKey(uid));
+ } catch (ErrnoException e) {
+ logErrorIfNotErrNoent(e, "Failed to delete tag data from app uid stats map");
+ }
+ }
+
/**
* Clean up {@link #mUidRecorder} after UID is removed.
*/
@@ -1817,10 +1930,7 @@
// Clear kernel stats associated with UID
for (int uid : uids) {
- final int ret = mDeps.getTagStatsDeleter().deleteTagData(uid);
- if (ret < 0) {
- Log.w(TAG, "problem clearing counters for uid " + uid + ": " + Os.strerror(-ret));
- }
+ deleteKernelTagData(uid);
}
}
@@ -2402,12 +2512,4 @@
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
private static native long nativeGetUidStat(int uid, int type);
-
- // TODO: use BpfNetMaps to delete tag data and remove this.
- @VisibleForTesting
- interface TagStatsDeleter {
- int deleteTagData(int uid);
- }
-
- private static native int nativeDeleteTagData(int uid);
}
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index fc82b79..2569198 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -14,6 +14,8 @@
static_libs: [
"androidx.annotation_annotation",
+ "androidx.core_core",
+ "windowExtLib",
"SettingsLibUtils",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
index 7f17d26..44b3b4e 100644
--- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
+++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
@@ -16,8 +16,14 @@
package com.android.settingslib.activityembedding;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.core.os.BuildCompat;
+import androidx.window.embedding.SplitController;
import com.android.settingslib.utils.BuildCompatUtils;
@@ -44,6 +50,33 @@
return false;
}
+ /**
+ * Whether current activity is embedded in the Settings app or not.
+ */
+ public static boolean isActivityEmbedded(Activity activity) {
+ return SplitController.getInstance().isActivityEmbedded(activity);
+ }
+
+ /**
+ * Whether current activity is suggested to show back button or not.
+ */
+ public static boolean shouldHideBackButton(Activity activity, boolean isSecondaryLayerPage) {
+ if (!BuildCompat.isAtLeastT()) {
+ return false;
+ }
+ if (!isSecondaryLayerPage) {
+ return false;
+ }
+ final String shouldHideBackButton = Settings.Global.getString(activity.getContentResolver(),
+ "settings_hide_secondary_page_back_button_in_two_pane");
+
+ if (TextUtils.isEmpty(shouldHideBackButton)
+ || TextUtils.equals("true", shouldHideBackButton)) {
+ return isActivityEmbedded(activity);
+ }
+ return false;
+ }
+
private ActivityEmbeddingUtils() {
}
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 684f4de..7a5ea47 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -48,7 +48,6 @@
"SettingsLibCollapsingToolbarBaseActivity",
"SettingsLibTwoTargetPreference",
"SettingsLibSettingsTransition",
- "SettingsLibActivityEmbedding",
"SettingsLibButtonPreference",
"setupdesign",
],
diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml b/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml
deleted file mode 100644
index 139cd95..0000000
--- a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_background.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 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.
- -->
-
-<ripple
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:priv-android="http://schemas.android.com/apk/prv/res/android"
- android:color="@color/ripple_color">
-
- <item android:id="@android:id/background">
- <layer-list android:paddingMode="stack">
- <item
- android:top="8dp"
- android:bottom="8dp">
-
- <shape>
- <corners android:radius="28dp"/>
- <solid android:color="?priv-android:attr/colorAccentPrimary"/>
- <size android:height="@dimen/spinner_height"/>
- </shape>
- </item>
-
- <item
- android:gravity="center|end"
- android:width="18dp"
- android:height="18dp"
- android:end="8dp"
- android:drawable="@drawable/arrow_drop_down"/>
- </layer-list>
- </item>
-</ripple>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_dropdown_view.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v31/settings_spinner_dropdown_view.xml
similarity index 78%
rename from packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_dropdown_view.xml
rename to packages/SettingsLib/SettingsSpinner/res/layout-v31/settings_spinner_dropdown_view.xml
index a342c84..cea1133 100644
--- a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_dropdown_view.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v31/settings_spinner_dropdown_view.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2021 The Android Open Source Project
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,8 +18,7 @@
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- style="@style/SettingsSpinnerTitleBar"
+ style="@style/SettingsSpinnerDropdown"
android:gravity="center_vertical"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/settings_spinner_dropdown_background"/>
+ android:layout_height="wrap_content"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml b/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml
index 7d5b6db..4c75344 100644
--- a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml
@@ -22,7 +22,7 @@
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
- <com.android.settingslib.widget.settingsspinner.SettingsSpinner
+ <Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SettingsLib/SettingsSpinner/res/values-night/colors.xml b/packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml
similarity index 72%
rename from packages/SettingsLib/SettingsSpinner/res/values-night/colors.xml
rename to packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml
index abcf822..8fda876 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values-night/colors.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/values-v31/colors.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
+<!-- Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,5 +15,6 @@
-->
<resources>
- <color name="ripple_color">@*android:color/material_grey_900</color>
+ <color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
+ <color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color>
</resources>
diff --git a/packages/SettingsLib/SettingsSpinner/res/values-v31/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values-v31/styles.xml
new file mode 100644
index 0000000..fc3ec43
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/values-v31/styles.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <style name="SettingsSpinnerTitleBar">
+ <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
+ <item name="android:textColor">@color/settingslib_spinner_title_color</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:minHeight">@dimen/spinner_height</item>
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">36dp</item>
+ <item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item>
+ <item name="android:paddingBottom">@dimen/spinner_padding_top_or_bottom</item>
+ </style>
+
+ <style name="SettingsSpinnerDropdown">
+ <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
+ <item name="android:textColor">@color/settingslib_spinner_dropdown_color</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:minHeight">@dimen/spinner_height</item>
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">36dp</item>
+ <item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item>
+ <item name="android:paddingBottom">@dimen/spinner_padding_top_or_bottom</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/SettingsSpinner/res/values/colors.xml b/packages/SettingsLib/SettingsSpinner/res/values/colors.xml
deleted file mode 100644
index 799b35e..0000000
--- a/packages/SettingsLib/SettingsSpinner/res/values/colors.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <color name="ripple_color">?android:attr/colorControlHighlight</color>
-</resources>
diff --git a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
index f665f38..8ea1f9a 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
@@ -18,10 +18,8 @@
<resources>
<style name="SettingsSpinnerTitleBar">
<item name="android:textAppearance">?android:attr/textAppearanceButton</item>
- <item name="android:textColor">@android:color/black</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">marquee</item>
- <item name="android:minHeight">@dimen/spinner_height</item>
<item name="android:paddingStart">16dp</item>
<item name="android:paddingEnd">36dp</item>
<item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item>
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinnerAdapter.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
similarity index 77%
rename from packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinnerAdapter.java
rename to packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
index 83da512..2611207 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinnerAdapter.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package com.android.settingslib.widget.settingsspinner;
+package com.android.settingslib.widget;
import android.content.Context;
+import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import com.android.settingslib.widget.R;
-
/**
- * An ArrayAdapter which was used by {@link SettingsSpinner} with settings style.
+ * An ArrayAdapter which was used by Spinner with settings style.
+ * @param <T> the data type to be loaded.
*/
public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> {
@@ -43,7 +43,7 @@
public SettingsSpinnerAdapter(Context context) {
super(context, DEFAULT_RESOURCE);
- setDropDownViewResource(DFAULT_DROPDOWN_RESOURCE);
+ setDropDownViewResource(getDropdownResource());
mDefaultInflater = LayoutInflater.from(context);
}
@@ -59,6 +59,11 @@
* drop down view.
*/
public View getDefaultDropDownView(int position, View convertView, ViewGroup parent) {
- return mDefaultInflater.inflate(DFAULT_DROPDOWN_RESOURCE, parent, false /* attachToRoot */);
+ return mDefaultInflater.inflate(getDropdownResource(), parent, false /* attachToRoot */);
+ }
+
+ private int getDropdownResource() {
+ return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ ? DFAULT_DROPDOWN_RESOURCE : android.R.layout.simple_spinner_dropdown_item;
}
}
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
index d993e44..6952875 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
@@ -20,16 +20,14 @@
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
+import android.widget.Spinner;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceViewHolder;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinner;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
-
/**
- * This preference uses SettingsSpinner & SettingsSpinnerAdapter which provide default layouts for
+ * This preference uses Spinner & SettingsSpinnerAdapter which provide default layouts for
* both view and drop down view of the Spinner.
*/
public class SettingsSpinnerPreference extends Preference implements OnPreferenceClickListener {
@@ -113,7 +111,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- final SettingsSpinner spinner = (SettingsSpinner) holder.findViewById(R.id.spinner);
+ final Spinner spinner = (Spinner) holder.findViewById(R.id.spinner);
spinner.setAdapter(mAdapter);
spinner.setSelection(mPosition);
spinner.setOnItemSelectedListener(mOnSelectedListener);
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinner.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinner.java
deleted file mode 100644
index 14286fa..0000000
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/settingsspinner/SettingsSpinner.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2018 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.settingslib.widget.settingsspinner;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.widget.Spinner;
-
-import androidx.annotation.RequiresApi;
-
-import com.android.settingslib.widget.R;
-
-/**
- * A {@link Spinner} with settings style.
- *
- * The items in the SettingsSpinner come from the {@link SettingsSpinnerAdapter} associated with
- * this view.
- */
-public class SettingsSpinner extends Spinner {
-
- /**
- * Constructs a new SettingsSpinner with the given context's theme.
- * And it also set a background resource with settings style.
- *
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- */
- public SettingsSpinner(Context context) {
- super(context);
- setBackgroundResource(R.drawable.settings_spinner_background);
- }
-
- /**
- * Constructs a new SettingsSpinner with the given context's theme and the supplied
- * mode of displaying choices. <code>mode</code> may be one of
- * {@link Spinner#MODE_DIALOG} or {@link Spinner#MODE_DROPDOWN}.
- * And it also set a background resource with settings style.
- *
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- * @param mode Constant describing how the user will select choices from
- * the spinner.
- *
- * @see Spinner#MODE_DIALOG
- * @see Spinner#MODE_DROPDOWN
- */
- public SettingsSpinner(Context context, int mode) {
- super(context, mode);
- setBackgroundResource(R.drawable.settings_spinner_background);
- }
-
- /**
- * Constructs a new SettingsSpinner with the given context's theme and the supplied
- * attribute set.
- * And it also set a background resource with settings style.
- *
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- * @param attrs The attributes of the XML tag that is inflating the view.
- */
- public SettingsSpinner(Context context, AttributeSet attrs) {
- super(context, attrs);
- setBackgroundResource(R.drawable.settings_spinner_background);
- }
-
- /**
- * Constructs a new SettingsSpinner with the given context's theme, the supplied
- * attribute set, and default style attribute.
- * And it also set a background resource with settings style.
- *
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- * @param attrs The attributes of the XML tag that is inflating the view.
- * @param defStyleAttr An attribute in the current theme that contains a
- * reference to a style resource that supplies default
- * values for the view. Can be 0 to not look for
- * defaults.
- */
- public SettingsSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setBackgroundResource(R.drawable.settings_spinner_background);
- }
-
- /**
- * Constructs a new SettingsSpinner with the given context's theme, the supplied
- * attribute set, and default styles. <code>mode</code> may be one of
- * {@link Spinner#MODE_DIALOG} or {@link Spinner#MODE_DROPDOWN} and determines how the
- * user will select choices from the spinner.
- * And it also set a background resource with settings style.
- *
- * @param context The Context the view is running in, through which it can
- * access the current theme, resources, etc.
- * @param attrs The attributes of the XML tag that is inflating the view.
- * @param defStyleAttr An attribute in the current theme that contains a
- * reference to a style resource that supplies default
- * values for the view. Can be 0 to not look for
- * defaults.
- * @param defStyleRes A resource identifier of a style resource that
- * supplies default values for the view, used only if
- * defStyleAttr is 0 or can not be found in the theme.
- * Can be 0 to not look for defaults.
- * @param mode Constant describing how the user will select choices from
- * the spinner.
- *
- * @see Spinner#MODE_DIALOG
- * @see Spinner#MODE_DROPDOWN
- */
- @RequiresApi(Build.VERSION_CODES.M)
- public SettingsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
- int mode) {
- super(context, attrs, defStyleAttr, defStyleRes, mode, null);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- setDropDownVerticalOffset(getMeasuredHeight() - (int) getContext().getResources()
- .getDimension(R.dimen.spinner_padding_top_or_bottom));
- }
-}
diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_arrow_drop_down.xml
similarity index 93%
rename from packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_arrow_drop_down.xml
index 0544526..770a69d 100644
--- a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_arrow_drop_down.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2018 The Android Open Source Project
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml
index a4c780b..ae63928 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_progress_horizontal.xml
@@ -27,11 +27,12 @@
<item
android:id="@android:id/progress">
- <clip>
+ <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true">
<shape>
<corners android:radius="8dp" />
<solid android:color="?android:attr/textColorPrimary" />
+ <size android:width="8dp"/>
</shape>
- </clip>
+ </scale>
</item>
</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_background.xml
new file mode 100644
index 0000000..e1764af
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_background.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:color="@color/settingslib_ripple_color">
+
+<item android:id="@android:id/background">
+ <layer-list android:paddingMode="stack">
+ <item
+ android:top="8dp"
+ android:bottom="8dp">
+
+ <shape>
+ <corners android:radius="28dp"/>
+ <solid android:color="@android:color/system_accent1_100"/>
+ <size android:height="@dimen/settingslib_spinner_height"/>
+ </shape>
+ </item>
+
+ <item
+ android:gravity="center|end"
+ android:width="18dp"
+ android:height="18dp"
+ android:end="12dp"
+ android:drawable="@drawable/settingslib_arrow_drop_down"/>
+ </layer-list>
+</item>
+</ripple>
diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_dropdown_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_dropdown_background.xml
similarity index 75%
rename from packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_dropdown_background.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_dropdown_background.xml
index aa451ae..056fb82 100644
--- a/packages/SettingsLib/SettingsSpinner/res/drawable/settings_spinner_dropdown_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v31/settingslib_spinner_dropdown_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2018 The Android Open Source Project
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,12 +17,12 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:priv-android="http://schemas.android.com/apk/prv/res/android"
- android:color="@color/ripple_color">
+ android:color="@color/settingslib_ripple_color">
<item android:id="@android:id/background">
<shape>
- <solid android:color="?priv-android:attr/colorAccentSecondary"/>
+ <corners android:radius="10dp"/>
+ <solid android:color="@android:color/system_accent2_100"/>
</shape>
</item>
</ripple>
diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml
similarity index 80%
copy from packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
copy to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml
index 0544526..6ed215d 100644
--- a/packages/SettingsLib/SettingsSpinner/res/drawable/arrow_drop_down.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_arrow_drop_down.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2018 The Android Open Source Project
+ Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="18"
- android:viewportHeight="18"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
android:width="24dp"
android:height="24dp">
<path
android:pathData="M7 10l5 5 5 -5z"
- android:fillColor="@android:color/black"/>
+ android:fillColor="?android:attr/textColorPrimary"/>
</vector>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml
new file mode 100644
index 0000000..7466712
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_spinner_background.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:color="@color/settingslib_ripple_color">
+
+ <item android:id="@android:id/background">
+ <layer-list android:paddingMode="stack">
+ <item
+ android:top="8dp"
+ android:bottom="8dp">
+
+ <shape>
+ <corners
+ android:radius="20dp"/>
+ <solid
+ android:color="?android:attr/colorPrimary"/>
+ <stroke
+ android:color="#1f000000"
+ android:width="1dp"/>
+ <size
+ android:height="32dp"/>
+ </shape>
+ </item>
+
+ <item
+ android:gravity="center|end"
+ android:width="24dp"
+ android:height="24dp"
+ android:end="4dp"
+ android:drawable="@drawable/settingslib_arrow_drop_down"/>
+ </layer-list>
+ </item>
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 3f2b8ac..77c4533 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -47,4 +47,6 @@
<color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_200</color>
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
+
+ <color name="settingslib_ripple_color">@color/settingslib_material_grey_900</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index ec3c336..6adb789 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -69,4 +69,7 @@
<color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
+
+ <color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
+ <color name="settingslib_material_grey_900">#ff212121</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
index 11546c8..29fdab1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -25,4 +25,6 @@
<dimen name="settingslib_listPreferredItemPaddingStart">24dp</dimen>
<!-- Right padding of the preference -->
<dimen name="settingslib_listPreferredItemPaddingEnd">24dp</dimen>
+
+ <dimen name="settingslib_spinner_height">36dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 8e7226b..9d39911 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -45,4 +45,13 @@
<item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item>
<item name="android:scaleY">0.5</item>
</style>
+
+ <style name="Spinner.SettingsLib"
+ parent="android:style/Widget.Material.Spinner">
+ <item name="android:background">@drawable/settingslib_spinner_background</item>
+ <item name="android:popupBackground">@drawable/settingslib_spinner_dropdown_background</item>
+ <item name="android:dropDownVerticalOffset">48dp</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">8dp</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 7bf75bc..e9bbcc78 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -26,6 +26,7 @@
<item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
<item name="android:switchStyle">@style/Switch.SettingsLib</item>
<item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
+ <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
</style>
<!-- Using in SubSettings page including injected settings page -->
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index aaab0f0..fa27bb6 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -26,4 +26,10 @@
<style name="TextAppearance.CategoryTitle.SettingsLib"
parent="@android:style/TextAppearance.DeviceDefault.Medium">
</style>
+
+ <style name="Spinner.SettingsLib"
+ parent="android:style/Widget.Material.Spinner">
+ <item name="android:background">@drawable/settingslib_spinner_background</item>
+ <item name="android:dropDownVerticalOffset">48dp</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
index 2d881d1..8dc0f3c 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -19,6 +19,7 @@
<!-- Only using in Settings application -->
<style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
+ <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
</style>
<!-- Using in SubSettings page including injected settings page -->
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 25d6c555..d893d09 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -22,8 +22,6 @@
<!-- The translation for disappearing security views after having solved them. -->
<dimen name="disappear_y_translation">-32dp</dimen>
- <dimen name="circle_avatar_size">190dp</dimen>
-
<!-- Height of a user icon view -->
<dimen name="user_icon_view_height">24dp</dimen>
<!-- User spinner -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 883e080..f6e3557 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -144,7 +144,7 @@
* Returns a circular icon for a user.
*/
public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
- final int iconSize = UserIconDrawable.getSizeForList(context);
+ final int iconSize = UserIconDrawable.getDefaultSize(context);
if (user.isManagedProfile()) {
Drawable drawable = UserIconDrawable.getManagedUserDrawable(context);
drawable.setBounds(0, 0, iconSize, iconSize);
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
index fc38ada..afd3626 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
@@ -22,7 +22,6 @@
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.UserHandle;
@@ -34,10 +33,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.ArrayList;
import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
import java.util.Set;
/**
@@ -52,12 +48,11 @@
private static DeviceStateRotationLockSettingsManager sSingleton;
private final ContentResolver mContentResolver;
+ private final String[] mDeviceStateRotationLockDefaults;
private final Handler mMainHandler = Handler.getMain();
private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
- private String[] mDeviceStateRotationLockDefaults;
private SparseIntArray mDeviceStateRotationLockSettings;
private SparseIntArray mDeviceStateRotationLockFallbackSettings;
- private List<SettableDeviceState> mSettableDeviceStates;
private DeviceStateRotationLockSettingsManager(Context context) {
mContentResolver = context.getContentResolver();
@@ -78,12 +73,6 @@
return sSingleton;
}
- /** Resets the singleton instance of this class. Only used for testing. */
- @VisibleForTesting
- public static synchronized void resetInstance() {
- sSingleton = null;
- }
-
/** Returns true if device-state based rotation lock settings are enabled. */
public static boolean isDeviceStateRotationLockEnabled(Context context) {
return context.getResources()
@@ -191,12 +180,6 @@
return true;
}
- /** Returns a list of device states and their respective auto-rotation setting availability. */
- public List<SettableDeviceState> getSettableDeviceStates() {
- // Returning a copy to make sure that nothing outside can mutate our internal list.
- return new ArrayList<>(mSettableDeviceStates);
- }
-
private void initializeInMemoryMap() {
String serializedSetting =
Settings.Secure.getStringForUser(
@@ -232,17 +215,6 @@
}
}
- /**
- * Resets the state of the class and saved settings back to the default values provided by the
- * resources config.
- */
- @VisibleForTesting
- public void resetStateForTesting(Resources resources) {
- mDeviceStateRotationLockDefaults =
- resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
- fallbackOnDefaults();
- }
-
private void fallbackOnDefaults() {
loadDefaults();
persistSettings();
@@ -279,7 +251,6 @@
}
private void loadDefaults() {
- mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockSettings = new SparseIntArray(
mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
@@ -300,8 +271,6 @@
+ values.length);
}
}
- boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
- mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
@@ -331,38 +300,4 @@
/** Called whenever the settings have changed. */
void onSettingsChanged();
}
-
- /** Represents a device state and whether it has an auto-rotation setting. */
- public static class SettableDeviceState {
- private final int mDeviceState;
- private final boolean mIsSettable;
-
- SettableDeviceState(int deviceState, boolean isSettable) {
- mDeviceState = deviceState;
- mIsSettable = isSettable;
- }
-
- /** Returns the device state associated with this object. */
- public int getDeviceState() {
- return mDeviceState;
- }
-
- /** Returns whether there is an auto-rotation setting for this device state. */
- public boolean isSettable() {
- return mIsSettable;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SettableDeviceState)) return false;
- SettableDeviceState that = (SettableDeviceState) o;
- return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mDeviceState, mIsSettable);
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java
index e5ea446..7db00f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/CircleFramedDrawable.java
@@ -31,8 +31,6 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import com.android.settingslib.R;
-
/**
* Converts the user avatar icon to a circularly clipped one.
* TODO: Move this to an internal framework class and share with the one in Keyguard.
@@ -49,9 +47,9 @@
public static CircleFramedDrawable getInstance(Context context, Bitmap icon) {
Resources res = context.getResources();
- float iconSize = res.getDimension(R.dimen.circle_avatar_size);
+ int iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size);
- CircleFramedDrawable instance = new CircleFramedDrawable(icon, (int) iconSize);
+ CircleFramedDrawable instance = new CircleFramedDrawable(icon, iconSize);
return instance;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 035fafd..d01f2b4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -49,8 +49,6 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.os.BuildCompat;
-import com.android.settingslib.R;
-
/**
* Converts the user avatar icon to a circularly clipped one with an optional badge and frame
*/
@@ -120,8 +118,9 @@
* @param context
* @return size in pixels
*/
- public static int getSizeForList(Context context) {
- return (int) context.getResources().getDimension(R.dimen.circle_avatar_size);
+ public static int getDefaultSize(Context context) {
+ return context.getResources()
+ .getDimensionPixelSize(com.android.internal.R.dimen.user_icon_size);
}
public UserIconDrawable() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index f8bb38b..5862f60 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,19 +16,17 @@
package com.android.settingslib.users;
-import android.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
import com.android.internal.util.UserIcons;
-import com.android.settingslib.R;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.settingslib.utils.ThreadUtils;
@@ -114,10 +112,10 @@
private void onDefaultIconSelected(int tintColor) {
try {
ThreadUtils.postOnBackgroundThread(() -> {
+ Resources res = mActivity.getResources();
Drawable drawable =
- UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
- Bitmap bitmap = convertToBitmap(drawable,
- (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
+ UserIcons.getDefaultUserIconInColor(res, tintColor);
+ Bitmap bitmap = UserIcons.convertToBitmapAtUserIconSize(res, drawable);
ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
}).get();
@@ -126,14 +124,6 @@
}
}
- private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- icon.setBounds(0, 0, size, size);
- icon.draw(canvas);
- return bitmap;
- }
-
private void onPhotoCropped(final Uri data) {
ThreadUtils.postOnBackgroundThread(() -> {
InputStream imageStream = null;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
deleted file mode 100644
index 8d687b6..0000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.devicestate;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DeviceStateRotationLockSettingsManagerTest {
-
- @Mock private Context mMockContext;
- @Mock private Resources mMockResources;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- Context context = InstrumentationRegistry.getTargetContext();
- when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
- when(mMockContext.getResources()).thenReturn(mMockResources);
- when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
- }
-
- @Test
- public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() {
- when(mMockResources.getStringArray(
- R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
- new String[]{"2:2", "4:0", "1:1", "0:0"});
-
- List<SettableDeviceState> settableDeviceStates =
- DeviceStateRotationLockSettingsManager.getInstance(
- mMockContext).getSettableDeviceStates();
-
- assertThat(settableDeviceStates).containsExactly(
- new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true),
- new SettableDeviceState(/* deviceState= */ 4, /* isSettable= */ false),
- new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ true),
- new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
- ).inOrder();
- }
-}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java
index 53a382a..b81d13d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/SettingsSpinnerPreferenceTest.java
@@ -22,14 +22,12 @@
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.Spinner;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinner;
-import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,7 +40,7 @@
private Context mContext;
private PreferenceViewHolder mViewHolder;
- private SettingsSpinner mSpinner;
+ private Spinner mSpinner;
private SettingsSpinnerPreference mSpinnerPreference;
@Before
@@ -53,7 +51,7 @@
final View rootView = inflater.inflate(mSpinnerPreference.getLayoutResource(),
new LinearLayout(mContext), false /* attachToRoot */);
mViewHolder = PreferenceViewHolder.createInstanceForTests(rootView);
- mSpinner = (SettingsSpinner) mViewHolder.findViewById(R.id.spinner);
+ mSpinner = (Spinner) mViewHolder.findViewById(R.id.spinner);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index d7b366e..bb6b293 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -206,9 +206,8 @@
when(config.isMandatoryCodec()).thenReturn(false);
when(config.getCodecType()).thenReturn(4);
- when(config.getCodecName()).thenReturn("LDAC");
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(
- String.format(KNOWN_CODEC_LABEL, config.getCodecName()));
+ String.format(KNOWN_CODEC_LABEL, "LDAC"));
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index d53a3e8..298ee90 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -28,6 +28,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.content.Context;
+import android.os.Parcel;
import android.os.ParcelUuid;
import org.junit.Before;
@@ -60,9 +61,9 @@
private final static Map<Integer, ParcelUuid> CAP_GROUP2 =
Map.of(2, BluetoothUuid.CAP);
private final BluetoothClass DEVICE_CLASS_1 =
- new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
private final BluetoothClass DEVICE_CLASS_2 =
- new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
@@ -92,6 +93,16 @@
private HearingAidDeviceManager mHearingAidDeviceManager;
private Context mContext;
+ private BluetoothClass createBtClass(int deviceClass) {
+ Parcel p = Parcel.obtain();
+ p.writeInt(deviceClass);
+ p.setDataPosition(0); // reset position of parcel before passing to constructor
+
+ BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
+ p.recycle();
+ return bluetoothClass;
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 7be176a..a8e6075 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -28,6 +28,7 @@
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.os.Parcel;
import org.junit.Before;
import org.junit.Test;
@@ -48,7 +49,7 @@
private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final BluetoothClass DEVICE_CLASS =
- new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
@@ -67,6 +68,16 @@
private HearingAidDeviceManager mHearingAidDeviceManager;
private Context mContext;
+ private BluetoothClass createBtClass(int deviceClass) {
+ Parcel p = Parcel.obtain();
+ p.writeInt(deviceClass);
+ p.setDataPosition(0); // reset position of parcel before passing to constructor
+
+ BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
+ p.recycle();
+ return bluetoothClass;
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
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 6f7f73a..c122a37 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
@@ -31,6 +31,7 @@
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
+import android.os.Parcel;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -65,9 +66,9 @@
private static final String ROUTER_ID_3 = "RouterId_3";
private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
private final BluetoothClass mHeadreeClass =
- new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
private final BluetoothClass mCarkitClass =
- new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO);
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO);
@Mock
private BluetoothDevice mDevice1;
@@ -118,6 +119,16 @@
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private PhoneMediaDevice mPhoneMediaDevice;
+ private BluetoothClass createBtClass(int deviceClass) {
+ Parcel p = Parcel.obtain();
+ p.writeInt(deviceClass);
+ p.setDataPosition(0); // reset position of parcel before passing to constructor
+
+ BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
+ p.recycle();
+ return bluetoothClass;
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
index 3b7fbc7..c7e96bc 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
@@ -69,7 +69,7 @@
}
@Implementation
- protected boolean removeActiveDevice(@BluetoothAdapter.ActiveDeviceUse int profiles) {
+ protected boolean removeActiveDevice(int profiles) {
if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL
&& profiles != ACTIVE_DEVICE_ALL) {
return false;
@@ -78,8 +78,7 @@
}
@Implementation
- protected boolean setActiveDevice(BluetoothDevice device,
- @BluetoothAdapter.ActiveDeviceUse int profiles) {
+ protected boolean setActiveDevice(BluetoothDevice device, int profiles) {
if (device == null) {
return false;
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 741fe4f..f0b180e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -536,6 +536,9 @@
<uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
+ <!-- Permission needed for CTS test - ConcurrencyTest#testP2pExternalApprover
+ P2P external approver API sets require MANAGE_WIFI_AUTO_JOIN permission. -->
+ <uses-permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN" />
<!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
<uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
@@ -655,6 +658,9 @@
<!-- Permission required for CTS test - CaptioningManagerTest -->
<uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
+ <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+ <uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b1cfb11..8cc9e00 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -112,6 +112,7 @@
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
+ "com.google.android.material_material",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
"iconloader_base",
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 46adfeb..f7bcf1f 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -39,5 +39,5 @@
],
manifest: "AndroidManifest.xml",
- kotlincflags: ["-Xjvm-default=enable"],
+ kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 4540b77..c3f6a5d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -48,9 +48,16 @@
* nicely into the starting window.
*/
class ActivityLaunchAnimator(
- private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS)
+ /** The animator used when animating a View into an app. */
+ private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+
+ /** The animator used when animating a Dialog into an app. */
+ // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
+ // TIMINGS.contentBeforeFadeOutDuration.
+ private val dialogToAppAnimator: LaunchAnimator = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
) {
companion object {
+ /** The timings when animating a View into an app. */
@JvmField
val TIMINGS = LaunchAnimator.Timings(
totalDuration = 500L,
@@ -60,6 +67,17 @@
contentAfterFadeInDuration = 183L
)
+ /**
+ * The timings when animating a Dialog into an app. We need to wait at least 200ms before
+ * showing the app (which is under the dialog window) so that the dialog window dim is fully
+ * faded out, to avoid flicker.
+ */
+ val DIALOG_TIMINGS = TIMINGS.copy(
+ contentBeforeFadeOutDuration = 200L,
+ contentAfterFadeInDelay = 200L
+ )
+
+ /** The interpolators when animating a View or a dialog into an app. */
val INTERPOLATORS = LaunchAnimator.Interpolators(
positionInterpolator = Interpolators.EMPHASIZED,
positionXInterpolator = createPositionXInterpolator(),
@@ -298,10 +316,17 @@
}
/**
+ * Whether this controller is controlling a dialog launch. This will be used to adapt the
+ * timings, making sure we don't show the app until the dialog dim had the time to fade out.
+ */
+ // TODO(b/218989950): Remove this.
+ val isDialogLaunch: Boolean
+ get() = false
+
+ /**
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
- @JvmDefault
fun onIntentStarted(willAnimate: Boolean) {}
/**
@@ -309,7 +334,6 @@
* this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
* before the cancellation.
*/
- @JvmDefault
fun onLaunchAnimationCancelled() {}
}
@@ -317,7 +341,9 @@
inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
- private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
+ private val transactionApplierView =
+ controller.openingWindowSyncView ?: controller.launchContainer
+ private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView)
private val matrix = Matrix()
private val invertMatrix = Matrix()
@@ -405,6 +431,13 @@
val callback = this@ActivityLaunchAnimator.callback!!
val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
+ // Make sure we use the modified timings when animating a dialog into an app.
+ val launchAnimator = if (controller.isDialogLaunch) {
+ dialogToAppAnimator
+ } else {
+ launchAnimator
+ }
+
// TODO(b/184121838): We should somehow get the top and bottom radius of the window
// instead of recomputing isExpandingFullyAbove here.
val isExpandingFullyAbove =
@@ -440,19 +473,29 @@
progress: Float,
linearProgress: Float
) {
- applyStateToWindow(window, state)
+ // Apply the state to the window only if it is visible, i.e. when the expanding
+ // view is *not* visible.
+ if (!state.visible) {
+ applyStateToWindow(window, state)
+ }
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
+
listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
}
}
- // We draw a hole when the additional layer is fading out to reveal the opening window.
animation = launchAnimator.startAnimation(
controller, endState, windowBackgroundColor, drawHole = true)
}
private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) {
+ if (transactionApplierView.viewRootImpl == null) {
+ // If the view root we synchronize with was detached, don't apply any transaction
+ // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+ return
+ }
+
val screenBounds = window.screenSpaceBounds
val centerX = (screenBounds.left + screenBounds.right) / 2f
val centerY = (screenBounds.top + screenBounds.bottom) / 2f
@@ -510,6 +553,12 @@
state: LaunchAnimator.State,
linearProgress: Float
) {
+ if (transactionApplierView.viewRootImpl == null) {
+ // If the view root we synchronize with was detached, don't apply any transaction
+ // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+ return
+ }
+
val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 3051d80..a3c5649 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -19,7 +19,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
-import android.app.ActivityManager
import android.app.Dialog
import android.graphics.Color
import android.graphics.Rect
@@ -28,12 +27,12 @@
import android.util.Log
import android.util.MathUtils
import android.view.GhostView
-import android.view.SurfaceControl
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewRootImpl
+import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
import kotlin.math.roundToInt
@@ -42,12 +41,17 @@
/**
* A class that allows dialogs to be started in a seamless way from a view that is transforming
* nicely into the starting dialog.
+ *
+ * This animator also allows to easily animate a dialog into an activity.
+ *
+ * @see showFromView
+ * @see showFromDialog
+ * @see createActivityLaunchController
*/
class DialogLaunchAnimator @JvmOverloads constructor(
private val dreamManager: IDreamManager,
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
- // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed
- private var forceDisableSynchronization: Boolean = ActivityManager.isLowRamDeviceStatic()
+ private val isForTesting: Boolean = false
) {
private companion object {
private val TIMINGS = ActivityLaunchAnimator.TIMINGS
@@ -113,7 +117,7 @@
dialog = dialog,
animateBackgroundBoundsChange,
animatedParent,
- forceDisableSynchronization
+ isForTesting
)
openedDialogs.add(animatedDialog)
@@ -141,6 +145,100 @@
}
/**
+ * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the
+ * dialog that contains [View]. Note that the dialog must have been show using [showFromView]
+ * and be currently showing, otherwise this will return null.
+ *
+ * The returned controller will take care of dismissing the dialog at the right time after the
+ * activity started, when the dialog to app animation is done (or when it is cancelled). If this
+ * method returns null, then the dialog won't be dismissed.
+ *
+ * @param view any view inside the dialog to animate.
+ */
+ @JvmOverloads
+ fun createActivityLaunchController(
+ view: View,
+ cujType: Int? = null
+ ): ActivityLaunchAnimator.Controller? {
+ val animatedDialog = openedDialogs
+ .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
+ ?: return null
+
+ // At this point, we know that the intent of the caller is to dismiss the dialog to show
+ // an app, so we disable the exit animation into the touch surface because we will never
+ // want to run it anyways.
+ animatedDialog.exitAnimationDisabled = true
+
+ val dialog = animatedDialog.dialog
+
+ // Don't animate if the dialog is not showing.
+ if (!dialog.isShowing) {
+ return null
+ }
+
+ val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null
+ val controller =
+ ActivityLaunchAnimator.Controller.fromView(dialogContentWithBackground, cujType)
+ ?: return null
+
+ // Wrap the controller into one that will instantly dismiss the dialog when the animation is
+ // done or dismiss it normally (fading it out) if the animation is cancelled.
+ return object : ActivityLaunchAnimator.Controller by controller {
+ override val isDialogLaunch = true
+
+ override fun onIntentStarted(willAnimate: Boolean) {
+ controller.onIntentStarted(willAnimate)
+
+ if (!willAnimate) {
+ dialog.dismiss()
+ }
+ }
+
+ override fun onLaunchAnimationCancelled() {
+ controller.onLaunchAnimationCancelled()
+ enableDialogDismiss()
+ dialog.dismiss()
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ controller.onLaunchAnimationStart(isExpandingFullyAbove)
+
+ // Make sure the dialog is not dismissed during the animation.
+ disableDialogDismiss()
+
+ // If this dialog was shown from a cascade of other dialogs, make sure those ones
+ // are dismissed too.
+ animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
+
+ // Remove the dim.
+ dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+
+ // Hide the dialog then dismiss it to instantly dismiss it without playing the
+ // animation.
+ dialog.hide()
+ enableDialogDismiss()
+ dialog.dismiss()
+ }
+
+ private fun disableDialogDismiss() {
+ dialog.setDismissOverride { /* Do nothing */ }
+ }
+
+ private fun enableDialogDismiss() {
+ // We don't set the override to null given that [AnimatedDialog.OnDialogDismissed]
+ // will still properly dismiss the dialog but will also make sure to clean up
+ // everything (like making sure that the touched view that triggered the dialog is
+ // made VISIBLE again).
+ dialog.setDismissOverride(animatedDialog::onDialogDismissed)
+ }
+ }
+ }
+
+ /**
* Ensure that all dialogs currently shown won't animate into their touch surface when
* dismissed.
*
@@ -358,6 +456,21 @@
// Make sure the dialog is visible instantly and does not do any window animation.
window.attributes.windowAnimations = R.style.Animation_LaunchAnimation
+ // Ensure that the animation is not clipped by the display cut-out when animating this
+ // dialog into an app.
+ window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ window.attributes = window.attributes
+
+ // We apply the insets ourselves to make sure that the paddings are set on the correct
+ // View.
+ window.setDecorFitsSystemWindows(false)
+ val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup)
+ viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets ->
+ val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout())
+ view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
+ WindowInsets.CONSUMED
+ }
+
// Start the animation once the background view is properly laid out.
dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
override fun onLayoutChange(
@@ -421,45 +534,12 @@
* (or inversely, removed from the UI when the touch surface is made visible).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
- if (forceDisableSynchronization ||
- !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
- !decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
- // No need to synchronize if either the touch surface or dialog view is not attached
- // to a window.
+ if (forceDisableSynchronization) {
then()
return
}
- // Consume the next frames of both view roots to make sure the ghost view is drawn at
- // exactly the same time as when the touch surface is made invisible.
- var remainingTransactions = 0
- val mergedTransactions = SurfaceControl.Transaction()
-
- fun onTransaction(transaction: SurfaceControl.Transaction?) {
- remainingTransactions--
- transaction?.let { mergedTransactions.merge(it) }
-
- if (remainingTransactions == 0) {
- mergedTransactions.apply()
- then()
- }
- }
-
- fun consumeNextDraw(viewRootImpl: ViewRootImpl) {
- if (viewRootImpl.consumeNextDraw(::onTransaction)) {
- remainingTransactions++
-
- // Make sure we trigger a traversal.
- viewRootImpl.view.invalidate()
- }
- }
-
- consumeNextDraw(touchSurface.viewRootImpl)
- consumeNextDraw(decorView.viewRootImpl)
-
- if (remainingTransactions == 0) {
- then()
- }
+ ViewRootSync.synchronizeNextDraw(touchSurface, decorView, then)
}
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -523,7 +603,7 @@
)
}
- private fun onDialogDismissed() {
+ fun onDialogDismissed() {
if (Looper.myLooper() != Looper.getMainLooper()) {
dialog.context.mainExecutor.execute { onDialogDismissed() }
return
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 77386cf..a4c5c30 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -77,8 +77,8 @@
* This will be used to:
* - Get the associated [Context].
* - Compute whether we are expanding fully above the launch container.
- * - Apply surface transactions in sync with RenderThread when animating an activity
- * launch.
+ * - Get to overlay to which we initially put the window background layer, until the
+ * opening window is made visible (see [openingWindowSyncView]).
*
* This container can be changed to force this [Controller] to animate the expanding view
* inside a different location, for instance to ensure correct layering during the
@@ -87,6 +87,18 @@
var launchContainer: ViewGroup
/**
+ * The [View] with which the opening app window should be synchronized with once it starts
+ * to be visible.
+ *
+ * We will also move the window background layer to this view's overlay once the opening
+ * window is visible.
+ *
+ * If null, this will default to [launchContainer].
+ */
+ val openingWindowSyncView: View?
+ get() = null
+
+ /**
* Return the [State] of the view that will be animated. We will animate from this state to
* the final window state.
*
@@ -100,11 +112,9 @@
* needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
* fully above the [launchContainer].
*/
- @JvmDefault
fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
/** The animation made progress and the expandable view [state] should be updated. */
- @JvmDefault
fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
/**
@@ -112,7 +122,6 @@
* called previously. This is typically used to clean up the resources initialized when the
* animation was started.
*/
- @JvmDefault
fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
}
@@ -154,7 +163,7 @@
}
/** The timings (durations and delays) used by this animator. */
- class Timings(
+ data class Timings(
/** The total duration of the animation. */
val totalDuration: Long,
@@ -257,8 +266,17 @@
animator.duration = timings.totalDuration
animator.interpolator = LINEAR
+ // Whether we should move the [windowBackgroundLayer] into the overlay of
+ // [Controller.openingWindowSyncView] once the opening app window starts to be visible.
+ val openingWindowSyncView = controller.openingWindowSyncView
+ val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
+ val moveBackgroundLayerWhenAppIsVisible = openingWindowSyncView != null &&
+ openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
+
val launchContainerOverlay = launchContainer.overlay
var cancelled = false
+ var movedBackgroundLayer = false
+
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
if (DEBUG) {
@@ -278,6 +296,10 @@
}
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
launchContainerOverlay.remove(windowBackgroundLayer)
+
+ if (moveBackgroundLayerWhenAppIsVisible) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
}
})
@@ -318,11 +340,29 @@
timings.contentBeforeFadeOutDuration
) < 1
+ if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) {
+ // The expanding view is not visible, so the opening app is visible. If this is the
+ // first frame when it happens, trigger a one-off sync and move the background layer
+ // in its new container.
+ movedBackgroundLayer = true
+
+ launchContainerOverlay.remove(windowBackgroundLayer)
+ openingWindowSyncViewOverlay!!.add(windowBackgroundLayer)
+
+ ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {})
+ }
+
+ val container = if (movedBackgroundLayer) {
+ openingWindowSyncView!!
+ } else {
+ controller.launchContainer
+ }
+
applyStateToWindowBackgroundLayer(
windowBackgroundLayer,
state,
linearProgress,
- launchContainer,
+ container,
drawHole
)
controller.onLaunchAnimationProgress(state, progress, linearProgress)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
new file mode 100644
index 0000000..5b3e45c
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.animation
+
+import android.app.ActivityManager
+import android.view.SurfaceControl
+import android.view.View
+import android.view.ViewRootImpl
+
+/** A util class to synchronize 2 view roots. */
+// TODO(b/200284684): Remove this class.
+object ViewRootSync {
+ // TODO(b/217621394): Remove special handling for low-RAM devices after animation sync is fixed
+ private val forceDisableSynchronization = ActivityManager.isLowRamDeviceStatic()
+
+ /**
+ * Synchronize the next draw between the view roots of [view] and [otherView], then run [then].
+ *
+ * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the
+ * next transactions) or disabled (temporarily, on low ram devices). In this case, [then] will
+ * be called without synchronizing.
+ */
+ fun synchronizeNextDraw(
+ view: View,
+ otherView: View,
+ then: () -> Unit
+ ) {
+ if (forceDisableSynchronization ||
+ !view.isAttachedToWindow || view.viewRootImpl == null ||
+ !otherView.isAttachedToWindow || otherView.viewRootImpl == null ||
+ view.viewRootImpl == otherView.viewRootImpl) {
+ // No need to synchronize if either the touch surface or dialog view is not attached
+ // to a window.
+ then()
+ return
+ }
+
+ // Consume the next frames of both view roots to make sure the ghost view is drawn at
+ // exactly the same time as when the touch surface is made invisible.
+ var remainingTransactions = 0
+ val mergedTransactions = SurfaceControl.Transaction()
+
+ fun onTransaction(transaction: SurfaceControl.Transaction?) {
+ remainingTransactions--
+ transaction?.let { mergedTransactions.merge(it) }
+
+ if (remainingTransactions == 0) {
+ mergedTransactions.apply()
+ then()
+ }
+ }
+
+ fun consumeNextDraw(viewRootImpl: ViewRootImpl) {
+ if (viewRootImpl.consumeNextDraw(::onTransaction)) {
+ remainingTransactions++
+
+ // Make sure we trigger a traversal.
+ viewRootImpl.view.invalidate()
+ }
+ }
+
+ consumeNextDraw(view.viewRootImpl)
+ consumeNextDraw(otherView.viewRootImpl)
+
+ if (remainingTransactions == 0) {
+ then()
+ }
+ }
+
+ /**
+ * A Java-friendly API for [synchronizeNextDraw].
+ */
+ @JvmStatic
+ fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) {
+ synchronizeNextDraw(view, otherView, then::run)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 208825c..d8b050a 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -76,14 +76,14 @@
enum class Style(internal val coreSpec: CoreSpec) {
SPRITZ(CoreSpec(
- a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
- a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
- a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
+ a1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)),
+ a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
+ a3 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
- n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0))
+ n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0))
)),
TONAL_SPOT(CoreSpec(
- a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
+ a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 32.0)),
a2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
a3 = TonalSpec(Hue(HueStrategy.ADD, 60.0), Chroma(ChromaStrategy.EQ, 24.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 4.0)),
@@ -91,17 +91,17 @@
)),
VIBRANT(CoreSpec(
a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
- a2 = TonalSpec(Hue(HueStrategy.ADD, 10.0), Chroma(ChromaStrategy.EQ, 24.0)),
- a3 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.GTE, 32.0)),
+ a2 = TonalSpec(Hue(HueStrategy.ADD, 15.0), Chroma(ChromaStrategy.EQ, 24.0)),
+ a3 = TonalSpec(Hue(HueStrategy.ADD, 30.0), Chroma(ChromaStrategy.GTE, 32.0)),
n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 8.0)),
n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
)),
EXPRESSIVE(CoreSpec(
- a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 40.0), Chroma(ChromaStrategy.GTE, 64.0)),
- a2 = TonalSpec(Hue(HueStrategy.ADD, 20.0), Chroma(ChromaStrategy.EQ, 24.0)),
- a3 = TonalSpec(Hue(HueStrategy.SUBTRACT, 80.0), Chroma(ChromaStrategy.GTE, 64.0)),
- n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0)),
- n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 32.0))
+ a1 = TonalSpec(Hue(HueStrategy.SUBTRACT, 60.0), Chroma(ChromaStrategy.GTE, 64.0)),
+ a2 = TonalSpec(Hue(HueStrategy.SUBTRACT, 30.0), Chroma(ChromaStrategy.EQ, 24.0)),
+ a3 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
+ n1 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 12.0)),
+ n2 = TonalSpec(chroma = Chroma(ChromaStrategy.EQ, 16.0))
)),
RAINBOW(CoreSpec(
a1 = TonalSpec(chroma = Chroma(ChromaStrategy.GTE, 48.0)),
diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
index 9891156..a751f58 100644
--- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
@@ -15,6 +15,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@android:color/system_neutral1_800" />
+ <solid android:color="@color/material_dynamic_neutral20" />
<corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
</shape>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
index ff7cbae..f7b0982 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
@@ -14,5 +14,5 @@
limitations under the License.
-->
<color xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@android:color/system_accent1_500" />
+ android:color="@color/material_dynamic_primary50" />
diff --git a/packages/SystemUI/res/drawable/ic_chevron_icon.xml b/packages/SystemUI/res/drawable/ic_chevron_icon.xml
new file mode 100644
index 0000000..acbbbcb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_chevron_icon.xml
@@ -0,0 +1,28 @@
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="31dp"
+ android:viewportWidth="18"
+ android:viewportHeight="31">
+ <path
+ android:pathData="M0.0061,27.8986L2.6906,30.5831L17.9219,15.3518L2.6906,0.1206L0.0061,2.8051L12.5338,15.3518"
+ android:strokeAlpha="0.7"
+ android:fillColor="#FFFFFF"
+ android:fillAlpha="0.7"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
index 3a228d5..e1b99ce 100644
--- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
+++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
@@ -30,7 +30,7 @@
android:radius="28dp"/>
<size
android:height="64dp"/>
- <solid android:color="@*android:color/system_accent1_200" />
+ <solid android:color="@color/material_dynamic_primary80" />
</shape>
</clip>
</item>
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index 5d80da8..e1dbe69 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -113,4 +113,16 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
+ <ImageView
+ android:id="@+id/chevron_icon"
+ android:autoMirrored="true"
+ android:src="@drawable/ic_chevron_icon"
+ android:visibility="invisible"
+ android:layout_width="@dimen/control_chevron_icon_size"
+ android:layout_height="@dimen/control_chevron_icon_size"
+ android:clickable="false"
+ android:focusable="false"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index b7265b9..51211a0 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -86,6 +86,36 @@
</LinearLayout>
<LinearLayout
+ android:id="@+id/cast_app_section"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:orientation="vertical"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/launch_app_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:ellipsize="end"
+ android:textColor="?android:attr/textColorPrimary"
+ android:text="@string/media_output_dialog_launch_app_text"
+ android:maxLines="1"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"/>
+
+ <Button
+ android:id="@+id/launch_app_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawablePadding="5dp"/>
+ </LinearLayout>
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
index ab600b3..2567176 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
@@ -27,11 +27,11 @@
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
- android:textColor="@android:color/system_neutral1_900"/>
+ android:textColor="@color/material_dynamic_neutral10"/>
<TextView
android:id="@+id/screen_recording_dialog_source_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@android:color/system_neutral2_700"/>
+ android:textColor="@color/material_dynamic_neutral_variant30"/>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 39d7f4f..c7910afc 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -89,11 +89,6 @@
layout="@layout/keyguard_status_view"
android:visibility="gone"/>
- <include layout="@layout/idle_host_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"/>
-
<include layout="@layout/dock_info_overlay"/>
<FrameLayout
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index b318bbc..4b96d5d 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -33,7 +33,7 @@
<!-- The color of the text inside a notification -->
<color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
- <color name="notif_pill_text">@android:color/system_neutral1_50</color>
+ <color name="notif_pill_text">@color/material_dynamic_neutral95</color>
<color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
<color name="notification_guts_sub_text_color">@color/GM2_grey_300</color>
@@ -66,15 +66,15 @@
<color name="media_divider">#85ffffff</color>
<!-- media output dialog-->
- <color name="media_dialog_background">@android:color/system_neutral1_900</color>
- <color name="media_dialog_active_item_main_content">@android:color/system_accent2_800</color>
- <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_100</color>
- <color name="media_dialog_item_status">@android:color/system_accent1_100</color>
- <color name="media_dialog_item_background">@android:color/system_neutral2_800</color>
+ <color name="media_dialog_background">@color/material_dynamic_neutral10</color>
+ <color name="media_dialog_active_item_main_content">@color/material_dynamic_neutral10</color>
+ <color name="media_dialog_inactive_item_main_content">@color/material_dynamic_neutral10</color>
+ <color name="media_dialog_item_status">@color/material_dynamic_neutral10</color>
+ <color name="media_dialog_item_background">@color/material_dynamic_secondary95</color>
<!-- Biometric dialog colors -->
<color name="biometric_dialog_gray">#ffcccccc</color>
- <color name="biometric_dialog_accent">@android:color/system_accent1_300</color>
+ <color name="biometric_dialog_accent">@color/material_dynamic_primary70</color>
<color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
<!-- UDFPS colors -->
@@ -99,5 +99,5 @@
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
- <color name="people_tile_background">@android:color/system_accent2_800</color>
+ <color name="people_tile_background">@color/material_dynamic_secondary20</color>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index faf518e..f4e7cf3 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -87,7 +87,7 @@
<color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
- <color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color>
+ <color name="user_switcher_fullscreen_bg">@color/material_dynamic_neutral10</color>
<color name="user_switcher_fullscreen_popup_item_tint">@*android:color/text_color_primary_device_default_dark</color>
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
@@ -112,7 +112,7 @@
<!-- Chosen so fill over background matches single tone -->
<color name="dark_mode_qs_icon_color_dual_tone_fill">#99000000</color>
- <color name="notif_pill_text">@android:color/system_neutral1_900</color>
+ <color name="notif_pill_text">@color/material_dynamic_neutral10</color>
<!-- Keyboard shortcuts colors -->
<color name="ksh_application_group_color">#fff44336</color>
@@ -131,7 +131,7 @@
<!-- Biometric dialog colors -->
<color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black -->
<color name="biometric_dialog_gray">#ff757575</color>
- <color name="biometric_dialog_accent">@android:color/system_accent1_600</color>
+ <color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
<!-- UDFPS colors -->
@@ -155,6 +155,7 @@
<color name="GM2_grey_900">#202124</color>
<color name="GM2_red_300">#F28B82</color>
+ <color name="GM2_red_500">#EA4335</color>
<color name="GM2_red_700">#C5221F</color>
<color name="GM2_blue_300">#8AB4F8</color>
@@ -174,11 +175,11 @@
<color name="media_seamless_border">?android:attr/colorAccent</color>
<!-- media output dialog-->
- <color name="media_dialog_background" android:lstar="98">@android:color/system_neutral1_100</color>
- <color name="media_dialog_active_item_main_content">@android:color/system_accent1_900</color>
- <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_600</color>
- <color name="media_dialog_item_status">@android:color/system_accent1_900</color>
- <color name="media_dialog_item_background">@android:color/system_accent2_50</color>
+ <color name="media_dialog_background" android:lstar="98">@color/material_dynamic_neutral90</color>
+ <color name="media_dialog_active_item_main_content">@color/material_dynamic_primary10</color>
+ <color name="media_dialog_inactive_item_main_content">@color/material_dynamic_primary40</color>
+ <color name="media_dialog_item_status">@color/material_dynamic_primary10</color>
+ <color name="media_dialog_item_background">@color/material_dynamic_secondary95</color>
<!-- controls -->
<color name="control_primary_text">#E6FFFFFF</color>
@@ -213,7 +214,7 @@
<!-- Wallet screen -->
<color name="wallet_card_border">#33FFFFFF</color>
- <color name="people_tile_background">@android:color/system_accent2_50</color>
+ <color name="people_tile_background">@color/material_dynamic_secondary95</color>
<!-- Internet Dialog -->
<!-- Material next state on color-->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index bb1ffa8..178f93a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -608,10 +608,6 @@
<!-- Component name of communal source service -->
<string name="config_communalSourceComponent" translatable="false">@null</string>
- <!-- Whether idle mode should be enabled. When enabled, the lock screen will timeout to an idle
- screen on inactivity. -->
- <bool name="config_enableIdleMode">false</bool>
-
<!-- This value is used when calculating whether the device is in ambient light mode. It is
light mode when the light sensor sample value exceeds above this value. -->
<integer name="config_ambientLightModeThreshold">5</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 47ffb18..3704134 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1038,6 +1038,7 @@
<dimen name="control_spinner_padding_horizontal">20dp</dimen>
<dimen name="control_text_size">14sp</dimen>
<dimen name="control_icon_size">24dp</dimen>
+ <dimen name="control_chevron_icon_size">20dp</dimen>
<dimen name="control_spacing">8dp</dimen>
<dimen name="control_list_divider">1dp</dimen>
<dimen name="control_corner_radius">14dp</dimen>
@@ -1121,6 +1122,7 @@
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
+ <dimen name="media_output_dialog_app_tier_icon_size">20dp</dimen>
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
shade -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3b7e9d4..3f80647 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1982,6 +1982,12 @@
<!-- Text for privacy dialog, indicating that an app (or multiple) is using an op on behalf of another [CHAR LIMIT=NONE] -->
<string name="ongoing_privacy_dialog_attribution_text">(through <xliff:g id="application name(s)" example="Maps, and Assistant">%s</xliff:g>)</string>
+ <!-- Text for privacy dialog, displaying just the subattribution label [CHAR LIMIT=NONE] -->
+ <string name="ongoing_privacy_dialog_attribution_label">(<xliff:g id="attribution_label" example="For Wallet">%s</xliff:g>)</string>
+
+ <!-- Text for privacy dialog, displaying both subattribution and proxy label [CHAR LIMIT=NONE] -->
+ <string name="ongoing_privacy_dialog_attribution_proxy_label">(<xliff:g id="attribution_label" example="For Wallet">%1$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Maps, and Assistant">%2$s</xliff:g>)</string>
+
<!-- Text for camera app op [CHAR LIMIT=20]-->
<string name="privacy_type_camera">camera</string>
@@ -2205,6 +2211,11 @@
<string name="media_output_dialog_connect_failed">Can\'t switch. Tap to try again.</string>
<!-- Title for pairing item [CHAR LIMIT=60] -->
<string name="media_output_dialog_pairing_new">Pair new device</string>
+ <!-- Title for launch app [CHAR LIMIT=60] -->
+ <string name="media_output_dialog_launch_app_text">To cast this session, please open the app.</string>
+ <!-- App name when can't get app name [CHAR LIMIT=60] -->
+ <string name="media_output_dialog_unknown_launch_app_name">Unknown app</string>
+
<!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_clip_data_label">Build number</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b983545..9d65c38 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -296,7 +296,7 @@
<item name="darkIconTheme">@style/DualToneDarkTheme</item>
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
- <item name="wallpaperTextColorAccent">@*android:color/system_accent1_100</item>
+ <item name="wallpaperTextColorAccent">@color/material_dynamic_primary90</item>
<item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
<item name="passwordStyle">@style/PasswordTheme</item>
@@ -312,7 +312,7 @@
<style name="Theme.SystemUI.LightWallpaper">
<item name="wallpaperTextColor">@*android:color/primary_text_material_light</item>
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item>
- <item name="wallpaperTextColorAccent">@*android:color/system_accent2_600</item>
+ <item name="wallpaperTextColorAccent">@color/material_dynamic_secondary40</item>
<item name="android:colorError">@*android:color/error_color_material_light</item>
<item name="shadowRadius">0</item>
@@ -353,9 +353,9 @@
<item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="android:windowIsFloating">true</item>
<item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
- <item name="offStateColor">@android:color/system_neutral1_800</item>
- <item name="underSurfaceColor">@android:color/system_neutral1_1000</item>
- <item name="android:colorBackground">@android:color/system_neutral1_900</item>
+ <item name="offStateColor">@color/material_dynamic_neutral20</item>
+ <item name="underSurfaceColor">@color/material_dynamic_neutral0</item>
+ <item name="android:colorBackground">@color/material_dynamic_neutral10</item>
<item name="android:itemTextAppearance">@style/Control.MenuItem</item>
</style>
@@ -399,6 +399,11 @@
<!-- that would otherwise be intercepted by the Shade. -->
<item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
+
+ <!-- Empty enter/exit animation, we will animate in-window. Note that the implementation -->
+ <!-- of ActionsDialogLite relies on this to be null (resource=0) to detect when to run -->
+ <!-- the in-window animation. -->
+ <item name="android:windowAnimationStyle">@null</item>
</style>
<style name="QSBorderlessButton">
@@ -573,7 +578,7 @@
<!-- Media controls always have light background -->
<style name="MediaPlayer" parent="@*android:style/Theme.DeviceDefault.Light">
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:backgroundTint">@android:color/system_accent2_50</item>
+ <item name="android:backgroundTint">@color/material_dynamic_secondary95</item>
</style>
<style name="MediaPlayer.ProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal">
@@ -882,8 +887,8 @@
</style>
<style name="Wallet.Theme" parent="@android:style/Theme.DeviceDefault">
- <item name="android:colorBackground">@android:color/system_neutral1_900</item>
- <item name="android:itemBackground">@android:color/system_neutral1_800</item>
+ <item name="android:colorBackground">@color/material_dynamic_neutral10</item>
+ <item name="android:itemBackground">@color/material_dynamic_neutral20</item>
<!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index 8bcb7c9..ccbb0c5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -43,6 +43,7 @@
private static final String TAG = "KeyguardEsimArea";
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ private int mSubscriptionId;
private EuiccManager mEuiccManager;
private BroadcastReceiver mReceiver =
@@ -87,6 +88,10 @@
setOnClickListener(this);
}
+ public void setSubscriptionId(int subscriptionId) {
+ mSubscriptionId = subscriptionId;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -113,6 +118,12 @@
@Override
public void onClick(View v) {
+ SubscriptionInfo sub = SubscriptionManager.from(mContext)
+ .getActiveSubscriptionInfo(mSubscriptionId);
+ if (sub == null) {
+ Log.e(TAG, "No active subscription with subscriptionId: " + mSubscriptionId);
+ return;
+ }
Intent intent = new Intent(ACTION_DISABLE_ESIM);
intent.setPackage(mContext.getPackageName());
PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
@@ -120,7 +131,7 @@
0 /* requestCode */,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, UserHandle.SYSTEM);
- mEuiccManager
- .switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, callbackIntent);
+ mEuiccManager.switchToSubscription(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID, sub.getPortIndex(), callbackIntent);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index c0f9ce7..736e34e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -37,8 +37,9 @@
super(context, attrs);
}
- public void setEsimLocked(boolean locked) {
+ public void setEsimLocked(boolean locked, int subscriptionId) {
KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
+ esimButton.setSubscriptionId(subscriptionId);
esimButton.setVisibility(locked ? View.VISIBLE : View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index e04bfdc..0394e76 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -103,7 +103,7 @@
showDefaultMessage();
}
- mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId));
+ mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId), mSubId);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 0730922..ea94b19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -169,6 +169,7 @@
boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId);
KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area);
+ esimButton.setSubscriptionId(mSubId);
esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
mPasswordEntry.requestFocus();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 23ca923..ec11065 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -192,6 +192,7 @@
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponents());
+ sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser());
startServicesIfNeeded(
sortedStartables, "StartServices", vendorComponent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 11fffd0..f5084f5 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -21,6 +21,7 @@
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Handler;
+import android.os.HandlerThread;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -28,6 +29,7 @@
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
+import com.android.wm.shell.dagger.WMShellConcurrencyModule;
import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
import com.android.wm.shell.transition.ShellTransitions;
@@ -96,8 +98,9 @@
&& android.os.Process.myUserHandle().isSystem()
&& ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName());
mRootComponent = buildGlobalRootComponent(context);
+
// Stand up WMComponent
- mWMComponent = mRootComponent.getWMComponentBuilder().build();
+ setupWmComponent(context);
if (mInitializeComponents) {
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
@@ -124,8 +127,8 @@
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
- .setCompatUI(Optional.of(mWMComponent.getCompatUI()))
- .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop()))
+ .setCompatUI(mWMComponent.getCompatUI())
+ .setDragAndDrop(mWMComponent.getDragAndDrop())
.setBackAnimation(mWMComponent.getBackAnimation());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -161,6 +164,36 @@
}
/**
+ * Sets up {@link #mWMComponent}. On devices where the Shell runs on its own main thread,
+ * this will pre-create the thread to ensure that the components are constructed on the
+ * same thread, to reduce the likelihood of side effects from running the constructors on
+ * a different thread than the rest of the class logic.
+ */
+ private void setupWmComponent(Context context) {
+ WMComponent.Builder wmBuilder = mRootComponent.getWMComponentBuilder();
+ if (!mInitializeComponents || !WMShellConcurrencyModule.enableShellMainThread(context)) {
+ // If running under tests or shell thread is not enabled, we don't need anything special
+ mWMComponent = wmBuilder.build();
+ return;
+ }
+
+ // If the shell main thread is enabled, initialize the component on that thread
+ HandlerThread shellThread = WMShellConcurrencyModule.createShellMainThread();
+ shellThread.start();
+
+ // Use an async handler since we don't care about synchronization
+ Handler shellHandler = Handler.createAsync(shellThread.getLooper());
+ boolean built = shellHandler.runWithScissors(() -> {
+ wmBuilder.setShellMainThread(shellThread);
+ mWMComponent = wmBuilder.build();
+ }, 5000);
+ if (!built) {
+ Log.w(TAG, "Failed to initialize WMComponent");
+ throw new RuntimeException();
+ }
+ }
+
+ /**
* Prepares the SysUIComponent builder before it is built.
* @param sysUIBuilder the builder provided by the root component's getSysUIComponent() method
* @param wm the built WMComponent from the root component's getWMComponent() method
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
deleted file mode 100644
index 2d59e13..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.conditions;
-
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.wifi.WifiInfo;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.condition.Condition;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-import javax.inject.Inject;
-
-/**
- * Monitors Wi-Fi connections and triggers callback, if any, when the device is connected to and
- * disconnected from a trusted network.
- */
-public class CommunalTrustedNetworkCondition extends Condition {
- private final String mTag = getClass().getSimpleName();
- private final ConnectivityManager mConnectivityManager;
- private final ContentObserver mTrustedNetworksObserver;
- private final SecureSettings mSecureSettings;
-
- // The SSID of the connected Wi-Fi network. Null if not connected to Wi-Fi.
- private String mWifiSSID;
-
- // Set of SSIDs of trusted networks.
- private final HashSet<String> mTrustedNetworks = new HashSet<>();
-
- /**
- * The deliminator used to separate trusted network keys saved as a string in secure settings.
- */
- public static final String SETTINGS_STRING_DELIMINATOR = ",/";
-
- @Inject
- public CommunalTrustedNetworkCondition(@Main Handler handler,
- ConnectivityManager connectivityManager, SecureSettings secureSettings) {
- mConnectivityManager = connectivityManager;
- mSecureSettings = secureSettings;
-
- mTrustedNetworksObserver = new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- fetchTrustedNetworks();
- }
- };
- }
-
- /**
- * Starts monitoring for trusted network connection. Ignores if already started.
- */
- @Override
- protected void start() {
- if (shouldLog()) Log.d(mTag, "start listening for wifi connections");
-
- fetchTrustedNetworks();
-
- final NetworkRequest wifiNetworkRequest = new NetworkRequest.Builder().addTransportType(
- NetworkCapabilities.TRANSPORT_WIFI).build();
- mConnectivityManager.registerNetworkCallback(wifiNetworkRequest, mNetworkCallback);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, false, mTrustedNetworksObserver,
- UserHandle.USER_SYSTEM);
- }
-
- /**
- * Stops monitoring for trusted network connection.
- */
- @Override
- protected void stop() {
- if (shouldLog()) Log.d(mTag, "stop listening for wifi connections");
-
- mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
- mSecureSettings.unregisterContentObserver(mTrustedNetworksObserver);
- }
-
- private void updateWifiInfo(WifiInfo wifiInfo) {
- if (wifiInfo == null) {
- mWifiSSID = null;
- } else {
- // Remove the wrapping quotes around the SSID.
- mWifiSSID = wifiInfo.getSSID().replace("\"", "");
- }
-
- checkIfConnectedToTrustedNetwork();
- }
-
- private void fetchTrustedNetworks() {
- final String trustedNetworksString = mSecureSettings.getStringForUser(
- Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS, UserHandle.USER_SYSTEM);
- mTrustedNetworks.clear();
-
- if (shouldLog()) Log.d(mTag, "fetched trusted networks: " + trustedNetworksString);
-
- if (TextUtils.isEmpty(trustedNetworksString)) {
- return;
- }
-
- mTrustedNetworks.addAll(
- Arrays.asList(trustedNetworksString.split(SETTINGS_STRING_DELIMINATOR)));
-
- checkIfConnectedToTrustedNetwork();
- }
-
- private void checkIfConnectedToTrustedNetwork() {
- final boolean connectedToTrustedNetwork = mWifiSSID != null && mTrustedNetworks.contains(
- mWifiSSID);
-
- if (shouldLog()) {
- Log.d(mTag, (connectedToTrustedNetwork ? "connected to" : "disconnected from")
- + " a trusted network");
- }
-
- updateCondition(connectedToTrustedNetwork);
- }
-
- private final ConnectivityManager.NetworkCallback mNetworkCallback =
- new ConnectivityManager.NetworkCallback(
- ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
- private boolean mIsConnected = false;
- private WifiInfo mWifiInfo;
-
- @Override
- public void onAvailable(@NonNull Network network) {
- super.onAvailable(network);
-
- if (shouldLog()) Log.d(mTag, "connected to wifi");
-
- mIsConnected = true;
- if (mWifiInfo != null) {
- updateWifiInfo(mWifiInfo);
- }
- }
-
- @Override
- public void onLost(@NonNull Network network) {
- super.onLost(network);
-
- if (shouldLog()) Log.d(mTag, "disconnected from wifi");
-
- mIsConnected = false;
- mWifiInfo = null;
- updateWifiInfo(null);
- }
-
- @Override
- public void onCapabilitiesChanged(@NonNull Network network,
- @NonNull NetworkCapabilities networkCapabilities) {
- super.onCapabilitiesChanged(network, networkCapabilities);
-
- mWifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
-
- if (mIsConnected) {
- updateWifiInfo(mWifiInfo);
- }
- }
- };
-
- private boolean shouldLog() {
- return Log.isLoggable(mTag, Log.DEBUG);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index e1f1ac4..814b251 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -20,8 +20,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
-import android.view.View;
-import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -30,9 +28,6 @@
import com.android.systemui.communal.PackageObserver;
import com.android.systemui.communal.conditions.CommunalSettingCondition;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.idle.AmbientLightModeMonitor;
-import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
-import com.android.systemui.idle.dagger.IdleViewComponent;
import com.android.systemui.util.condition.Condition;
import com.android.systemui.util.condition.Monitor;
import com.android.systemui.util.condition.dagger.MonitorComponent;
@@ -46,7 +41,6 @@
import javax.inject.Named;
import javax.inject.Provider;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
@@ -58,22 +52,12 @@
*/
@Module(subcomponents = {
CommunalViewComponent.class,
- IdleViewComponent.class,
})
public interface CommunalModule {
- String IDLE_VIEW = "idle_view";
String COMMUNAL_CONDITIONS = "communal_conditions";
/** */
@Provides
- @Named(IDLE_VIEW)
- static View provideIdleView(Context context) {
- FrameLayout view = new FrameLayout(context);
- return view;
- }
-
- /** */
- @Provides
static Optional<CommunalSource.Observer> provideCommunalSourcePackageObserver(
Context context, @Main Resources resources) {
final String componentName = resources.getString(R.string.config_communalSourceComponent);
@@ -87,15 +71,6 @@
}
/**
- * Provides LightSensorEventsDebounceAlgorithm as an instance to DebounceAlgorithm interface.
- * @param algorithm the instance of algorithm that is bound to the interface.
- * @return the interface that is bound to.
- */
- @Binds
- AmbientLightModeMonitor.DebounceAlgorithm ambientLightDebounceAlgorithm(
- LightSensorEventsDebounceAlgorithm algorithm);
-
- /**
* Provides a set of conditions that need to be fulfilled in order for Communal Mode to display.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 47e749c..4819bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -118,6 +118,7 @@
private var nextStatusText: CharSequence = ""
val title: TextView = layout.requireViewById(R.id.title)
val subtitle: TextView = layout.requireViewById(R.id.subtitle)
+ val chevronIcon: ImageView = layout.requireViewById(R.id.chevron_icon)
val context: Context = layout.getContext()
val clipLayer: ClipDrawable
lateinit var cws: ControlWithState
@@ -163,6 +164,7 @@
cws.control?.let {
title.setText(it.title)
subtitle.setText(it.subtitle)
+ chevronIcon.visibility = if (usePanel()) View.VISIBLE else View.INVISIBLE
}
}
@@ -469,6 +471,7 @@
updateContentDescription()
status.setTextColor(color)
+ chevronIcon.imageTintList = color
control?.getCustomIcon()?.let {
icon.setImageIcon(it)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b926692..b02074a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -17,6 +17,9 @@
package com.android.systemui.dagger;
import android.content.Context;
+import android.os.HandlerThread;
+
+import androidx.annotation.Nullable;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.tv.TvWMComponent;
@@ -26,6 +29,7 @@
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule;
@@ -44,6 +48,7 @@
import java.util.Optional;
+import dagger.BindsInstance;
import dagger.Subcomponent;
/**
@@ -64,6 +69,10 @@
*/
@Subcomponent.Builder
interface Builder {
+
+ @BindsInstance
+ Builder setShellMainThread(@Nullable @ShellMainThread HandlerThread t);
+
WMComponent build();
}
@@ -120,10 +129,10 @@
Optional<RecentTasks> getRecentTasks();
@WMSingleton
- CompatUI getCompatUI();
+ Optional<CompatUI> getCompatUI();
@WMSingleton
- DragAndDrop getDragAndDrop();
+ Optional<DragAndDrop> getDragAndDrop();
@WMSingleton
Optional<BackAnimation> getBackAnimation();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index befb648..789ad62 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -118,9 +118,11 @@
switch (this) {
case UNINITIALIZED:
case INITIALIZED:
- case DOZE_REQUEST_PULSE:
return parameters.shouldControlScreenOff() ? Display.STATE_ON
: Display.STATE_OFF;
+ case DOZE_REQUEST_PULSE:
+ return parameters.getDisplayNeedsBlanking() ? Display.STATE_OFF
+ : Display.STATE_ON;
case DOZE_AOD_PAUSED:
case DOZE:
return Display.STATE_OFF;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4dacf65..338a8b2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -132,6 +132,7 @@
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
setCurrentState(Lifecycle.State.STARTED);
mExecutor.execute(() -> {
+ mStateController.setShouldShowComplications(shouldShowComplications());
addOverlayWindowLocked(layoutParams);
setCurrentState(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index bc5a52a..fc71e2f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams;
+import android.service.dreams.DreamService;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -84,6 +85,8 @@
@Complication.ComplicationType
private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE;
+ private boolean mShouldShowComplications = DreamService.DEFAULT_SHOW_COMPLICATIONS;
+
private final Collection<Complication> mComplications = new HashSet();
@VisibleForTesting
@@ -131,7 +134,12 @@
.filter(complication -> {
@Complication.ComplicationType
final int requiredTypes = complication.getRequiredTypeAvailability();
- return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
+ // If it should show complications, show ones whose required types are
+ // available. Otherwise, only show ones that don't require types.
+ if (mShouldShowComplications) {
+ return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
+ }
+ return requiredTypes == Complication.COMPLICATION_TYPE_NONE;
})
.collect(Collectors.toCollection(HashSet::new))
: mComplications);
@@ -221,7 +229,24 @@
public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
mExecutor.execute(() -> {
mAvailableComplicationTypes = types;
- mCallbacks.forEach(callback -> callback.onAvailableComplicationTypesChanged());
+ mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
+ });
+ }
+
+ /**
+ * Returns whether the dream overlay should show complications.
+ */
+ public boolean getShouldShowComplications() {
+ return mShouldShowComplications;
+ }
+
+ /**
+ * Sets whether the dream overlay should show complications.
+ */
+ public void setShouldShowComplications(boolean shouldShowComplications) {
+ mExecutor.execute(() -> {
+ mShouldShowComplications = shouldShowComplications;
+ mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index dd36fd6..5487c42 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -145,7 +145,7 @@
// 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
- public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false);
+ public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, true);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index f0371fc..84fa6a6 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -31,6 +31,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -76,8 +77,10 @@
import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -109,6 +112,7 @@
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
@@ -2170,6 +2174,7 @@
private Optional<StatusBar> mStatusBarOptional;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private LockPatternUtils mLockPatternUtils;
+ private float mWindowDimAmount;
protected ViewGroup mContainer;
@@ -2251,6 +2256,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeLayout();
+ mWindowDimAmount = getWindow().getAttributes().dimAmount;
}
@Override
@@ -2459,6 +2465,96 @@
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
.commitUpdate(mContext.getDisplayId());
+
+ // By default this dialog windowAnimationStyle is null, and therefore windowAnimations
+ // should be equal to 0 which means we need to animate the dialog in-window. If it's not
+ // equal to 0, it means it has been overridden to animate (e.g. by the
+ // DialogLaunchAnimator) so we don't run the animation.
+ boolean shouldAnimateInWindow = getWindow().getAttributes().windowAnimations == 0;
+ if (shouldAnimateInWindow) {
+ startAnimation(true /* isEnter */, null /* then */);
+
+ // Override the dialog dismiss so that we can animate in-window before dismissing
+ // the dialog.
+ setDismissOverride(() -> {
+ startAnimation(false /* isEnter */, /* then */ () -> {
+ setDismissOverride(null);
+
+ // Hide then dismiss to instantly dismiss.
+ hide();
+ dismiss();
+ });
+ });
+ }
+ }
+
+ /** Run either the enter or exit animation, then run {@code then}. */
+ private void startAnimation(boolean isEnter, @Nullable Runnable then) {
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+
+ // Note: these specs should be the same as in popup_enter_material and
+ // popup_exit_material.
+ float translationPx;
+ Resources resources = getContext().getResources();
+ if (isEnter) {
+ translationPx = resources.getDimension(R.dimen.popup_enter_animation_from_y_delta);
+ animator.setInterpolator(Interpolators.STANDARD);
+ animator.setDuration(resources.getInteger(R.integer.config_activityDefaultDur));
+ } else {
+ translationPx = resources.getDimension(R.dimen.popup_exit_animation_to_y_delta);
+ animator.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+ animator.setDuration(resources.getInteger(R.integer.config_activityShortDur));
+ }
+
+ Window window = getWindow();
+ int rotation = window.getWindowManager().getDefaultDisplay().getRotation();
+
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = (float) valueAnimator.getAnimatedValue();
+
+ float alpha = isEnter ? progress : 1 - progress;
+ mGlobalActionsLayout.setAlpha(alpha);
+ window.setDimAmount(mWindowDimAmount * alpha);
+
+ // TODO(b/213872558): Support devices that don't have their power button on the
+ // right.
+ float translation =
+ isEnter ? translationPx * (1 - progress) : translationPx * progress;
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ mGlobalActionsLayout.setTranslationX(translation);
+ break;
+ case Surface.ROTATION_90:
+ mGlobalActionsLayout.setTranslationY(-translation);
+ break;
+ case Surface.ROTATION_180:
+ mGlobalActionsLayout.setTranslationX(-translation);
+ break;
+ case Surface.ROTATION_270:
+ mGlobalActionsLayout.setTranslationY(translation);
+ break;
+ }
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ private int mPreviousLayerType;
+
+ @Override
+ public void onAnimationStart(Animator animation, boolean isReverse) {
+ mPreviousLayerType = mGlobalActionsLayout.getLayerType();
+ mGlobalActionsLayout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mGlobalActionsLayout.setLayerType(mPreviousLayerType, null);
+ if (then != null) {
+ then.run();
+ }
+ }
+ });
+
+ animator.start();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt
deleted file mode 100644
index fa00dbb..0000000
--- a/packages/SystemUI/src/com/android/systemui/idle/AmbientLightModeMonitor.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle
-
-import android.annotation.IntDef
-import android.hardware.Sensor
-import android.hardware.SensorEvent
-import android.hardware.SensorEventListener
-import android.hardware.SensorManager
-import android.util.Log
-import com.android.systemui.util.sensors.AsyncSensorManager
-import javax.inject.Inject
-
-/**
- * Monitors ambient light signals, applies a debouncing algorithm, and produces the current
- * ambient light mode.
- *
- * @property algorithm the debounce algorithm which transforms light sensor events into an
- * ambient light mode.
- * @property sensorManager the sensor manager used to register sensor event updates.
- */
-class AmbientLightModeMonitor @Inject constructor(
- private val algorithm: DebounceAlgorithm,
- private val sensorManager: AsyncSensorManager
-) {
- companion object {
- private const val TAG = "AmbientLightModeMonitor"
- private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
-
- const val AMBIENT_LIGHT_MODE_LIGHT = 0
- const val AMBIENT_LIGHT_MODE_DARK = 1
- const val AMBIENT_LIGHT_MODE_UNDECIDED = 2
- }
-
- // Light sensor used to detect ambient lighting conditions.
- private val lightSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
-
- // Represents all ambient light modes.
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(AMBIENT_LIGHT_MODE_LIGHT, AMBIENT_LIGHT_MODE_DARK, AMBIENT_LIGHT_MODE_UNDECIDED)
- annotation class AmbientLightMode
-
- /**
- * Start monitoring the current ambient light mode.
- *
- * @param callback callback that gets triggered when the ambient light mode changes.
- */
- fun start(callback: Callback) {
- if (DEBUG) Log.d(TAG, "start monitoring ambient light mode")
-
- if (lightSensor == null) {
- if (DEBUG) Log.w(TAG, "light sensor not available")
- return
- }
-
- algorithm.start(callback)
- sensorManager.registerListener(mSensorEventListener, lightSensor,
- SensorManager.SENSOR_DELAY_NORMAL)
- }
-
- /**
- * Stop monitoring the current ambient light mode.
- */
- fun stop() {
- if (DEBUG) Log.d(TAG, "stop monitoring ambient light mode")
-
- algorithm.stop()
- sensorManager.unregisterListener(mSensorEventListener)
- }
-
- private val mSensorEventListener: SensorEventListener = object : SensorEventListener {
- override fun onSensorChanged(event: SensorEvent) {
- if (event.values.isEmpty()) {
- if (DEBUG) Log.w(TAG, "SensorEvent doesn't have any value")
- return
- }
-
- algorithm.onUpdateLightSensorEvent(event.values[0])
- }
-
- override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
- // Do nothing.
- }
- }
-
- /**
- * Interface of the ambient light mode callback, which gets triggered when the mode changes.
- */
- interface Callback {
- fun onChange(@AmbientLightMode mode: Int)
- }
-
- /**
- * Interface of the algorithm that transforms light sensor events to an ambient light mode.
- */
- interface DebounceAlgorithm {
- // Setting Callback to nullable so mockito can verify without throwing NullPointerException.
- fun start(callback: Callback?)
- fun stop()
- fun onUpdateLightSensorEvent(value: Float)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/idle/IdleHostView.java b/packages/SystemUI/src/com/android/systemui/idle/IdleHostView.java
deleted file mode 100644
index 7d79279..0000000
--- a/packages/SystemUI/src/com/android/systemui/idle/IdleHostView.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * {@link IdleHostView} houses a surface to be displayed when the device idle.
- */
-public class IdleHostView extends FrameLayout {
- public IdleHostView(@NonNull Context context) {
- this(context, null);
- }
-
- public IdleHostView(@NonNull Context context,
- @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public IdleHostView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java b/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java
deleted file mode 100644
index 624d01f..0000000
--- a/packages/SystemUI/src/com/android/systemui/idle/IdleHostViewController.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle;
-
-import static com.android.systemui.communal.dagger.CommunalModule.IDLE_VIEW;
-
-import android.annotation.IntDef;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.ViewController;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-/**
- * {@link IdleHostViewController} processes signals to control the lifecycle of the idle screen.
- */
-public class IdleHostViewController extends ViewController<IdleHostView> {
- private static final String TAG = "IdleHostViewController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- @Retention(RetentionPolicy.RUNTIME)
- @IntDef({STATE_IDLE_MODE_ENABLED, STATE_KEYGUARD_SHOWING, STATE_DOZING, STATE_DREAMING,
- STATE_LOW_LIGHT})
- public @interface State {}
-
- // Set at construction to indicate idle mode is available.
- private static final int STATE_IDLE_MODE_ENABLED = 1 << 0;
-
- // Set when keyguard is present, even below a dream or AOD. AKA the device is locked.
- private static final int STATE_KEYGUARD_SHOWING = 1 << 1;
-
- // Set when the device has entered a dozing / low power state.
- private static final int STATE_DOZING = 1 << 2;
-
- // Set when the device has entered a dreaming state, which includes dozing.
- private static final int STATE_DREAMING = 1 << 3;
-
- // Set when the device is in a low light environment.
- private static final int STATE_LOW_LIGHT = 1 << 4;
-
- // The aggregate current state.
- private int mState;
- private boolean mIsMonitoringLowLight;
- private boolean mIsMonitoringDream;
-
- private final BroadcastDispatcher mBroadcastDispatcher;
-
- private final PowerManager mPowerManager;
-
- // Keyguard state controller for monitoring keyguard show state.
- private final KeyguardStateController mKeyguardStateController;
-
- // Status bar state controller for monitoring when the device is dozing.
- private final StatusBarStateController mStatusBarStateController;
-
- // Intent filter for receiving dream broadcasts.
- private IntentFilter mDreamIntentFilter;
-
- // Monitor for the current ambient light mode. Used to trigger / exit low-light mode.
- private final AmbientLightModeMonitor mAmbientLightModeMonitor;
-
- private final KeyguardStateController.Callback mKeyguardCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
- setState(STATE_KEYGUARD_SHOWING, mKeyguardStateController.isShowing());
- }
- };
-
- private final StatusBarStateController.StateListener mStatusBarCallback =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozingChanged(boolean isDozing) {
- setState(STATE_DOZING, isDozing);
- }
- };
-
- private final BroadcastReceiver mDreamStateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_DREAMING_STARTED.equals(intent.getAction())) {
- setState(STATE_DREAMING, true);
- } else if (Intent.ACTION_DREAMING_STOPPED.equals(intent.getAction())) {
- setState(STATE_DREAMING, false);
- }
- }
- };
-
- private final AmbientLightModeMonitor.Callback mAmbientLightModeCallback =
- mode -> {
- boolean shouldBeLowLight;
- switch (mode) {
- case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED:
- return;
- case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT:
- shouldBeLowLight = false;
- break;
- case AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK:
- shouldBeLowLight = true;
- break;
- default:
- Log.w(TAG, "invalid ambient light mode");
- return;
- }
-
- if (DEBUG) Log.d(TAG, "ambient light mode changed to " + mode);
-
- final boolean isLowLight = getState(STATE_LOW_LIGHT);
- if (shouldBeLowLight != isLowLight) {
- setState(STATE_LOW_LIGHT, shouldBeLowLight);
- }
- };
-
- final Provider<View> mIdleViewProvider;
-
- @Inject
- protected IdleHostViewController(
- BroadcastDispatcher broadcastDispatcher,
- PowerManager powerManager,
- IdleHostView view,
- @Main Resources resources,
- @Named(IDLE_VIEW) Provider<View> idleViewProvider,
- KeyguardStateController keyguardStateController,
- StatusBarStateController statusBarStateController,
- AmbientLightModeMonitor ambientLightModeMonitor) {
- super(view);
- mBroadcastDispatcher = broadcastDispatcher;
- mPowerManager = powerManager;
- mIdleViewProvider = idleViewProvider;
- mKeyguardStateController = keyguardStateController;
- mStatusBarStateController = statusBarStateController;
- mAmbientLightModeMonitor = ambientLightModeMonitor;
-
- mState = STATE_KEYGUARD_SHOWING;
-
- final boolean enabled = resources.getBoolean(R.bool.config_enableIdleMode);
- if (enabled) {
- mState |= STATE_IDLE_MODE_ENABLED;
- }
-
- setState(mState, true);
-
- if (DEBUG) {
- Log.d(TAG, "initial state:" + mState + " enabled:" + enabled);
- }
- }
-
- @Override
- public void init() {
- super.init();
-
- setState(STATE_KEYGUARD_SHOWING, mKeyguardStateController.isShowing());
- setState(STATE_DOZING, mStatusBarStateController.isDozing());
- }
-
- private void setState(@State int state, boolean active) {
- final int oldState = mState;
-
- if (active) {
- mState |= state;
- } else {
- mState &= ~state;
- }
-
- if (oldState == mState) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "set " + getStateName(state) + " to " + active);
- logCurrentState();
- }
-
- final boolean inCommunalMode = getState(STATE_IDLE_MODE_ENABLED)
- && getState(STATE_KEYGUARD_SHOWING);
-
- enableDreamMonitoring(inCommunalMode);
- enableLowLightMonitoring(inCommunalMode);
-
- if (state == STATE_LOW_LIGHT) {
- enableLowLightMode(inCommunalMode && active);
- }
- }
-
- private void enableDreamMonitoring(boolean enable) {
- if (mIsMonitoringDream == enable) {
- return;
- }
-
- mIsMonitoringDream = enable;
-
- if (DEBUG) {
- Log.d(TAG, (enable ? "enable" : "disable") + " dream monitoring");
- }
-
- if (mDreamIntentFilter == null) {
- mDreamIntentFilter = new IntentFilter();
- mDreamIntentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
- mDreamIntentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
- }
-
- if (enable) {
- mBroadcastDispatcher.registerReceiver(mDreamStateReceiver, mDreamIntentFilter);
- } else {
- mBroadcastDispatcher.unregisterReceiver(mDreamStateReceiver);
- }
- }
-
- private void enableLowLightMonitoring(boolean enable) {
- if (enable == mIsMonitoringLowLight) {
- return;
- }
-
- mIsMonitoringLowLight = enable;
-
- if (mIsMonitoringLowLight) {
- if (DEBUG) Log.d(TAG, "enable low light monitoring");
- mAmbientLightModeMonitor.start(mAmbientLightModeCallback);
- } else {
- if (DEBUG) Log.d(TAG, "disable low light monitoring");
- mAmbientLightModeMonitor.stop();
- }
- }
-
- private void enableLowLightMode(boolean enable) {
- if (enable == getState(STATE_DOZING)) {
- return;
- }
-
- if (enable) {
- if (DEBUG) Log.d(TAG, "enter low light, start dozing");
-
- mPowerManager.goToSleep(
- SystemClock.uptimeMillis(),
- PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0);
- } else {
- if (DEBUG) Log.d(TAG, "exit low light, stop dozing");
- mPowerManager.wakeUp(SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION, "Exit low light condition");
- }
- }
-
- @Override
- protected void onViewAttached() {
- if (DEBUG) {
- Log.d(TAG, "onViewAttached");
- }
-
- mKeyguardStateController.addCallback(mKeyguardCallback);
- mStatusBarStateController.addCallback(mStatusBarCallback);
- }
-
- @Override
- protected void onViewDetached() {
- mKeyguardStateController.removeCallback(mKeyguardCallback);
- mStatusBarStateController.removeCallback(mStatusBarCallback);
- }
-
- private String getStateName(@State int state) {
- switch (state) {
- case STATE_IDLE_MODE_ENABLED:
- return "STATE_IDLE_MODE_ENABLED";
- case STATE_KEYGUARD_SHOWING:
- return "STATE_KEYGUARD_SHOWING";
- case STATE_DOZING:
- return "STATE_DOZING";
- case STATE_DREAMING:
- return "STATE_DREAMING";
- case STATE_LOW_LIGHT:
- return "STATE_LOW_LIGHT";
- default:
- return "STATE_UNKNOWN";
- }
- }
-
- private boolean getState(@State int state) {
- return getState(state, mState);
- }
-
- private boolean getState(@State int state, int oldState) {
- return (oldState & state) == state;
- }
-
- private String getStateLog(@State int state) {
- return getStateName(state) + " = " + getState(state);
- }
-
- private void logCurrentState() {
- Log.d(TAG, "current state: {\n"
- + "\t" + getStateLog(STATE_IDLE_MODE_ENABLED) + "\n"
- + "\t" + getStateLog(STATE_KEYGUARD_SHOWING) + "\n"
- + "\t" + getStateLog(STATE_DOZING) + "\n"
- + "\t" + getStateLog(STATE_DREAMING) + "\n"
- + "\t" + getStateLog(STATE_LOW_LIGHT) + "\n"
- + "}");
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/idle/InputMonitorFactory.java b/packages/SystemUI/src/com/android/systemui/idle/InputMonitorFactory.java
deleted file mode 100644
index be0a378..0000000
--- a/packages/SystemUI/src/com/android/systemui/idle/InputMonitorFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle;
-
-import com.android.systemui.dagger.qualifiers.DisplayId;
-import com.android.systemui.shared.system.InputMonitorCompat;
-
-import javax.inject.Inject;
-
-/**
- * {@link InputMonitorFactory} generates instances of {@link InputMonitorCompat}.
- */
-public class InputMonitorFactory {
- private final int mDisplayId;
-
- @Inject
- public InputMonitorFactory(@DisplayId int displayId) {
- mDisplayId = displayId;
- }
-
- /**
- * Generates a new {@link InputMonitorCompat}.
- *
- * @param identifier Identifier to generate monitor with.
- * @return A {@link InputMonitorCompat} instance.
- */
- public InputMonitorCompat getInputMonitor(String identifier) {
- return new InputMonitorCompat(identifier, mDisplayId);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/idle/LightSensorEventsDebounceAlgorithm.kt b/packages/SystemUI/src/com/android/systemui/idle/LightSensorEventsDebounceAlgorithm.kt
deleted file mode 100644
index 7b06b96..0000000
--- a/packages/SystemUI/src/com/android/systemui/idle/LightSensorEventsDebounceAlgorithm.kt
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle
-
-import android.content.res.Resources
-import android.util.Log
-import com.android.systemui.R
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.util.concurrency.DelayableExecutor
-import javax.inject.Inject
-
-/**
- * An algorithm that receives light sensor events, debounces the signals, and transforms into an
- * ambient light mode: light, dark, or undecided.
- *
- * More about the algorithm at go/titan-light-sensor-debouncer.
- */
-class LightSensorEventsDebounceAlgorithm @Inject constructor(
- @Main private val executor: DelayableExecutor,
- @Main resources: Resources
-) : AmbientLightModeMonitor.DebounceAlgorithm {
- companion object {
- private const val TAG = "LightDebounceAlgorithm"
- private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
- }
-
- // The ambient mode is considered light mode when the light sensor value increases exceeding
- // this value.
- private val lightModeThreshold =
- resources.getInteger(R.integer.config_ambientLightModeThreshold)
-
- // The ambient mode is considered dark mode when the light sensor value drops below this
- // value.
- private val darkModeThreshold = resources.getInteger(R.integer.config_ambientDarkModeThreshold)
-
- // Each sample for calculating light mode contains light sensor events collected for this
- // duration of time in milliseconds.
- private val lightSamplingSpanMillis =
- resources.getInteger(R.integer.config_ambientLightModeSamplingSpanMillis)
-
- // Each sample for calculating dark mode contains light sensor events collected for this
- // duration of time in milliseconds.
- private val darkSamplingSpanMillis =
- resources.getInteger(R.integer.config_ambientDarkModeSamplingSpanMillis)
-
- // The calculations for light mode is performed at this frequency in milliseconds.
- private val lightSamplingFrequencyMillis =
- resources.getInteger(R.integer.config_ambientLightModeSamplingFrequencyMillis)
-
- // The calculations for dark mode is performed at this frequency in milliseconds.
- private val darkSamplingFrequencyMillis =
- resources.getInteger(R.integer.config_ambientDarkModeSamplingFrequencyMillis)
-
- // Registered callback, which gets triggered when the ambient light mode changes.
- private var callback: AmbientLightModeMonitor.Callback? = null
-
- // Queue of bundles used for calculating [isLightMode], ordered from oldest to latest.
- val bundlesQueueLightMode = ArrayList<ArrayList<Float>>()
-
- // Queue of bundles used for calculating [isDarkMode], ordered from oldest to latest
- val bundlesQueueDarkMode = ArrayList<ArrayList<Float>>()
-
- // The current ambient light mode.
- @AmbientLightModeMonitor.AmbientLightMode
- var mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED
- set(value) {
- if (field == value) return
- field = value
-
- if (DEBUG) Log.d(TAG, "ambient light mode changed to $value")
-
- callback?.onChange(value)
- }
-
- // The latest claim of whether it should be light mode.
- var isLightMode = false
- set(value) {
- if (field == value) return
- field = value
-
- if (DEBUG) Log.d(TAG, "isLightMode: $value")
-
- mode = when {
- isDarkMode -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
- value -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT
- else -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED
- }
- }
-
- // The latest claim of whether it should be dark mode.
- var isDarkMode = false
- set(value) {
- if (field == value) return
- field = value
-
- if (DEBUG) Log.d(TAG, "isDarkMode: $value")
-
- mode = when {
- value -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
- isLightMode -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT
- else -> AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED
- }
- }
-
- // The latest average of the light mode bundle.
- var bundleAverageLightMode = 0.0
- set(value) {
- field = value
-
- if (DEBUG) Log.d(TAG, "light mode average: $value")
-
- isLightMode = value > lightModeThreshold
- }
-
- // The latest average of the dark mode bundle.
- var bundleAverageDarkMode = 0.0
- set(value) {
- field = value
-
- if (DEBUG) Log.d(TAG, "dark mode average: $value")
-
- isDarkMode = value < darkModeThreshold
- }
-
- // The latest bundle for calculating light mode claim.
- var bundleLightMode = ArrayList<Float>()
- set(value) {
- field = value
-
- val average = value.average()
-
- if (!average.isNaN()) {
- bundleAverageLightMode = average
- }
- }
-
- // The latest bundle for calculating dark mode claim.
- var bundleDarkMode = ArrayList<Float>()
- set(value) {
- field = value
-
- val average = value.average()
-
- if (!average.isNaN()) {
- bundleAverageDarkMode = average
- }
- }
-
- // The latest light level from light sensor event updates.
- var lightSensorLevel = 0.0f
- set(value) {
- field = value
-
- bundlesQueueLightMode.forEach { bundle -> bundle.add(value) }
- bundlesQueueDarkMode.forEach { bundle -> bundle.add(value) }
- }
-
- // Creates a new bundle that collects light sensor events for calculating the light mode claim,
- // and adds it to the end of the queue. It schedules a call to dequeue this bundle after
- // [LIGHT_SAMPLING_SPAN_MILLIS]. Once started, it also repeatedly calls itself at
- // [LIGHT_SAMPLING_FREQUENCY_MILLIS].
- val enqueueLightModeBundle: Runnable = object : Runnable {
- override fun run() {
- if (DEBUG) Log.d(TAG, "enqueueing a light mode bundle")
-
- bundlesQueueLightMode.add(ArrayList())
-
- executor.executeDelayed(dequeueLightModeBundle, lightSamplingSpanMillis.toLong())
- executor.executeDelayed(this, lightSamplingFrequencyMillis.toLong())
- }
- }
-
- // Creates a new bundle that collects light sensor events for calculating the dark mode claim,
- // and adds it to the end of the queue. It schedules a call to dequeue this bundle after
- // [DARK_SAMPLING_SPAN_MILLIS]. Once started, it also repeatedly calls itself at
- // [DARK_SAMPLING_FREQUENCY_MILLIS].
- val enqueueDarkModeBundle: Runnable = object : Runnable {
- override fun run() {
- if (DEBUG) Log.d(TAG, "enqueueing a dark mode bundle")
-
- bundlesQueueDarkMode.add(ArrayList())
-
- executor.executeDelayed(dequeueDarkModeBundle, darkSamplingSpanMillis.toLong())
- executor.executeDelayed(this, darkSamplingFrequencyMillis.toLong())
- }
- }
-
- // Collects the oldest bundle from the light mode bundles queue, and as a result triggering a
- // calculation of the light mode claim.
- val dequeueLightModeBundle: Runnable = object : Runnable {
- override fun run() {
- if (bundlesQueueLightMode.isEmpty()) return
-
- bundleLightMode = bundlesQueueLightMode.removeAt(0)
-
- if (DEBUG) Log.d(TAG, "dequeued a light mode bundle of size ${bundleLightMode.size}")
- }
- }
-
- // Collects the oldest bundle from the dark mode bundles queue, and as a result triggering a
- // calculation of the dark mode claim.
- val dequeueDarkModeBundle: Runnable = object : Runnable {
- override fun run() {
- if (bundlesQueueDarkMode.isEmpty()) return
-
- bundleDarkMode = bundlesQueueDarkMode.removeAt(0)
-
- if (DEBUG) Log.d(TAG, "dequeued a dark mode bundle of size ${bundleDarkMode.size}")
- }
- }
-
- /**
- * Start the algorithm.
- *
- * @param callback callback that gets triggered when the ambient light mode changes.
- */
- override fun start(callback: AmbientLightModeMonitor.Callback?) {
- if (DEBUG) Log.d(TAG, "start algorithm")
-
- if (callback == null) {
- if (DEBUG) Log.w(TAG, "callback is null")
- return
- }
-
- if (this.callback != null) {
- if (DEBUG) Log.w(TAG, "already started")
- return
- }
-
- this.callback = callback
-
- executor.execute(enqueueLightModeBundle)
- executor.execute(enqueueDarkModeBundle)
- }
-
- /**
- * Stop the algorithm.
- */
- override fun stop() {
- if (DEBUG) Log.d(TAG, "stop algorithm")
-
- if (callback == null) {
- if (DEBUG) Log.w(TAG, "haven't started")
- return
- }
-
- callback = null
-
- // Resets bundle queues.
- bundlesQueueLightMode.clear()
- bundlesQueueDarkMode.clear()
-
- // Resets ambient light mode.
- mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED
- }
-
- /**
- * Update the light sensor event value.
- *
- * @param value light sensor update value.
- */
- override fun onUpdateLightSensorEvent(value: Float) {
- if (callback == null) {
- if (DEBUG) Log.w(TAG, "ignore light sensor event because algorithm not started")
- return
- }
-
- lightSensorLevel = value
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/idle/dagger/IdleViewComponent.java b/packages/SystemUI/src/com/android/systemui/idle/dagger/IdleViewComponent.java
deleted file mode 100644
index 9754b1f..0000000
--- a/packages/SystemUI/src/com/android/systemui/idle/dagger/IdleViewComponent.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle.dagger;
-
-import com.android.systemui.idle.IdleHostView;
-import com.android.systemui.idle.IdleHostViewController;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Subcomponent for working with {@link IdleHostView}.
- */
-@Subcomponent
-public interface IdleViewComponent {
- /** Simple factory for {@link Factory}. */
- @Subcomponent.Factory
- interface Factory {
- IdleViewComponent build(@BindsInstance IdleHostView idleHostView);
- }
-
- /** Builds a {@link IdleHostViewController}. */
- IdleHostViewController getIdleHostViewController();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b96eee7..88555ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -102,7 +102,7 @@
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index ecaa142..83ad027 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -540,14 +540,14 @@
}
private fun getBackgroundColor(): Int {
- return context.getColor(android.R.color.system_accent2_50)
+ return context.getColor(R.color.material_dynamic_secondary95)
}
private fun getForegroundColor(): Int {
return if (mediaFlags.useMediaSessionLayout()) {
- context.getColor(android.R.color.system_neutral2_200)
+ context.getColor(R.color.material_dynamic_neutral_variant80)
} else {
- context.getColor(android.R.color.system_accent2_900)
+ context.getColor(R.color.material_dynamic_secondary10)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index ce7a697..831a606 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -476,7 +476,7 @@
appIconView.clearColorFilter();
if (data.getAppIcon() != null && !data.getResumption()) {
appIconView.setImageIcon(data.getAppIcon());
- int color = mContext.getColor(android.R.color.system_accent2_900);
+ int color = mContext.getColor(R.color.material_dynamic_secondary10);
appIconView.setColorFilter(color);
} else {
// Resume players use launcher icon
@@ -590,7 +590,7 @@
} else {
appIconView.setImageResource(R.drawable.ic_music_note);
}
- int color = mContext.getColor(android.R.color.system_accent2_900);
+ int color = mContext.getColor(R.color.material_dynamic_secondary10);
appIconView.setColorFilter(color);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index e1ff110..1d1a230 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -150,7 +150,7 @@
private val themeText = com.android.settingslib.Utils.getColorAttr(context,
com.android.internal.R.attr.textColorPrimary).defaultColor
- private val bgColor = context.getColor(android.R.color.system_accent2_50)
+ private val bgColor = context.getColor(R.color.material_dynamic_secondary95)
// Internal listeners are part of the internal pipeline. External listeners (those registered
// with [MediaDeviceManager.addListener]) receive events after they have propagated through
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 2dff947..c3b4354 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -16,12 +16,7 @@
package com.android.systemui.media.dagger;
-import android.content.Context;
-import android.os.Handler;
-import android.view.WindowManager;
-
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaFlags;
import com.android.systemui.media.MediaHierarchyManager;
@@ -34,11 +29,8 @@
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.commandline.CommandRegistry;
import java.util.Optional;
-import java.util.concurrent.Executor;
import javax.inject.Named;
@@ -101,13 +93,11 @@
@SysUISingleton
static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
MediaTttFlags mediaTttFlags,
- CommandQueue commandQueue,
- Context context,
- WindowManager windowManager) {
+ Lazy<MediaTttChipControllerSender> controllerSenderLazy) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
- return Optional.of(new MediaTttChipControllerSender(commandQueue, context, windowManager));
+ return Optional.of(controllerSenderLazy.get());
}
/** */
@@ -115,16 +105,11 @@
@SysUISingleton
static Optional<MediaTttChipControllerReceiver> providesMediaTttChipControllerReceiver(
MediaTttFlags mediaTttFlags,
- CommandQueue commandQueue,
- Context context,
- WindowManager windowManager,
- @Main Handler mainHandler) {
+ Lazy<MediaTttChipControllerReceiver> controllerReceiverLazy) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
- return Optional.of(
- new MediaTttChipControllerReceiver(
- commandQueue, context, windowManager, mainHandler));
+ return Optional.of(controllerReceiverLazy.get());
}
/** */
@@ -132,14 +117,11 @@
@SysUISingleton
static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper(
MediaTttFlags mediaTttFlags,
- CommandRegistry commandRegistry,
- Context context,
- @Main Executor mainExecutor) {
+ Lazy<MediaTttCommandLineHelper> helperLazy) {
if (!mediaTttFlags.isMediaTttEnabled()) {
return Optional.empty();
}
- return Optional.of(
- new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor));
+ return Optional.of(helperLazy.get());
}
/** */
@@ -147,13 +129,12 @@
@SysUISingleton
static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
MediaFlags mediaFlags,
- CommandRegistry commandRegistry,
- Context context
+ Lazy<MediaMuteAwaitConnectionCli> muteAwaitConnectionCliLazy
) {
if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
return Optional.empty();
}
- return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context));
+ return Optional.of(muteAwaitConnectionCliLazy.get());
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 5b29719..3cd3905 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -103,6 +103,7 @@
}
mCheckBox.setVisibility(View.GONE);
mStatusIcon.setVisibility(View.GONE);
+ mContainerLayout.setOnClickListener(null);
mTitleText.setTextColor(mController.getColorInactiveItem());
mSeekBar.getProgressDrawable().setColorFilter(
new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
@@ -151,6 +152,7 @@
setSingleLineLayout(getItemTitle(device), true /* bFocused */,
true /* showSeekBar */,
false /* showProgressBar */, false /* showStatus */);
+ mCheckBox.setOnCheckedChangeListener(null);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(true);
mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
@@ -169,6 +171,7 @@
initSeekbar(device);
mCurrentActivePosition = position;
} else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+ mCheckBox.setOnCheckedChangeListener(null);
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(false);
mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
@@ -204,7 +207,7 @@
d.setColorFilter(new PorterDuffColorFilter(
Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN));
mTitleIcon.setImageDrawable(d);
- mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
+ mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
}
}
@@ -241,11 +244,5 @@
notifyDataSetChanged();
}
}
-
- private void onItemClick(int customizedItem) {
- if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
- mController.launchBluetoothPairing();
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 85d1795..1f11d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -177,6 +177,9 @@
.mutate() : mContext.getDrawable(
R.drawable.media_output_item_background)
.mutate();
+ backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+ mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
mItemLayout.setBackground(backgroundDrawable);
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
@@ -212,8 +215,13 @@
mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
- mItemLayout.setBackground(mContext.getDrawable(R.drawable.media_output_item_background)
- .mutate());
+ final Drawable backgroundDrawable = mContext.getDrawable(
+ R.drawable.media_output_item_background)
+ .mutate();
+ backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+ mController.getColorItemBackground(),
+ PorterDuff.Mode.SRC_IN));
+ mItemLayout.setBackground(backgroundDrawable);
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
mTwoLineTitleText.setTranslationY(0);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index e55b25f..7bb5454 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -21,10 +21,15 @@
import android.app.WallpaperColors;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -76,8 +81,10 @@
private ImageView mAppResourceIcon;
private RecyclerView mDevicesRecyclerView;
private LinearLayout mDeviceListLayout;
+ private LinearLayout mCastAppLayout;
private Button mDoneButton;
private Button mStopButton;
+ private Button mAppButton;
private int mListMaxHeight;
private WallpaperColors mWallpaperColors;
@@ -129,7 +136,9 @@
mDeviceListLayout = mDialogView.requireViewById(R.id.device_list);
mDoneButton = mDialogView.requireViewById(R.id.done);
mStopButton = mDialogView.requireViewById(R.id.stop);
+ mAppButton = mDialogView.requireViewById(R.id.launch_app_button);
mAppResourceIcon = mDialogView.requireViewById(R.id.app_source_icon);
+ mCastAppLayout = mDialogView.requireViewById(R.id.cast_app_section);
mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
mDeviceListLayoutListener);
@@ -144,6 +153,13 @@
mMediaOutputController.releaseSession();
dismiss();
});
+ mAppButton.setOnClickListener(v -> {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ if (mMediaOutputController.getAppLaunchIntent() != null) {
+ mContext.startActivity(mMediaOutputController.getAppLaunchIntent());
+ }
+ dismiss();
+ });
}
@Override
@@ -169,8 +185,16 @@
final IconCompat iconCompat = getHeaderIcon();
final Drawable appSourceDrawable = getAppSourceIcon();
boolean colorSetUpdated = false;
+ mCastAppLayout.setVisibility(
+ mMediaOutputController.shouldShowLaunchSection()
+ ? View.VISIBLE : View.GONE);
if (appSourceDrawable != null) {
mAppResourceIcon.setImageDrawable(appSourceDrawable);
+ mAppButton.setCompoundDrawablesWithIntrinsicBounds(resizeDrawable(appSourceDrawable,
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_app_tier_icon_size
+ )),
+ null, null, null);
} else {
mAppResourceIcon.setVisibility(View.GONE);
}
@@ -205,6 +229,7 @@
R.dimen.media_output_dialog_header_icon_padding);
mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
}
+ mAppButton.setText(mMediaOutputController.getAppSourceName());
// Update title and subtitle
mHeaderTitle.setText(getHeaderText());
final CharSequence subTitle = getHeaderSubtitle();
@@ -229,6 +254,22 @@
mStopButton.setVisibility(getStopButtonVisibility());
}
+ private Drawable resizeDrawable(Drawable drawable, int size) {
+ if (drawable == null) {
+ return null;
+ }
+ int width = drawable.getIntrinsicWidth();
+ int height = drawable.getIntrinsicHeight();
+ Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
+ : Bitmap.Config.RGB_565;
+ Bitmap bitmap = Bitmap.createBitmap(width, height, config);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(canvas);
+ return new BitmapDrawable(mContext.getResources(),
+ Bitmap.createScaledBitmap(bitmap, size, size, false));
+ }
+
abstract Drawable getAppSourceIcon();
abstract int getHeaderIconRes();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 1c6b7dc..7bc0f52 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -22,6 +22,7 @@
import android.app.WallpaperColors;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
@@ -45,6 +46,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
+import androidx.mediarouter.media.MediaRouter;
+import androidx.mediarouter.media.MediaRouterParams;
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -56,6 +59,7 @@
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
@@ -108,6 +112,7 @@
private int mColorInactiveItem;
private int mColorSeekbarProgress;
private int mColorButtonBackground;
+ private int mColorItemBackground;
@Inject
public MediaOutputController(@NonNull Context context, String packageName,
@@ -139,6 +144,8 @@
android.R.color.system_accent1_200);
mColorButtonBackground = Utils.getColorStateListDefaultColor(mContext,
R.color.media_dialog_item_background);
+ mColorItemBackground = Utils.getColorStateListDefaultColor(mContext,
+ android.R.color.system_accent2_50);
}
void start(@NonNull Callback cb) {
@@ -171,6 +178,12 @@
mLocalMediaManager.startScan();
}
+ boolean shouldShowLaunchSection() {
+ MediaRouterParams routerParams = MediaRouter.getInstance(mContext).getRouterParams();
+ Log.d(TAG, "try to get routerParams: " + routerParams);
+ return routerParams != null && !routerParams.isMediaTransferReceiverEnabled();
+ }
+
void stop() {
if (mMediaController != null) {
mMediaController.unregisterCallback(mCb);
@@ -220,6 +233,32 @@
}
}
+ String getAppSourceName() {
+ if (mPackageName.isEmpty()) {
+ return null;
+ }
+ final PackageManager packageManager = mContext.getPackageManager();
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = packageManager.getApplicationInfo(mPackageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ } catch (PackageManager.NameNotFoundException e) {
+ applicationInfo = null;
+ }
+ final String applicationName =
+ (String) (applicationInfo != null ? packageManager.getApplicationLabel(
+ applicationInfo)
+ : mContext.getString(R.string.media_output_dialog_unknown_launch_app_name));
+ return applicationName;
+ }
+
+ Intent getAppLaunchIntent() {
+ if (mPackageName.isEmpty()) {
+ return null;
+ }
+ return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName);
+ }
+
CharSequence getHeaderTitle() {
if (mMediaController != null) {
final MediaMetadata metadata = mMediaController.getMetadata();
@@ -315,14 +354,16 @@
isDarkTheme);
if (isDarkTheme) {
mColorActiveItem = mCurrentColorScheme.getNeutral1().get(10);
- mColorInactiveItem = mCurrentColorScheme.getAccent1().get(2);
- mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(3);
+ mColorInactiveItem = mCurrentColorScheme.getNeutral1().get(10);
+ mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(2);
mColorButtonBackground = mCurrentColorScheme.getAccent1().get(2);
+ mColorItemBackground = mCurrentColorScheme.getAccent2().get(0);
} else {
mColorActiveItem = mCurrentColorScheme.getNeutral1().get(10);
mColorInactiveItem = mCurrentColorScheme.getAccent1().get(7);
mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(3);
- mColorButtonBackground = mCurrentColorScheme.getAccent2().get(1);
+ mColorButtonBackground = mCurrentColorScheme.getAccent1().get(3);
+ mColorItemBackground = mCurrentColorScheme.getAccent2().get(0);
}
}
@@ -342,6 +383,10 @@
return mColorButtonBackground;
}
+ public int getColorItemBackground() {
+ return mColorItemBackground;
+ }
+
private void buildMediaDevices(List<MediaDevice> devices) {
// For the first time building list, to make sure the top device is the connected device.
if (mMediaDevices.isEmpty()) {
@@ -533,12 +578,14 @@
return false;
}
- void launchBluetoothPairing() {
- // Dismissing a dialog into its touch surface and starting an activity at the same time
- // looks bad, so let's make sure the dialog just fades out quickly.
- mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
+ void launchBluetoothPairing(View view) {
+ ActivityLaunchAnimator.Controller controller =
+ mDialogLaunchAnimator.createActivityLaunchController(view);
- mCallback.dismissDialog();
+ if (controller == null) {
+ mCallback.dismissDialog();
+ }
+
Intent launchIntent =
new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -553,10 +600,10 @@
deepLinkIntent.putExtra(
Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
PAGE_CONNECTED_DEVICES_KEY);
- mActivityStarter.startActivity(deepLinkIntent, true);
+ mActivityStarter.startActivity(deepLinkIntent, true, controller);
return;
}
- mActivityStarter.startActivity(launchIntent, true);
+ mActivityStarter.startActivity(launchIntent, true, controller);
}
void launchMediaOutputGroupDialog(View mediaOutputDialog) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 4993105..ee2fba0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,8 +25,11 @@
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.DelayableExecutor
/**
* A superclass controller that provides common functionality for showing chips on the sender device
@@ -38,6 +41,7 @@
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
internal val context: Context,
private val windowManager: WindowManager,
+ @Main private val mainExecutor: DelayableExecutor,
@LayoutRes private val chipLayoutRes: Int
) {
/** The window layout parameters we'll use when attaching the view to a window. */
@@ -56,6 +60,9 @@
/** The chip view currently being displayed. Null if the chip is not being displayed. */
var chipView: ViewGroup? = null
+ /** A [Runnable] that, when run, will cancel the pending timeout of the chip. */
+ var cancelChipViewTimeout: Runnable? = null
+
/**
* Displays the chip with the current state.
*
@@ -77,8 +84,11 @@
if (oldChipView == null) {
windowManager.addView(chipView, windowLayoutParams)
}
- }
+ // Cancel and re-set the chip timeout each time we get a new state.
+ cancelChipViewTimeout?.run()
+ cancelChipViewTimeout = mainExecutor.executeDelayed(this::removeChip, TIMEOUT_MILLIS)
+ }
/** Hides the chip. */
fun removeChip() {
@@ -118,3 +128,5 @@
// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
// UpdateMediaTapToTransferReceiverDisplayTest
private const val WINDOW_TITLE = "Media Transfer Chip View"
+@VisibleForTesting
+const val TIMEOUT_MILLIS = 3000L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 18623c0..214a888 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -18,7 +18,6 @@
import android.app.StatusBarManager
import android.content.Context
-import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.os.Handler
@@ -27,10 +26,10 @@
import android.view.WindowManager
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
/**
@@ -43,9 +42,10 @@
commandQueue: CommandQueue,
context: Context,
windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
@Main private val mainHandler: Handler,
) : MediaTttChipControllerCommon<ChipStateReceiver>(
- context, windowManager, R.layout.media_ttt_chip_receiver
+ context, windowManager, mainExecutor, R.layout.media_ttt_chip_receiver
) {
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index da767ea..482e604 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -27,8 +27,10 @@
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
/**
@@ -40,8 +42,9 @@
commandQueue: CommandQueue,
context: Context,
windowManager: WindowManager,
+ @Main private val mainExecutor: DelayableExecutor,
) : MediaTttChipControllerCommon<ChipStateSender>(
- context, windowManager, R.layout.media_ttt_chip
+ context, windowManager, mainExecutor, R.layout.media_ttt_chip
) {
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index d16c019..8b39e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -542,7 +542,7 @@
return mNavigationBarView;
}
- public View createView(Bundle savedState) {
+ public View createView(Bundle savedState, boolean initialVisibility) {
mFrame = (NavigationBarFrame) LayoutInflater.from(mContext).inflate(
R.layout.navigation_bar_window, null);
View barView = LayoutInflater.from(mFrame.getContext()).inflate(
@@ -550,6 +550,8 @@
barView.addOnAttachStateChangeListener(this);
mNavigationBarView = barView.findViewById(R.id.navigation_bar_view);
+ mNavigationBarView.setVisibility(initialVisibility ? View.VISIBLE : View.INVISIBLE);
+
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView);
mWindowManager.addView(mFrame,
getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index aa1117c..a049736 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -85,6 +86,7 @@
private final NavigationBar.Factory mNavigationBarFactory;
private final DisplayManager mDisplayManager;
private final TaskbarDelegate mTaskbarDelegate;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private int mNavMode;
@VisibleForTesting boolean mIsTablet;
@@ -108,6 +110,7 @@
NavBarHelper navBarHelper,
TaskbarDelegate taskbarDelegate,
NavigationBar.Factory navigationBarFactory,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
DumpManager dumpManager,
AutoHideController autoHideController,
LightBarController lightBarController,
@@ -122,6 +125,7 @@
mConfigChanges.applyNewConfig(mContext.getResources());
mNavMode = navigationModeController.addListener(this);
mTaskbarDelegate = taskbarDelegate;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
navBarHelper, navigationModeController, sysUiFlagsContainer,
dumpManager, autoHideController, lightBarController, pipOptional,
@@ -323,7 +327,8 @@
mNavigationBars.put(displayId, navBar);
- View navigationBarView = navBar.createView(savedState);
+ boolean navBarVisible = mStatusBarKeyguardViewManager.isNavBarVisible();
+ View navigationBarView = navBar.createView(savedState, navBarVisible);
navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9199911..9ea2763 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -263,15 +263,6 @@
// Notify FalsingManager that an intentional gesture has occurred.
// TODO(b/186519446): use a different method than isFalseTouch
mFalsingManager.isFalseTouch(BACK_GESTURE);
- // Only inject back keycodes when ahead-of-time back dispatching is disabled.
- if (mBackAnimation == null) {
- boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
- boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
- + sendDown + ", up=" + sendUp);
- }
- }
mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
(int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 23482677..fe4cb71 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -17,6 +17,7 @@
package com.android.systemui.privacy
import android.content.Context
+import android.content.Intent
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.text.TextUtils
@@ -40,12 +41,12 @@
* @param context A context to create the dialog
* @param list list of elements to show in the dialog. The elements will show in the same order they
* appear in the list
- * @param activityStarter a callback to start an activity for a given package name and user id
+ * @param activityStarter a callback to start an activity for a given package name, user id, attributionTag and intent
*/
class PrivacyDialog(
context: Context,
private val list: List<PrivacyElement>,
- activityStarter: (String, Int) -> Unit
+ activityStarter: (String, Int, CharSequence?, Intent?) -> Unit
) : SystemUIDialog(context, R.style.PrivacyDialog) {
private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
@@ -118,13 +119,7 @@
app
}
val firstLine = context.getString(stringId, appName)
- val finalText = element.attribution?.let {
- TextUtils.concat(
- firstLine,
- " ",
- context.getString(R.string.ongoing_privacy_dialog_attribution_text, it)
- )
- } ?: firstLine
+ val finalText = getFinalText(firstLine, element.attributionLabel, element.proxyLabel)
newView.requireViewById<TextView>(R.id.text).text = finalText
if (element.phoneCall) {
newView.requireViewById<View>(R.id.chevron).visibility = View.GONE
@@ -138,6 +133,25 @@
return newView
}
+ private fun getFinalText(
+ firstLine: CharSequence,
+ attributionLabel: CharSequence?,
+ proxyLabel: CharSequence?
+ ): CharSequence {
+ var dialogText: CharSequence? = null
+ if (attributionLabel != null && proxyLabel != null) {
+ dialogText = context.getString(R.string.ongoing_privacy_dialog_attribution_proxy_label,
+ attributionLabel, proxyLabel)
+ } else if (attributionLabel != null) {
+ dialogText = context.getString(R.string.ongoing_privacy_dialog_attribution_label,
+ attributionLabel)
+ } else if (proxyLabel != null) {
+ dialogText = context.getString(R.string.ongoing_privacy_dialog_attribution_text,
+ proxyLabel)
+ }
+ return if (dialogText != null) TextUtils.concat(firstLine, " ", dialogText) else firstLine
+ }
+
private fun getStringIdForState(active: Boolean): Int {
return if (active) {
R.string.ongoing_privacy_dialog_using_op
@@ -157,7 +171,8 @@
private val clickListener = View.OnClickListener { v ->
v.tag?.let {
val element = it as PrivacyElement
- activityStarter(element.packageName, element.userId)
+ activityStarter(element.packageName, element.userId,
+ element.attributionTag, element.navigationIntent)
}
}
@@ -167,11 +182,15 @@
val packageName: String,
val userId: Int,
val applicationName: CharSequence,
- val attribution: CharSequence?,
+ val attributionTag: CharSequence?,
+ val attributionLabel: CharSequence?,
+ val proxyLabel: CharSequence?,
val lastActiveTimestamp: Long,
val active: Boolean,
val enterprise: Boolean,
- val phoneCall: Boolean
+ val phoneCall: Boolean,
+ val permGroupName: CharSequence,
+ val navigationIntent: Intent?
) {
private val builder = StringBuilder("PrivacyElement(")
@@ -180,8 +199,14 @@
builder.append(", packageName=$packageName")
builder.append(", userId=$userId")
builder.append(", appName=$applicationName")
- if (attribution != null) {
- builder.append(", attribution=$attribution")
+ if (attributionTag != null) {
+ builder.append(", attributionTag=$attributionTag")
+ }
+ if (attributionLabel != null) {
+ builder.append(", attributionLabel=$attributionLabel")
+ }
+ if (proxyLabel != null) {
+ builder.append(", proxyLabel=$proxyLabel")
}
builder.append(", lastActive=$lastActiveTimestamp")
if (active) {
@@ -193,7 +218,10 @@
if (phoneCall) {
builder.append(", phoneCall")
}
- builder.append(")")
+ builder.append(", permGroupName=$permGroupName)")
+ if (navigationIntent != null) {
+ builder.append(", navigationIntent=$navigationIntent")
+ }
}
override fun toString(): String = builder.toString()
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index d0aa710..6107123 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -19,11 +19,12 @@
import android.Manifest
import android.app.ActivityManager
import android.app.Dialog
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.UserHandle
-import android.permission.PermGroupUsage
+import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
import android.util.Log
import androidx.annotation.MainThread
@@ -45,11 +46,12 @@
override fun makeDialog(
context: Context,
list: List<PrivacyDialog.PrivacyElement>,
- starter: (String, Int) -> Unit
+ starter: (String, Int, CharSequence?, Intent?) -> Unit
): PrivacyDialog {
return PrivacyDialog(context, list, starter)
}
}
+
/**
* Controller for [PrivacyDialog].
*
@@ -115,12 +117,19 @@
}
@MainThread
- private fun startActivity(packageName: String, userId: Int) {
- val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
- intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+ private fun startActivity(
+ packageName: String,
+ userId: Int,
+ attributionTag: CharSequence?,
+ navigationIntent: Intent?
+ ) {
+ val intent = if (navigationIntent == null) {
+ getDefaultManageAppPermissionsIntent(packageName, userId)
+ } else {
+ navigationIntent
+ }
uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
- userId, packageName)
+ userId, packageName)
privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
if (!keyguardStateController.isUnlocked) {
// If we are locked, hide the dialog so the user can unlock
@@ -137,7 +146,39 @@
}
@WorkerThread
- private fun permGroupUsage(): List<PermGroupUsage> {
+ private fun getManagePermissionIntent(
+ packageName: String,
+ userId: Int,
+ permGroupName: CharSequence,
+ attributionTag: CharSequence?
+ ): Intent
+ {
+ lateinit var intent: Intent
+ if (attributionTag != null) {
+ intent = Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE)
+ intent.setPackage(packageName)
+ intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName.toString())
+ intent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, arrayOf(attributionTag.toString()))
+ intent.putExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, true)
+ val resolveInfo = packageManager.resolveActivity(
+ intent, PackageManager.ResolveInfoFlags.of(0))
+ ?: return getDefaultManageAppPermissionsIntent(packageName, userId)
+ intent.component = ComponentName(packageName, resolveInfo.activityInfo.name)
+ return intent
+ } else {
+ return getDefaultManageAppPermissionsIntent(packageName, userId)
+ }
+ }
+
+ fun getDefaultManageAppPermissionsIntent(packageName: String, userId: Int): Intent {
+ val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+ return intent
+ }
+
+ @WorkerThread
+ private fun permGroupUsage(): List<PermissionGroupUsage> {
return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
}
@@ -160,7 +201,7 @@
val userInfos = userTracker.userProfiles
privacyLogger.logUnfilteredPermGroupUsage(usage)
val items = usage.mapNotNull {
- val type = filterType(permGroupToPrivacyType(it.permGroupName))
+ val type = filterType(permGroupToPrivacyType(it.permissionGroupName))
val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
if (userInfo != null || it.isPhoneCall) {
type?.let { t ->
@@ -170,17 +211,24 @@
} else {
getLabelForPackage(it.packageName, it.uid)
}
+ val userId = UserHandle.getUserId(it.uid)
PrivacyDialog.PrivacyElement(
t,
it.packageName,
- UserHandle.getUserId(it.uid),
+ userId,
appName,
- it.attribution,
- it.lastAccess,
+ it.attributionTag,
+ it.attributionLabel,
+ it.proxyLabel,
+ it.lastAccessTimeMillis,
it.isActive,
// If there's no user info, we're in a phoneCall in secondary user
userInfo?.isManagedProfile ?: false,
- it.isPhoneCall
+ it.isPhoneCall,
+ it.permissionGroupName,
+ getManagePermissionIntent(it.packageName, userId,
+ it.permissionGroupName,
+ it.attributionTag)
)
}
} else {
@@ -215,8 +263,8 @@
private fun getLabelForPackage(packageName: String, uid: Int): CharSequence {
return try {
packageManager
- .getApplicationInfoAsUser(packageName, 0, UserHandle.getUserId(uid))
- .loadLabel(packageManager)
+ .getApplicationInfoAsUser(packageName, 0, UserHandle.getUserId(uid))
+ .loadLabel(packageManager)
} catch (_: PackageManager.NameNotFoundException) {
Log.w(TAG, "Label not found for: $packageName")
packageName
@@ -235,7 +283,7 @@
private fun filterType(type: PrivacyType?): PrivacyType? {
return type?.let {
if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) &&
- privacyItemController.micCameraAvailable) {
+ privacyItemController.micCameraAvailable) {
it
} else if (it == PrivacyType.TYPE_LOCATION && privacyItemController.locationAvailable) {
it
@@ -278,7 +326,7 @@
fun makeDialog(
context: Context,
list: List<PrivacyDialog.PrivacyElement>,
- starter: (String, Int) -> Unit
+ starter: (String, Int, CharSequence?, Intent?) -> Unit
): PrivacyDialog
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index 7c82a82..1a268b5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -16,7 +16,7 @@
package com.android.systemui.privacy.logging
-import android.permission.PermGroupUsage
+import android.permission.PermissionGroupUsage
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogMessage
@@ -100,7 +100,7 @@
})
}
- fun logUnfilteredPermGroupUsage(contents: List<PermGroupUsage>) {
+ fun logUnfilteredPermGroupUsage(contents: List<PermissionGroupUsage>) {
log(LogLevel.DEBUG, {
str1 = contents.toString()
}, {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java
index 6a6f572..e473dd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/AlphaControlledSignalTileView.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
@@ -73,7 +74,7 @@
}
@Override
- protected void setDrawableTintList(ColorStateList tint) {
+ protected void setDrawableTintList(@Nullable ColorStateList tint) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index 3b305bb..8afb793 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -1,8 +1,16 @@
package com.android.systemui.qs
+import android.content.Intent
+import android.permission.PermissionGroupUsage
+import android.permission.PermissionManager
+import android.provider.DeviceConfig
import android.view.View
+import androidx.annotation.WorkerThread
import com.android.internal.R
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyChipEvent
import com.android.systemui.privacy.PrivacyDialogController
@@ -10,7 +18,11 @@
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.util.DeviceConfigProxy
+import java.util.concurrent.Executor
import javax.inject.Inject
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
interface ChipVisibilityListener {
fun onChipVisibilityRefreshed(visible: Boolean)
@@ -32,18 +44,46 @@
private val privacyChip: OngoingPrivacyChip,
private val privacyDialogController: PrivacyDialogController,
private val privacyLogger: PrivacyLogger,
- private val iconContainer: StatusIconContainer
+ private val iconContainer: StatusIconContainer,
+ private val permissionManager: PermissionManager,
+ @Background private val backgroundExecutor: Executor,
+ @Main private val uiExecutor: Executor,
+ private val activityStarter: ActivityStarter,
+ private val appOpsController: AppOpsController,
+ private val deviceConfigProxy: DeviceConfigProxy
) {
+ companion object {
+ const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+ }
+
var chipVisibilityListener: ChipVisibilityListener? = null
+
private var listening = false
private var micCameraIndicatorsEnabled = false
private var locationIndicatorsEnabled = false
private var privacyChipLogged = false
+ private var safetyCenterEnabled = false
private val cameraSlot = privacyChip.resources.getString(R.string.status_bar_camera)
private val micSlot = privacyChip.resources.getString(R.string.status_bar_microphone)
private val locationSlot = privacyChip.resources.getString(R.string.status_bar_location)
+ private val devicePropertiesChangedListener =
+ object : DeviceConfig.OnPropertiesChangedListener {
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ safetyCenterEnabled = properties.getBoolean(SAFETY_CENTER_ENABLED, false)
+ }
+ }
+
+ init {
+ safetyCenterEnabled = deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_CENTER_ENABLED, false)
+ deviceConfigProxy.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ uiExecutor,
+ devicePropertiesChangedListener)
+ }
+
private val picCallback: PrivacyItemController.Callback =
object : PrivacyItemController.Callback {
override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
@@ -77,7 +117,11 @@
privacyChip.setOnClickListener {
// If the privacy chip is visible, it means there were some indicators
uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK)
- privacyDialogController.showDialog(privacyChip.context)
+ if (safetyCenterEnabled) {
+ showSafetyHub()
+ } else {
+ privacyDialogController.showDialog(privacyChip.context)
+ }
}
setChipVisibility(privacyChip.visibility == View.VISIBLE)
micCameraIndicatorsEnabled = privacyItemController.micCameraAvailable
@@ -87,6 +131,26 @@
updatePrivacyIconSlots()
}
+ private fun showSafetyHub() {
+ backgroundExecutor.execute {
+ val usage = ArrayList(permGroupUsage())
+ privacyLogger.logUnfilteredPermGroupUsage(usage)
+ val startSafetyHub = Intent(Intent.ACTION_VIEW_SAFETY_HUB)
+ startSafetyHub.putParcelableArrayListExtra(PermissionManager.EXTRA_PERMISSION_USAGES,
+ usage)
+ startSafetyHub.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ uiExecutor.execute {
+ activityStarter.startActivity(startSafetyHub, true,
+ ActivityLaunchAnimator.Controller.fromView(privacyChip))
+ }
+ }
+ }
+
+ @WorkerThread
+ private fun permGroupUsage(): List<PermissionGroupUsage> {
+ return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
+ }
+
fun onParentInvisible() {
chipVisibilityListener = null
privacyChip.setOnClickListener(null)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java
index 67cfc59..26399d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDualTileLabel.java
@@ -27,6 +27,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.systemui.R;
import java.util.Objects;
@@ -48,6 +50,7 @@
private final TextView mSecondLine;
private final int mHorizontalPaddingPx;
+ @Nullable
private String mText;
public QSDualTileLabel(Context context) {
@@ -122,6 +125,7 @@
rescheduleUpdateText();
}
+ @Nullable
public String getText() {
return mText;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 17f85ee..3c7933f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -381,14 +381,17 @@
: View.INVISIBLE);
mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
- boolean footerVisible = !mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating
+ boolean qsPanelVisible = !mQsDisabled && expandVisually;
+ boolean footerVisible = qsPanelVisible && (expanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard);
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
mQSFooterActionController.setVisible(footerVisible);
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (expanded && !mStackScrollerOverscrolling));
- mQSPanelController.setVisibility(
- !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
+ mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
+ if (DEBUG) {
+ Log.d(TAG, "Footer: " + footerVisible + ", QS Panel: " + qsPanelVisible);
+ }
}
private boolean isKeyguardState() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 0c854df..5126fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -48,6 +48,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/** View that represents the quick settings tile panel (when expanded/pulled down). **/
public class QSPanel extends LinearLayout implements Tunable {
@@ -78,7 +79,7 @@
protected boolean mExpanded;
protected boolean mListening;
- protected QSTileHost mHost;
+ @Nullable protected QSTileHost mHost;
private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
new ArrayList<>();
@@ -92,14 +93,18 @@
@Nullable
private ViewGroup mHeaderContainer;
+ @Nullable
private PageIndicator mFooterPageIndicator;
private int mContentMarginStart;
private int mContentMarginEnd;
private boolean mUsingHorizontalLayout;
+ @Nullable
private LinearLayout mHorizontalLinearLayout;
+ @Nullable
protected LinearLayout mHorizontalContentContainer;
+ @Nullable
protected QSTileLayout mTileLayout;
private float mSquishinessFraction = 1f;
private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
@@ -284,7 +289,7 @@
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (move) {
- int top = mChildrenLayoutTop.get(child);
+ int top = Objects.requireNonNull(mChildrenLayoutTop.get(child));
child.setLeftTopRightBottom(child.getLeft(), top + tileHeightOffset,
child.getRight(), top + tileHeightOffset + child.getHeight());
}
@@ -337,6 +342,7 @@
}
}
+ @Nullable
public QSTileHost getHost() {
return mHost;
}
@@ -501,7 +507,6 @@
mListening = listening;
}
-
protected void drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state) {
r.tileView.onStateChanged(state);
}
@@ -548,6 +553,7 @@
return getMeasuredHeight();
}
+ @Nullable
QSTileLayout getTileLayout() {
return mTileLayout;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 0bff722..3172aa9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -76,6 +76,7 @@
private Consumer<Boolean> mMediaVisibilityChangedListener;
private int mLastOrientation;
private String mCachedSpecs = "";
+ @Nullable
private QSTileRevealController mQsTileRevealController;
private float mRevealExpansion;
@@ -185,6 +186,7 @@
mDumpManager.unregisterDumpable(mView.getDumpableTag());
}
+ @Nullable
protected QSTileRevealController createTileRevealController() {
return null;
}
@@ -250,6 +252,7 @@
return !mRecords.isEmpty();
}
+ @Nullable
QSTileView getTileView(QSTile tile) {
for (QSPanelControllerBase.TileRecord r : mRecords) {
if (r.tile == tile) {
@@ -411,6 +414,7 @@
mUsingHorizontalLayoutChangedListener = listener;
}
+ @Nullable
public View getBrightnessView() {
return mView.getBrightnessView();
}
@@ -425,6 +429,7 @@
public QSTile tile;
public com.android.systemui.plugins.qs.QSTileView tileView;
public boolean scanState;
+ @Nullable
public QSTile.Callback callback;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index 4b705ad..6908e5a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -20,6 +20,7 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
@@ -81,6 +82,7 @@
private final CarrierConfigTracker mCarrierConfigTracker;
private boolean mIsSingleCarrier;
+ @Nullable
private OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
private final SlotIndexResolver mSlotIndexResolver;
@@ -294,7 +296,8 @@
* This will get notified when the number of carriers changes between 1 and "not one".
* @param listener
*/
- public void setOnSingleCarrierChangedListener(OnSingleCarrierChangedListener listener) {
+ public void setOnSingleCarrierChangedListener(
+ @Nullable OnSingleCarrierChangedListener listener) {
mOnSingleCarrierChangedListener = listener;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 90cf92a..c1970b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -96,15 +96,20 @@
private int mFocusIndex;
private boolean mNeedsFocus;
+ @Nullable
private List<String> mCurrentSpecs;
+ @Nullable
private List<TileInfo> mOtherTiles;
+ @Nullable
private List<TileInfo> mAllTiles;
+ @Nullable
private Holder mCurrentDrag;
private int mAccessibilityAction = ACTION_NONE;
private int mAccessibilityFromIndex;
private final UiEventLogger mUiEventLogger;
private final AccessibilityDelegateCompat mAccessibilityDelegate;
+ @Nullable
private RecyclerView mRecyclerView;
private int mNumColumns;
@@ -240,6 +245,7 @@
notifyDataSetChanged();
}
+ @Nullable
private TileInfo getAndRemoveOther(String s) {
for (int i = 0; i < mOtherTiles.size(); i++) {
if (mOtherTiles.get(i).spec.equals(s)) {
@@ -555,7 +561,7 @@
}
public class Holder extends ViewHolder {
- private QSTileViewImpl mTileView;
+ @Nullable private QSTileViewImpl mTileView;
public Holder(View itemView) {
super(itemView);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 106a1b6..7fb9ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -17,6 +17,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -49,6 +50,7 @@
private boolean mAnimationEnabled = true;
private int mState = -1;
private int mTint;
+ @Nullable
private QSTile.Icon mLastIcon;
public QSIconViewImpl(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index e8d27ec..e088f54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -36,6 +36,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -191,10 +192,16 @@
mContext,
ROUTE_TYPE_REMOTE_DISPLAY,
v -> {
- mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
- holder.mDialog.dismiss();
+ ActivityLaunchAnimator.Controller controller =
+ mDialogLaunchAnimator.createActivityLaunchController(v);
+
+ if (controller == null) {
+ holder.mDialog.dismiss();
+ }
+
mActivityStarter
- .postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
+ .postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
+ controller);
});
holder.init(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
@@ -316,5 +323,5 @@
public void onKeyguardShowingChanged() {
refreshState();
}
- };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index 4fe155c..e1d2070 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -164,7 +164,7 @@
if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
mWifiListLayout.setOnClickListener(
v -> mInternetDialogController.launchWifiNetworkDetailsSetting(
- wifiEntry.getKey()));
+ wifiEntry.getKey(), v));
return;
}
mWifiListLayout.setOnClickListener(v -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 8b6ddb4..d1c7844 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -355,8 +355,8 @@
isChecked, false);
}
});
- mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi());
- mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
+ mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
+ mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
mWiFiToggle.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (mWifiManager == null) return;
@@ -519,7 +519,7 @@
if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) {
final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo(
AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION,
- v -> mInternetDialogController.launchWifiScanningSetting());
+ mInternetDialogController::launchWifiScanningSetting);
mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify(
getContext().getText(R.string.wifi_scan_notify_message), linkInfo));
mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance());
@@ -527,15 +527,16 @@
mWifiScanNotifyLayout.setVisibility(View.VISIBLE);
}
- void onClickConnectedWifi() {
+ void onClickConnectedWifi(View view) {
if (mConnectedWifiEntry == null) {
return;
}
- mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey());
+ mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey(),
+ view);
}
- void onClickSeeMoreButton() {
- mInternetDialogController.launchNetworkSetting();
+ void onClickSeeMoreButton(View view) {
+ mInternetDialogController.launchNetworkSetting(view);
}
CharSequence getDialogTitleText() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index f89b7a3..b3bc3be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -72,6 +72,7 @@
import com.android.settingslib.net.SignalStrengthUtil;
import com.android.settingslib.wifi.WifiUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -620,36 +621,32 @@
return summary;
}
- void launchNetworkSetting() {
- // Dismissing a dialog into its touch surface and starting an activity at the same time
- // looks bad, so let's make sure the dialog just fades out quickly.
- mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
- mCallback.dismissDialog();
+ private void startActivity(Intent intent, View view) {
+ ActivityLaunchAnimator.Controller controller =
+ mDialogLaunchAnimator.createActivityLaunchController(view);
- mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
+ if (controller == null) {
+ mCallback.dismissDialog();
+ }
+
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller);
}
- void launchWifiNetworkDetailsSetting(String key) {
+ void launchNetworkSetting(View view) {
+ startActivity(getSettingsIntent(), view);
+ }
+
+ void launchWifiNetworkDetailsSetting(String key, View view) {
Intent intent = getWifiDetailsSettingsIntent(key);
if (intent != null) {
- // Dismissing a dialog into its touch surface and starting an activity at the same time
- // looks bad, so let's make sure the dialog just fades out quickly.
- mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
- mCallback.dismissDialog();
-
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ startActivity(intent, view);
}
}
- void launchWifiScanningSetting() {
- // Dismissing a dialog into its touch surface and starting an activity at the same time
- // looks bad, so let's make sure the dialog just fades out quickly.
- mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
- mCallback.dismissDialog();
-
+ void launchWifiScanningSetting(View view) {
final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ startActivity(intent, view);
}
void connectCarrierNetwork() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 8c8c5c8..88aa734 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -19,6 +19,7 @@
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEUTRAL
import android.content.Intent
import android.provider.Settings
import android.view.LayoutInflater
@@ -84,16 +85,20 @@
setPositiveButton(R.string.quick_settings_done) { _, _ ->
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
}
- setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ ->
+ setNeutralButton(R.string.quick_settings_more_user_settings, { _, _ ->
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
+ val controller = dialogLaunchAnimator.createActivityLaunchController(
+ getButton(BUTTON_NEUTRAL))
+
+ if (controller == null) {
+ dismiss()
+ }
+
activityStarter.postStartActivityDismissingKeyguard(
- USER_SETTINGS_INTENT,
- 0
- )
+ USER_SETTINGS_INTENT, 0, controller)
}
- }
+ }, false /* dismissOnClick */)
val gridFrame = LayoutInflater.from(this.context)
.inflate(R.layout.qs_user_dialog_content, null)
setView(gridFrame)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index f982790..4a9a1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -263,22 +263,29 @@
inoutInfo.touchableRegion.set(getTouchRegion(true));
}
- private Region getTouchRegion(boolean includeScrim) {
- Region touchRegion = new Region();
+ private Region getSwipeRegion() {
+ Region swipeRegion = new Region();
final Rect tmpRect = new Rect();
mScreenshotPreview.getBoundsOnScreen(tmpRect);
tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
mActionsContainerBackground.getBoundsOnScreen(tmpRect);
tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
(int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
- touchRegion.op(tmpRect, Region.Op.UNION);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
mDismissButton.getBoundsOnScreen(tmpRect);
- touchRegion.op(tmpRect, Region.Op.UNION);
+ swipeRegion.op(tmpRect, Region.Op.UNION);
+
+ return swipeRegion;
+ }
+
+ private Region getTouchRegion(boolean includeScrim) {
+ Region touchRegion = getSwipeRegion();
if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
+ final Rect tmpRect = new Rect();
mScrollingScrim.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
}
@@ -328,7 +335,7 @@
@Override // ViewGroup
public boolean onInterceptTouchEvent(MotionEvent ev) {
// scrolling scrim should not be swipeable; return early if we're on the scrim
- if (!getTouchRegion(false).contains((int) ev.getRawX(), (int) ev.getRawY())) {
+ if (!getSwipeRegion().contains((int) ev.getRawX(), (int) ev.getRawY())) {
return false;
}
// always pass through the down event so the swipe handler knows the initial state
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index b312ce2..8366bdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -67,7 +67,7 @@
wakefulnessLifecycle: WakefulnessLifecycle,
configurationController: ConfigurationController,
falsingManager: FalsingManager,
- dumpManager: DumpManager,
+ dumpManager: DumpManager
) : Dumpable {
private var pulseHeight: Float = 0f
private var useSplitShade: Boolean = false
@@ -363,6 +363,7 @@
notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - scrimProgress)
depthController.transitionToFullShadeProgress = scrimProgress
udfpsKeyguardViewController?.setTransitionToFullShadeProgress(scrimProgress)
+ statusbar.setTransitionToFullShadeProgress(scrimProgress)
}
private fun setDragDownAmountAnimated(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index fcbe179..02870a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -32,6 +32,7 @@
import android.text.format.DateFormat;
import android.util.FloatProperty;
import android.util.Log;
+import android.view.Choreographer;
import android.view.InsetsFlags;
import android.view.InsetsVisibilities;
import android.view.View;
@@ -44,6 +45,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
@@ -352,8 +354,17 @@
}
private void beginInteractionJankMonitor() {
+ final boolean shouldPost =
+ (mIsDozing && mDozeAmount == 0) || (!mIsDozing && mDozeAmount == 1);
if (mInteractionJankMonitor != null && mView != null && mView.isAttachedToWindow()) {
- mInteractionJankMonitor.begin(mView, getCujType());
+ if (shouldPost) {
+ Choreographer.getInstance().postCallback(
+ Choreographer.CALLBACK_ANIMATION, this::beginInteractionJankMonitor, null);
+ } else {
+ Configuration.Builder builder = Configuration.Builder.withView(getCujType(), mView)
+ .setDeferMonitorForAnimationStart(false);
+ mInteractionJankMonitor.begin(builder);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index d01fc93..10e90fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -55,7 +55,6 @@
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
ripplePaint.shader = rippleShader
- visibility = View.GONE
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -86,12 +85,10 @@
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
rippleInProgress = false
- visibility = View.GONE
onAnimationEnd?.run()
}
})
animator.start()
- visibility = View.VISIBLE
rippleInProgress = true
}
@@ -100,6 +97,11 @@
}
override fun onDraw(canvas: Canvas?) {
+ if (canvas == null || !canvas.isHardwareAccelerated) {
+ // Drawing with the ripple shader requires hardware acceleration, so skip
+ // if it's unsupported.
+ return
+ }
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index 842be5b..48717e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -28,15 +28,15 @@
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.Utils
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.leak.RotationUtils
-import com.android.systemui.R
-import com.android.systemui.flags.Flags
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
@@ -53,8 +53,8 @@
@SysUISingleton
class WiredChargingRippleController @Inject constructor(
commandRegistry: CommandRegistry,
- batteryController: BatteryController,
- configurationController: ConfigurationController,
+ private val batteryController: BatteryController,
+ private val configurationController: ConfigurationController,
featureFlags: FeatureFlags,
private val context: Context,
private val windowManager: WindowManager,
@@ -88,6 +88,11 @@
init {
pluggedIn = batteryController.isPluggedIn
+ commandRegistry.registerCommand("charging-ripple") { ChargingRippleCommand() }
+ updateRippleColor()
+ }
+
+ fun registerCallbacks() {
val batteryStateChangeCallback = object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged(
level: Int,
@@ -123,9 +128,6 @@
}
}
configurationController.addCallback(configurationChangedListener)
-
- commandRegistry.registerCommand("charging-ripple") { ChargingRippleCommand() }
- updateRippleColor()
}
// Lazily debounce ripple to avoid triggering ripple constantly (e.g. from flaky chargers).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index 5361a671..2b924a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -34,6 +34,7 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.wifi.WifiStatusTracker;
import com.android.systemui.R;
+import com.android.systemui.util.Assert;
import java.io.PrintWriter;
@@ -190,6 +191,7 @@
}
private void handleStatusUpdated() {
+ Assert.isMainThread();
copyWifiStates();
notifyListenersIfNecessary();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
index 6f8e5da..4ebf337 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
@@ -21,8 +21,9 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.ListenerSet
import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Binds
import dagger.Module
-import dagger.Provides
+import javax.inject.Inject
/**
* Choreographs evaluation resulting from multiple asynchronous sources. Specifically, it exposes
@@ -46,22 +47,21 @@
fun removeOnEvalListener(onEvalListener: Runnable)
}
+@Module(includes = [PrivateModule::class])
+object NotifPipelineChoreographerModule
+
@Module
-object NotifPipelineChoreographerModule {
- @Provides
- @JvmStatic
- @SysUISingleton
- fun provideChoreographer(
- choreographer: Choreographer,
- @Main mainExecutor: DelayableExecutor
- ): NotifPipelineChoreographer = NotifPipelineChoreographerImpl(choreographer, mainExecutor)
+private interface PrivateModule {
+ @Binds
+ fun bindChoreographer(impl: NotifPipelineChoreographerImpl): NotifPipelineChoreographer
}
private const val TIMEOUT_MS: Long = 100
-private class NotifPipelineChoreographerImpl(
+@SysUISingleton
+private class NotifPipelineChoreographerImpl @Inject constructor(
private val viewChoreographer: Choreographer,
- private val executor: DelayableExecutor
+ @Main private val executor: DelayableExecutor
) : NotifPipelineChoreographer {
private val listeners = ListenerSet<Runnable>()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 9c82cb6..72d0918 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -30,29 +30,24 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import dagger.Binds
import dagger.Module
-import dagger.Provides
+import javax.inject.Inject
+
+@Module(includes = [PrivateModule::class])
+interface SensitiveContentCoordinatorModule
@Module
-object SensitiveContentCoordinatorModule {
- @Provides
- @JvmStatic
- @CoordinatorScope
- fun provideCoordinator(
- dynamicPrivacyController: DynamicPrivacyController,
- lockscreenUserManager: NotificationLockscreenUserManager,
- keyguardUpdateMonitor: KeyguardUpdateMonitor,
- statusBarStateController: StatusBarStateController,
- keyguardStateController: KeyguardStateController
- ): SensitiveContentCoordinator =
- SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager,
- keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
+private interface PrivateModule {
+ @Binds
+ fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
}
/** Coordinates re-inflation and post-processing of sensitive notification content. */
interface SensitiveContentCoordinator : Coordinator
-private class SensitiveContentCoordinatorImpl(
+@CoordinatorScope
+private class SensitiveContentCoordinatorImpl @Inject constructor(
private val dynamicPrivacyController: DynamicPrivacyController,
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 3a3f581..952cd9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -202,10 +202,12 @@
float newHeight = state.height;
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
+ final boolean shadeClosedWithHUN =
+ ambientState.isShadeOpening() && !ambientState.isShadeExpanded();
if (mClipNotificationScrollToTop
&& (!state.inShelf || (isHeadsUp && !firstHeadsUp))
&& newYTranslation < clipStart
- && !ambientState.isShadeOpening()) {
+ && shadeClosedWithHUN) {
// The previous view is overlapping on top, clip!
float overlapAmount = clipStart - newYTranslation;
state.clipTopAmount = (int) overlapAmount;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d93c013..a0f8d05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -135,9 +135,6 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.idle.IdleHostView;
-import com.android.systemui.idle.IdleHostViewController;
-import com.android.systemui.idle.dagger.IdleViewComponent;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
@@ -322,7 +319,6 @@
private final KeyguardQsUserSwitchComponent.Factory mKeyguardQsUserSwitchComponentFactory;
private final KeyguardUserSwitcherComponent.Factory mKeyguardUserSwitcherComponentFactory;
private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
- private final IdleViewComponent.Factory mIdleViewComponentFactory;
private final FragmentService mFragmentService;
private final ScrimController mScrimController;
private final PrivacyDotViewController mPrivacyDotViewController;
@@ -356,8 +352,6 @@
@Nullable
private CommunalHostViewController mCommunalViewController;
private KeyguardStatusViewController mKeyguardStatusViewController;
- @Nullable
- private IdleHostViewController mIdleHostViewController;
private LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private NotificationsQSContainerController mNotificationsQSContainerController;
@@ -369,7 +363,6 @@
private VelocityTracker mQsVelocityTracker;
private boolean mQsTracking;
- private IdleHostView mIdleHostView;
private CommunalHostView mCommunalView;
/**
@@ -768,7 +761,6 @@
KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
CommunalViewComponent.Factory communalViewComponentFactory,
- IdleViewComponent.Factory idleViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
NotificationGroupManagerLegacy groupManager,
NotificationIconAreaController notificationIconAreaController,
@@ -839,7 +831,6 @@
mCommunalViewComponentFactory = communalViewComponentFactory;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
- mIdleViewComponentFactory = idleViewComponentFactory;
mDepthController = notificationShadeDepthController;
mContentResolver = contentResolver;
mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
@@ -966,7 +957,6 @@
private void onFinishInflate() {
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
- mIdleHostView = mView.findViewById(R.id.idle_host_view);
mCommunalView = mView.findViewById(R.id.communal_host);
FrameLayout userAvatarContainer = null;
@@ -989,12 +979,6 @@
.getKeyguardStatusBarViewController();
mKeyguardStatusBarViewController.init();
- if (mIdleHostView != null) {
- IdleViewComponent idleViewComponent = mIdleViewComponentFactory.build(mIdleHostView);
- mIdleHostViewController = idleViewComponent.getIdleHostViewController();
- mIdleHostViewController.init();
- }
-
if (mCommunalView != null) {
CommunalViewComponent communalViewComponent =
mCommunalViewComponentFactory.build(mCommunalView);
@@ -1008,7 +992,6 @@
mView.findViewById(R.id.keyguard_status_view),
userAvatarContainer,
keyguardUserSwitcherView,
- mIdleHostView,
mCommunalView);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
@@ -1101,7 +1084,6 @@
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
FrameLayout userAvatarView,
KeyguardUserSwitcherView keyguardUserSwitcherView,
- IdleHostView idleHostView,
CommunalHostView communalView) {
// Re-associate the KeyguardStatusViewController
KeyguardStatusViewComponent statusViewComponent =
@@ -1109,13 +1091,6 @@
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
- if (idleHostView != null && idleHostView != mIdleHostView) {
- mIdleHostView = idleHostView;
- IdleViewComponent idleViewComponent = mIdleViewComponentFactory.build(idleHostView);
- mIdleHostViewController = idleViewComponent.getIdleHostViewController();
- mIdleHostViewController.init();
- }
-
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
// Otherwise, NPV can get into a state where some of the views are still hidden
@@ -1287,7 +1262,7 @@
showKeyguardUserSwitcher /* enabled */);
updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
- keyguardUserSwitcherView, mView.findViewById(R.id.idle_host_view), mCommunalView);
+ keyguardUserSwitcherView, mCommunalView);
// Update keyguard bottom area
int index = mView.indexOfChild(mKeyguardBottomArea);
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 f13334e..82e0e67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -203,6 +203,7 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.charging.WiredChargingRippleController;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -343,6 +344,7 @@
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final DreamOverlayStateController mDreamOverlayStateController;
private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
+ private float mTransitionToFullShadeProgress = 0f;
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
updateBubblesVisibility();
@@ -786,7 +788,8 @@
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
- DreamOverlayStateController dreamOverlayStateController) {
+ DreamOverlayStateController dreamOverlayStateController,
+ WiredChargingRippleController wiredChargingRippleController) {
super(context);
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -912,6 +915,7 @@
deviceStateManager.registerCallback(mMainExecutor,
new FoldStateListener(mContext, this::onFoldedStateChanged));
+ wiredChargingRippleController.registerCallbacks();
}
@Override
@@ -2580,9 +2584,9 @@
return controllerFromStatusBar.get();
}
- if (dismissShade && rootView == mNotificationShadeWindowView) {
- // We are animating a view in the shade. We have to make sure that we collapse it when
- // the animation ends or is cancelled.
+ if (dismissShade) {
+ // If the view is not in the status bar, then we are animating a view in the shade.
+ // We have to make sure that we collapse it when the animation ends or is cancelled.
return new StatusBarLaunchAnimatorController(animationController, this,
true /* isLaunchForActivity */);
}
@@ -3774,6 +3778,15 @@
updateScrimController();
}
+ /**
+ * Set the amount of progress we are currently in if we're transitioning to the full shade.
+ * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
+ * shade.
+ */
+ public void setTransitionToFullShadeProgress(float transitionToFullShadeProgress) {
+ mTransitionToFullShadeProgress = transitionToFullShadeProgress;
+ }
+
@VisibleForTesting
public void updateScrimController() {
Trace.beginSection("StatusBar#updateScrimController");
@@ -3792,7 +3805,8 @@
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
- if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED) {
+ if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
+ || mTransitionToFullShadeProgress > 0f) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
} else {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d42a423..11d9c31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -68,10 +68,13 @@
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.Optional;
import javax.inject.Inject;
@@ -87,7 +90,7 @@
public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
PanelExpansionListener, NavigationModeController.ModeChangedListener,
- KeyguardViewController {
+ KeyguardViewController, FoldAodAnimationController.FoldAodAnimationStatus {
// When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
private static final long HIDE_TIMING_CORRECTION_MS = - 16 * 3;
@@ -113,6 +116,8 @@
private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
private final DreamOverlayStateController mDreamOverlayStateController;
+ @Nullable
+ private final FoldAodAnimationController mFoldAodAnimationController;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
private final Lazy<ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -186,6 +191,7 @@
private boolean mPulsing;
private boolean mGesturalNav;
private boolean mIsDocked;
+ private boolean mScreenOffAnimationPlaying;
protected boolean mFirstUpdate = true;
protected boolean mLastShowing;
@@ -199,6 +205,7 @@
private boolean mLastIsDocked;
private boolean mLastPulsing;
private int mLastBiometricMode;
+ private boolean mLastScreenOffAnimationPlaying;
private boolean mQsExpanded;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -246,6 +253,7 @@
NotificationMediaManager notificationMediaManager,
KeyguardBouncer.Factory keyguardBouncerFactory,
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
+ Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
LatencyTracker latencyTracker) {
mContext = context;
@@ -264,6 +272,8 @@
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
+ mFoldAodAnimationController = sysUIUnfoldComponent
+ .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
}
@Override
@@ -317,6 +327,9 @@
mConfigurationController.addCallback(this);
mGesturalNav = QuickStepContract.isGesturalMode(
mNavigationModeController.addListener(this));
+ if (mFoldAodAnimationController != null) {
+ mFoldAodAnimationController.addCallback(this);
+ }
if (mDockManager != null) {
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
@@ -965,6 +978,10 @@
private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() {
@Override
public void run() {
+ NavigationBarView view = mStatusBar.getNavigationBarView();
+ if (view != null) {
+ view.setVisibility(View.VISIBLE);
+ }
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.show(navigationBars());
}
@@ -1019,6 +1036,7 @@
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
mLastPulsing = mPulsing;
+ mLastScreenOffAnimationPlaying = mScreenOffAnimationPlaying;
mLastBiometricMode = mBiometricUnlockController.getMode();
mLastGesturalNav = mGesturalNav;
mLastIsDocked = mIsDocked;
@@ -1054,14 +1072,15 @@
/**
* @return Whether the navigation bar should be made visible based on the current state.
*/
- protected boolean isNavBarVisible() {
- int biometricMode = mBiometricUnlockController.getMode();
+ public boolean isNavBarVisible() {
+ boolean isWakeAndUnlockPulsing = mBiometricUnlockController != null
+ && mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardShowing = mShowing && !mOccluded;
- boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
+ boolean hideWhileDozing = mDozing && !isWakeAndUnlockPulsing;
boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive || keyguardWithGestureNav
+ return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
+ || mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav
|| mGlobalActionsVisible);
}
@@ -1073,8 +1092,8 @@
boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing
|| mLastPulsing && !mLastIsDocked) && mLastGesturalNav;
- return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive || keyguardWithGestureNav
+ return (!keyguardShowing && !hideWhileDozing && !mLastScreenOffAnimationPlaying
+ || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav
|| mLastGlobalActionsVisible);
}
@@ -1224,6 +1243,13 @@
setDozing(isDozing);
}
+ @Override
+ public void onFoldToAodAnimationChanged() {
+ if (mFoldAodAnimationController != null) {
+ mScreenOffAnimationPlaying = mFoldAodAnimationController.shouldPlayAnimation();
+ }
+ }
+
/**
* Whether qs is currently expanded.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 2ba37c2..09fca100 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -1,5 +1,6 @@
package com.android.systemui.statusbar.phone
+import android.view.View
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
@@ -12,6 +13,11 @@
private val statusBar: StatusBar,
private val isLaunchForActivity: Boolean = true
) : ActivityLaunchAnimator.Controller by delegate {
+ // Always sync the opening window with the shade, given that we draw a hole punch in the shade
+ // of the same size and position as the opening app to make it visible.
+ override val openingWindowSyncView: View?
+ get() = statusBar.notificationShadeWindowView
+
override fun onIntentStarted(willAnimate: Boolean) {
delegate.onIntentStarted(willAnimate)
if (!willAnimate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 6e1ec9c..9722528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -44,6 +44,9 @@
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Base class for dialogs that should appear over panels and keyguard.
*
@@ -68,6 +71,8 @@
private int mLastConfigurationWidthDp = -1;
private int mLastConfigurationHeightDp = -1;
+ private List<Runnable> mOnCreateRunnables = new ArrayList<>();
+
public SystemUIDialog(Context context) {
this(context, R.style.Theme_SystemUI_Dialog);
}
@@ -110,6 +115,10 @@
mLastConfigurationWidthDp = config.screenWidthDp;
mLastConfigurationHeightDp = config.screenHeightDp;
updateWindowSize();
+
+ for (int i = 0; i < mOnCreateRunnables.size(); i++) {
+ mOnCreateRunnables.get(i).run();
+ }
}
private void updateWindowSize() {
@@ -197,16 +206,67 @@
setMessage(mContext.getString(resId));
}
+ /**
+ * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog
+ * will automatically be dismissed when the button is clicked.
+ */
public void setPositiveButton(int resId, OnClickListener onClick) {
- setButton(BUTTON_POSITIVE, mContext.getString(resId), onClick);
+ setPositiveButton(resId, onClick, true /* dismissOnClick */);
}
+ /**
+ * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog
+ * will be dismissed when the button is clicked iff {@code dismissOnClick} is true.
+ */
+ public void setPositiveButton(int resId, OnClickListener onClick, boolean dismissOnClick) {
+ setButton(BUTTON_POSITIVE, resId, onClick, dismissOnClick);
+ }
+
+ /**
+ * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog
+ * will automatically be dismissed when the button is clicked.
+ */
public void setNegativeButton(int resId, OnClickListener onClick) {
- setButton(BUTTON_NEGATIVE, mContext.getString(resId), onClick);
+ setNegativeButton(resId, onClick, true /* dismissOnClick */);
}
+ /**
+ * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog
+ * will be dismissed when the button is clicked iff {@code dismissOnClick} is true.
+ */
+ public void setNegativeButton(int resId, OnClickListener onClick, boolean dismissOnClick) {
+ setButton(BUTTON_NEGATIVE, resId, onClick, dismissOnClick);
+ }
+
+ /**
+ * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog
+ * will automatically be dismissed when the button is clicked.
+ */
public void setNeutralButton(int resId, OnClickListener onClick) {
- setButton(BUTTON_NEUTRAL, mContext.getString(resId), onClick);
+ setNeutralButton(resId, onClick, true /* dismissOnClick */);
+ }
+
+ /**
+ * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog
+ * will be dismissed when the button is clicked iff {@code dismissOnClick} is true.
+ */
+ public void setNeutralButton(int resId, OnClickListener onClick, boolean dismissOnClick) {
+ setButton(BUTTON_NEUTRAL, resId, onClick, dismissOnClick);
+ }
+
+ private void setButton(int whichButton, int resId, OnClickListener onClick,
+ boolean dismissOnClick) {
+ if (dismissOnClick) {
+ setButton(whichButton, mContext.getString(resId), onClick);
+ } else {
+ // Set a null OnClickListener to make sure the button is still created and shown.
+ setButton(whichButton, mContext.getString(resId), (OnClickListener) null);
+
+ // When the dialog is created, set the click listener but don't dismiss the dialog when
+ // it is clicked.
+ mOnCreateRunnables.add(() -> getButton(whichButton).setOnClickListener(
+ view -> onClick.onClick(this, whichButton)));
+ }
}
public static void setShowForAllUsers(Dialog dialog, boolean show) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 83bdd1b..c6b5b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.charging.WiredChargingRippleController;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -233,7 +234,8 @@
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
- DreamOverlayStateController dreamOverlayStateController) {
+ DreamOverlayStateController dreamOverlayStateController,
+ WiredChargingRippleController wiredChargingRippleController) {
return new StatusBar(
context,
notificationsController,
@@ -330,7 +332,8 @@
notifPipelineFlags,
jankMonitor,
deviceStateManager,
- dreamOverlayStateController
+ dreamOverlayStateController,
+ wiredChargingRippleController
);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 5e91a25..f52c6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -272,7 +272,7 @@
}
boolean highPowerOp = areActiveHighPowerLocationRequests();
- mAreActiveLocationRequests = highPowerOp || shouldDisplay;
+ mAreActiveLocationRequests = shouldDisplay;
if (mAreActiveLocationRequests != hadActiveLocationRequests) {
mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 2e627a8..2a9076e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -48,17 +48,17 @@
private var pendingScrimReadyCallback: Runnable? = null
private var shouldPlayAnimation = false
+ private var isAnimationPlaying = false
+
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
private val startAnimationRunnable = Runnable {
statusBar.notificationPanelViewController.startFoldToAodAnimation {
// End action
- isAnimationPlaying = false
+ setAnimationState(playing = false)
}
}
- private var isAnimationPlaying = false
-
override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
this.statusBar = statusBar
@@ -71,17 +71,13 @@
override fun startAnimation(): Boolean =
if (alwaysOnEnabled &&
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
- globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0") {
- shouldPlayAnimation = true
-
- isAnimationPlaying = true
+ globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
+ ) {
+ setAnimationState(playing = true)
statusBar.notificationPanelViewController.prepareFoldToAodAnimation()
-
- statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
-
true
} else {
- shouldPlayAnimation = false
+ setAnimationState(playing = false)
false
}
@@ -91,8 +87,13 @@
statusBar.notificationPanelViewController.cancelFoldToAodAnimation();
}
- shouldPlayAnimation = false
- isAnimationPlaying = false
+ setAnimationState(playing = false)
+ }
+
+ private fun setAnimationState(playing: Boolean) {
+ shouldPlayAnimation = playing
+ isAnimationPlaying = playing
+ statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
index 3a270bb..0686071c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
@@ -19,6 +19,7 @@
import android.app.Dialog;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
@@ -70,10 +71,12 @@
}
Drawable newUserIcon = userIcon;
+ Resources res = mContext.getResources();
if (newUserIcon == null) {
- newUserIcon = UserIcons.getDefaultUserIcon(mContext.getResources(), user.id, false);
+ newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false);
}
- mUserManager.setUserIcon(user.id, UserIcons.convertToBitmap(newUserIcon));
+ mUserManager.setUserIcon(
+ user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon));
userCreationProgressDialog.dismiss();
successCallback.accept(user);
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java
index 5e9bae9..d2142dc 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java
@@ -48,8 +48,8 @@
R.dimen.card_carousel_dot_selected_radius);
mDotMargin = context.getResources().getDimensionPixelSize(R.dimen.card_carousel_dot_margin);
- mUnselectedColor = context.getColor(com.android.internal.R.color.system_neutral1_300);
- mSelectedColor = context.getColor(com.android.internal.R.color.system_neutral1_0);
+ mUnselectedColor = context.getColor(R.color.material_dynamic_neutral70);
+ mSelectedColor = context.getColor(R.color.material_dynamic_neutral100);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 9917777..92e7f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -278,7 +278,7 @@
private Drawable getHomeIndicatorDrawable() {
Drawable drawable = getDrawable(R.drawable.ic_close);
- drawable.setTint(getColor(com.android.internal.R.color.system_neutral1_300));
+ drawable.setTint(getColor(R.color.material_dynamic_neutral70));
return drawable;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 589eeb5..d5df9fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -46,7 +46,7 @@
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val launchContainer = LinearLayout(mContext)
- private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+ private val testLaunchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
@Mock lateinit var listener: ActivityLaunchAnimator.Listener
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@@ -58,7 +58,7 @@
@Before
fun setup() {
- activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator)
+ activityLaunchAnimator = ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator)
activityLaunchAnimator.callback = callback
activityLaunchAnimator.addListener(listener)
}
@@ -129,13 +129,11 @@
@Test
fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
`when`(callback.isOnKeyguard()).thenReturn(true)
- val animator = ActivityLaunchAnimator(launchAnimator)
- animator.callback = callback
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(animator) { adapter ->
+ startIntentWithAnimation(activityLaunchAnimator) { adapter ->
animationAdapter = adapter
ActivityManager.START_DELIVERED_TO_TOP
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 61e78f5..fe2efa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -20,8 +20,10 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import org.junit.After
+import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -43,7 +45,7 @@
@Before
fun setUp() {
dialogLaunchAnimator = DialogLaunchAnimator(
- dreamManager, launchAnimator, forceDisableSynchronization = true)
+ dreamManager, launchAnimator, isForTesting = true)
}
@After
@@ -92,11 +94,6 @@
// Clicking the transparent background should dismiss the dialog.
runOnMainThreadAndWaitForIdleSync {
- // TODO(b/204561691): Remove this call to disableAllCurrentDialogsExitAnimations() and
- // make sure that the test still pass on git_master/cf_x86_64_phone-userdebug in
- // Forrest.
- dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
-
transparentBackground.performClick()
}
assertFalse(dialog.isShowing)
@@ -110,7 +107,6 @@
assertTrue(firstDialog.isShowing)
assertTrue(secondDialog.isShowing)
runOnMainThreadAndWaitForIdleSync {
- dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
dialogLaunchAnimator.dismissStack(secondDialog)
}
@@ -118,7 +114,63 @@
assertFalse(secondDialog.isShowing)
}
+ @Test
+ fun testActivityLaunchControllerFromDialog() {
+ val firstDialog = createAndShowDialog()
+ val secondDialog = createDialogAndShowFromDialog(firstDialog)
+
+ val controller =
+ dialogLaunchAnimator.createActivityLaunchController(secondDialog.contentView)!!
+
+ // The dialog shouldn't be dismissable during the animation.
+ runOnMainThreadAndWaitForIdleSync {
+ controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
+ secondDialog.dismiss()
+ }
+ assertTrue(secondDialog.isShowing)
+
+ // Both dialogs should be dismissed at the end of the animation.
+ runOnMainThreadAndWaitForIdleSync {
+ controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+ }
+ assertFalse(firstDialog.isShowing)
+ assertFalse(secondDialog.isShowing)
+ }
+
+ @Test
+ fun testActivityLaunchFromHiddenDialog() {
+ val dialog = createAndShowDialog()
+ runOnMainThreadAndWaitForIdleSync {
+ dialog.hide()
+ }
+ assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView))
+ }
+
+ @Test
+ fun testDialogAnimationIsChangedByAnimator() {
+ // Important: the power menu animation relies on this behavior to know when to animate (see
+ // http://ag/16774605).
+ val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) }
+ dialog.window.setWindowAnimations(0)
+ assertEquals(0, dialog.window.attributes.windowAnimations)
+
+ val touchSurface = createTouchSurface()
+ runOnMainThreadAndWaitForIdleSync {
+ dialogLaunchAnimator.showFromView(dialog, touchSurface)
+ }
+ assertNotEquals(0, dialog.window.attributes.windowAnimations)
+ }
+
private fun createAndShowDialog(): TestDialog {
+ val touchSurface = createTouchSurface()
+ return runOnMainThreadAndWaitForIdleSync {
+ val dialog = TestDialog(context)
+ dialogLaunchAnimator.showFromView(dialog, touchSurface)
+ dialog
+ }
+ }
+
+ private fun createTouchSurface(): View {
return runOnMainThreadAndWaitForIdleSync {
val touchSurfaceRoot = LinearLayout(context)
val touchSurface = View(context)
@@ -129,9 +181,7 @@
ViewUtils.attachView(touchSurfaceRoot)
attachedViews.add(touchSurfaceRoot)
- val dialog = TestDialog(context)
- dialogLaunchAnimator.showFromView(dialog, touchSurface)
- dialog
+ touchSurface
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
deleted file mode 100644
index 500205c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalTrustedNetworkConditionTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.wifi.WifiInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalTrustedNetworkCondition;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.utils.os.FakeHandler;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class CommunalTrustedNetworkConditionTest extends SysuiTestCase {
- @Mock private ConnectivityManager mConnectivityManager;
-
- @Captor private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor;
-
- private final Handler mHandler = new FakeHandler(Looper.getMainLooper());
- private CommunalTrustedNetworkCondition mCondition;
-
- private final String mTrustedWifi1 = "wifi-1";
- private final String mTrustedWifi2 = "wifi-2";
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- final FakeSettings secureSettings = new FakeSettings();
- secureSettings.putStringForUser(Settings.Secure.COMMUNAL_MODE_TRUSTED_NETWORKS,
- mTrustedWifi1 + CommunalTrustedNetworkCondition.SETTINGS_STRING_DELIMINATOR
- + mTrustedWifi2, UserHandle.USER_SYSTEM);
- mCondition = new CommunalTrustedNetworkCondition(mHandler, mConnectivityManager,
- secureSettings);
- }
-
- @Test
- public void updateCallback_connectedToTrustedNetwork_reportsTrue() {
- final CommunalTrustedNetworkCondition.Callback callback =
- mock(CommunalTrustedNetworkCondition.Callback.class);
- mCondition.addCallback(callback);
-
- final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
-
- // Connected to trusted Wi-Fi network.
- final Network network = mock(Network.class);
- networkCallback.onAvailable(network);
- networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
-
- // Verifies that the callback is triggered.
- verify(callback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionMet()).isTrue();
- }
-
- @Test
- public void updateCallback_switchedToAnotherTrustedNetwork_reportsNothing() {
- final CommunalTrustedNetworkCondition.Callback callback =
- mock(CommunalTrustedNetworkCondition.Callback.class);
- mCondition.addCallback(callback);
-
- final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
-
- // Connected to a trusted Wi-Fi network.
- final Network network = mock(Network.class);
- networkCallback.onAvailable(network);
- networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
- clearInvocations(callback);
-
- // Connected to another trusted Wi-Fi network.
- networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi2));
-
- // Verifies that the callback is not triggered.
- verify(callback, never()).onConditionChanged(eq(mCondition));
- }
-
- @Test
- public void updateCallback_connectedToNonTrustedNetwork_reportsFalse() {
- final CommunalTrustedNetworkCondition.Callback callback =
- mock(CommunalTrustedNetworkCondition.Callback.class);
- mCondition.addCallback(callback);
-
- final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
-
- // Connected to trusted Wi-Fi network.
- final Network network = mock(Network.class);
- networkCallback.onAvailable(network);
- networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
-
- Mockito.clearInvocations(callback);
- // Connected to non-trusted Wi-Fi network.
- networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities("random-wifi"));
-
- // Verifies that the callback is triggered.
- verify(callback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- @Test
- public void updateCallback_disconnectedFromNetwork_reportsFalse() {
- final CommunalTrustedNetworkCondition.Callback callback =
- mock(CommunalTrustedNetworkCondition.Callback.class);
- mCondition.addCallback(callback);
-
- final ConnectivityManager.NetworkCallback networkCallback = captureNetworkCallback();
-
- // Connected to Wi-Fi.
- final Network network = mock(Network.class);
- networkCallback.onAvailable(network);
- networkCallback.onCapabilitiesChanged(network, fakeNetworkCapabilities(mTrustedWifi1));
- clearInvocations(callback);
-
- // Disconnected from Wi-Fi.
- networkCallback.onLost(network);
-
- // Verifies that the callback is triggered.
- verify(callback).onConditionChanged(mCondition);
- assertThat(mCondition.isConditionMet()).isFalse();
- }
-
- // Captures and returns the network callback, assuming it is registered with the connectivity
- // manager.
- private ConnectivityManager.NetworkCallback captureNetworkCallback() {
- verify(mConnectivityManager).registerNetworkCallback(any(NetworkRequest.class),
- mNetworkCallbackCaptor.capture());
- return mNetworkCallbackCaptor.getValue();
- }
-
- private NetworkCapabilities fakeNetworkCapabilities(String ssid) {
- final NetworkCapabilities networkCapabilities = mock(NetworkCapabilities.class);
- final WifiInfo wifiInfo = mock(WifiInfo.class);
- when(wifiInfo.getSSID()).thenReturn(ssid);
- when(networkCapabilities.getTransportInfo()).thenReturn(wifiInfo);
- return networkCapabilities;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
index 47ab17d..d3c465d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
@@ -23,22 +23,24 @@
import android.graphics.drawable.Icon
import android.service.controls.Control
import android.service.controls.DeviceTypes
+import android.service.controls.templates.ControlTemplate
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.R
-import com.android.systemui.controls.controller.ControlsController
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.runner.RunWith
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@SmallTest
@@ -49,11 +51,12 @@
private val clock = FakeSystemClock()
private lateinit var cvh: ControlViewHolder
+ private lateinit var baseLayout: ViewGroup
@Before
fun setUp() {
TestableLooper.get(this).runWithLooper {
- val baseLayout = LayoutInflater.from(mContext).inflate(
+ baseLayout = LayoutInflater.from(mContext).inflate(
R.layout.controls_base_item, null, false) as ViewGroup
cvh = ControlViewHolder(
@@ -106,6 +109,25 @@
assertThat(cvh.icon.imageTintList).isEqualTo(customIconTintList)
}
+
+ @Test
+ fun chevronIcon() {
+ val control = Control.StatefulBuilder(CONTROL_ID, mock(PendingIntent::class.java))
+ .setStatus(Control.STATUS_OK)
+ .setControlTemplate(ControlTemplate.NO_TEMPLATE)
+ .build()
+ val cws = ControlWithState(
+ ComponentName.createRelative("pkg", "cls"),
+ ControlInfo(
+ CONTROL_ID, CONTROL_TITLE, "subtitle", DeviceTypes.TYPE_AIR_FRESHENER
+ ),
+ control
+ )
+ cvh.bindData(cws, false)
+ val chevronIcon = baseLayout.findViewById<View>(R.id.chevron_icon)
+
+ assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE)
+ }
}
private const val CONTROL_ID = "CONTROL_ID"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 8078b6c..5684429 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -42,12 +42,14 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
+import android.view.Display;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -444,4 +446,20 @@
assertTrue(mServiceFake.requestedWakeup);
}
+
+ @Test
+ public void testDozePulsing_displayRequiresBlanking_screenState() {
+ DozeParameters dozeParameters = mock(DozeParameters.class);
+ when(dozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
+
+ assertEquals(Display.STATE_OFF, DOZE_REQUEST_PULSE.screenState(dozeParameters));
+ }
+
+ @Test
+ public void testDozePulsing_displayDoesNotRequireBlanking_screenState() {
+ DozeParameters dozeParameters = mock(DozeParameters.class);
+ when(dozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+
+ assertEquals(Display.STATE_ON, DOZE_REQUEST_PULSE.screenState(dozeParameters));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 627da3c..515a1ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -123,7 +123,7 @@
}
@Test
- public void testComplicationFiltering() {
+ public void testComplicationFilteringWhenShouldShowComplications() {
final DreamOverlayStateController stateController =
new DreamOverlayStateController(mExecutor);
@@ -160,4 +160,50 @@
}
}
+
+ @Test
+ public void testComplicationFilteringWhenShouldHideComplications() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+ stateController.setShouldShowComplications(true);
+
+ final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
+ final Complication weatherComplication = Mockito.mock(Complication.class);
+ when(alwaysAvailableComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_NONE);
+ when(weatherComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_WEATHER);
+
+ stateController.addComplication(alwaysAvailableComplication);
+ stateController.addComplication(weatherComplication);
+
+ final DreamOverlayStateController.Callback callback =
+ Mockito.mock(DreamOverlayStateController.Callback.class);
+
+ stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER);
+ stateController.addCallback(callback);
+ mExecutor.runAllReady();
+
+ {
+ clearInvocations(callback);
+ stateController.setShouldShowComplications(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onAvailableComplicationTypesChanged();
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+ assertThat(complications.contains(weatherComplication)).isTrue();
+ }
+
+ {
+ clearInvocations(callback);
+ stateController.setShouldShowComplications(false);
+ mExecutor.runAllReady();
+
+ verify(callback).onAvailableComplicationTypesChanged();
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(alwaysAvailableComplication)).isTrue();
+ assertThat(complications.contains(weatherComplication)).isFalse();
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt
deleted file mode 100644
index 98b9252..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/idle/AmbientLightModeMonitorTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle
-
-import android.hardware.Sensor
-import android.hardware.SensorEventListener
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.sensors.AsyncSensorManager
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.never
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class AmbientLightModeMonitorTest : SysuiTestCase() {
- @Mock private lateinit var sensorManager: AsyncSensorManager
- @Mock private lateinit var sensor: Sensor
- @Mock private lateinit var algorithm: AmbientLightModeMonitor.DebounceAlgorithm
-
- private lateinit var ambientLightModeMonitor: AmbientLightModeMonitor
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- `when`(sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(sensor)
-
- ambientLightModeMonitor = AmbientLightModeMonitor(algorithm, sensorManager)
- }
-
- @Test
- fun shouldRegisterSensorEventListenerOnStart() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- ambientLightModeMonitor.start(callback)
-
- verify(sensorManager).registerListener(any(), eq(sensor), anyInt())
- }
-
- @Test
- fun shouldUnregisterSensorEventListenerOnStop() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- ambientLightModeMonitor.start(callback)
-
- val sensorEventListener = captureSensorEventListener()
-
- ambientLightModeMonitor.stop()
-
- verify(sensorManager).unregisterListener(eq(sensorEventListener))
- }
-
- @Test
- fun shouldStartDebounceAlgorithmOnStart() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- ambientLightModeMonitor.start(callback)
-
- verify(algorithm).start(eq(callback))
- }
-
- @Test
- fun shouldStopDebounceAlgorithmOnStop() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- ambientLightModeMonitor.start(callback)
- ambientLightModeMonitor.stop()
-
- verify(algorithm).stop()
- }
-
- @Test
- fun shouldNotRegisterForSensorUpdatesIfSensorNotAvailable() {
- `when`(sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null)
- val ambientLightModeMonitor = AmbientLightModeMonitor(algorithm, sensorManager)
-
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- ambientLightModeMonitor.start(callback)
-
- verify(sensorManager, never()).registerListener(any(), any(Sensor::class.java), anyInt())
- }
-
- // Captures [SensorEventListener], assuming it has been registered with [sensorManager].
- private fun captureSensorEventListener(): SensorEventListener {
- val captor = ArgumentCaptor.forClass(SensorEventListener::class.java)
- verify(sensorManager).registerListener(captor.capture(), any(), anyInt())
- return captor.value
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java
deleted file mode 100644
index 3c24a3a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/idle/IdleHostViewControllerTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.os.PowerManager;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import javax.inject.Provider;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class IdleHostViewControllerTest extends SysuiTestCase {
- @Mock private BroadcastDispatcher mBroadcastDispatcher;
- @Mock private PowerManager mPowerManager;
- @Mock private IdleHostView mIdleHostView;
- @Mock private Resources mResources;
- @Mock private Provider<View> mViewProvider;
- @Mock private KeyguardStateController mKeyguardStateController;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private AmbientLightModeMonitor mAmbientLightModeMonitor;
-
- private KeyguardStateController.Callback mKeyguardStateCallback;
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- when(mResources.getBoolean(R.bool.config_enableIdleMode)).thenReturn(true);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
-
- final IdleHostViewController controller = new IdleHostViewController(mBroadcastDispatcher,
- mPowerManager, mIdleHostView, mResources, mViewProvider, mKeyguardStateController,
- mStatusBarStateController, mAmbientLightModeMonitor);
- controller.init();
- controller.onViewAttached();
-
- // Captures keyguard state controller callback.
- ArgumentCaptor<KeyguardStateController.Callback> keyguardStateCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
- verify(mKeyguardStateController).addCallback(keyguardStateCallbackCaptor.capture());
- mKeyguardStateCallback = keyguardStateCallbackCaptor.getValue();
-
- // Captures status bar state listener.
- ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture());
- mStatusBarStateListener = statusBarStateListenerCaptor.getValue();
- }
-
- @Test
- public void testStartDozingWhenLowLight() {
- // Keyguard showing.
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- mKeyguardStateCallback.onKeyguardShowingChanged();
-
- // Regular ambient lighting.
- final AmbientLightModeMonitor.Callback lightMonitorCallback =
- captureAmbientLightModeMonitorCallback();
- lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
-
- // Verifies it doesn't go to sleep yet.
- verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
-
- // Ambient lighting becomes dim.
- lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
-
- // Verifies it goes to sleep.
- verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
- }
-
- @Test
- public void testWakeUpWhenRegularLight() {
- // Keyguard showing.
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- mKeyguardStateCallback.onKeyguardShowingChanged();
-
- // In low light / dozing.
- final AmbientLightModeMonitor.Callback lightMonitorCallback =
- captureAmbientLightModeMonitorCallback();
- lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
- mStatusBarStateListener.onDozingChanged(true /*isDozing*/);
-
- // Regular ambient lighting.
- lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
-
- // Verifies it wakes up from sleep.
- verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
- }
-
- // Captures [AmbientLightModeMonitor.Callback] assuming that the ambient light mode monitor
- // has been started.
- private AmbientLightModeMonitor.Callback captureAmbientLightModeMonitorCallback() {
- ArgumentCaptor<AmbientLightModeMonitor.Callback> captor =
- ArgumentCaptor.forClass(AmbientLightModeMonitor.Callback.class);
- verify(mAmbientLightModeMonitor).start(captor.capture());
- return captor.getValue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/idle/LightSensorDebounceAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/idle/LightSensorDebounceAlgorithmTest.kt
deleted file mode 100644
index ebc7345..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/idle/LightSensorDebounceAlgorithmTest.kt
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.idle
-
-import android.content.res.Resources
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.`when`
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class LightSensorDebounceAlgorithmTest : SysuiTestCase() {
- @Mock private lateinit var resources: Resources
-
- private val systemClock = FakeSystemClock()
- private val executor = FakeExecutor(systemClock)
-
- private lateinit var algorithm: LightSensorEventsDebounceAlgorithm
-
- private val mockLightModeThreshold = 5
- private val mockDarkModeThreshold = 2
- private val mockLightModeSpan = 100
- private val mockDarkModeSpan = 50
- private val mockLightModeFrequency = 10
- private val mockDarkModeFrequency = 5
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- `when`(resources.getInteger(R.integer.config_ambientLightModeThreshold))
- .thenReturn(mockLightModeThreshold)
- `when`(resources.getInteger(R.integer.config_ambientDarkModeThreshold))
- .thenReturn(mockDarkModeThreshold)
- `when`(resources.getInteger(R.integer.config_ambientLightModeSamplingSpanMillis))
- .thenReturn(mockLightModeSpan)
- `when`(resources.getInteger(R.integer.config_ambientDarkModeSamplingSpanMillis))
- .thenReturn(mockDarkModeSpan)
- `when`(resources.getInteger(R.integer.config_ambientLightModeSamplingFrequencyMillis))
- .thenReturn(mockLightModeFrequency)
- `when`(resources.getInteger(R.integer.config_ambientDarkModeSamplingFrequencyMillis))
- .thenReturn(mockDarkModeFrequency)
-
- algorithm = LightSensorEventsDebounceAlgorithm(executor, resources)
- }
-
- @Test
- fun shouldOnlyTriggerCallbackWhenValueChanges() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- algorithm.start(callback)
-
- // Light mode, should trigger callback.
- algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT
- verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
- reset(callback)
-
- // Light mode again, should NOT trigger callback.
- algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT
- verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
- reset(callback)
-
- // Dark mode, should trigger callback.
- algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
- verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
- reset(callback)
-
- // Dark mode again, should not trigger callback.
- algorithm.mode = AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK
- verify(callback, never()).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
- }
-
- @Test
- fun shouldReportUndecidedWhenNeitherLightNorDarkClaimIsTrue() {
- algorithm.isDarkMode = false
- algorithm.isLightMode = false
-
- assertThat(algorithm.mode).isEqualTo(
- AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_UNDECIDED)
- }
-
- @Test
- fun shouldReportDarkModeAsLongAsDarkModeClaimIsTrue() {
- algorithm.isDarkMode = true
- algorithm.isLightMode = false
-
- assertThat(algorithm.mode).isEqualTo(
- AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
-
- algorithm.isLightMode = true
- assertThat(algorithm.mode).isEqualTo(
- AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
- }
-
- @Test
- fun shouldReportLightModeWhenLightModeClaimIsTrueAndDarkModeClaimIsFalse() {
- algorithm.isLightMode = true
- algorithm.isDarkMode = false
-
- assertThat(algorithm.mode).isEqualTo(
- AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
- }
-
- @Test
- fun shouldSetIsLightModeToTrueWhenBundleAverageIsGreaterThanThreshold() {
- // Note: [mockLightModeThreshold] is 5.0.
- algorithm.bundleAverageLightMode = 5.1
- assertThat(algorithm.isLightMode).isTrue()
-
- algorithm.bundleAverageLightMode = 10.0
- assertThat(algorithm.isLightMode).isTrue()
-
- algorithm.bundleAverageLightMode = 20.0
- assertThat(algorithm.isLightMode).isTrue()
-
- algorithm.bundleAverageLightMode = 5.0
- assertThat(algorithm.isLightMode).isFalse()
-
- algorithm.bundleAverageLightMode = 3.0
- assertThat(algorithm.isLightMode).isFalse()
-
- algorithm.bundleAverageLightMode = 0.0
- assertThat(algorithm.isLightMode).isFalse()
- }
-
- @Test
- fun shouldSetIsDarkModeToTrueWhenBundleAverageIsLessThanThreshold() {
- // Note: [mockDarkModeThreshold] is 2.0.
- algorithm.bundleAverageDarkMode = 1.9
- assertThat(algorithm.isDarkMode).isTrue()
-
- algorithm.bundleAverageDarkMode = 1.0
- assertThat(algorithm.isDarkMode).isTrue()
-
- algorithm.bundleAverageDarkMode = 0.0
- assertThat(algorithm.isDarkMode).isTrue()
-
- algorithm.bundleAverageDarkMode = 2.0
- assertThat(algorithm.isDarkMode).isFalse()
-
- algorithm.bundleAverageDarkMode = 3.0
- assertThat(algorithm.isDarkMode).isFalse()
-
- algorithm.bundleAverageDarkMode = 10.0
- assertThat(algorithm.isDarkMode).isFalse()
- }
-
- @Test
- fun shouldCorrectlyCalculateAverageFromABundle() {
- // For light mode.
- algorithm.bundleLightMode = arrayListOf(1.0f, 3.0f, 5.0f, 7.0f)
- assertThat(algorithm.bundleAverageLightMode).isEqualTo(4.0)
-
- algorithm.bundleLightMode = arrayListOf(2.0f, 4.0f, 6.0f, 8.0f)
- assertThat(algorithm.bundleAverageLightMode).isEqualTo(5.0)
-
- // For dark mode.
- algorithm.bundleDarkMode = arrayListOf(1.0f, 3.0f, 5.0f, 7.0f, 9.0f)
- assertThat(algorithm.bundleAverageDarkMode).isEqualTo(5.0)
-
- algorithm.bundleDarkMode = arrayListOf(2.0f, 4.0f, 6.0f, 8.0f, 10.0f)
- assertThat(algorithm.bundleAverageDarkMode).isEqualTo(6.0)
- }
-
- @Test
- fun shouldAddSensorEventUpdatesToBundles() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- // On start() one bundle is created for light and dark mode each.
- algorithm.start(callback)
- executor.runAllReady()
-
- // Add 1 more bundle to queue for each mode.
- algorithm.bundlesQueueLightMode.add(ArrayList())
- algorithm.bundlesQueueDarkMode.add(ArrayList())
-
- algorithm.onUpdateLightSensorEvent(1.0f)
- algorithm.onUpdateLightSensorEvent(1.0f)
- algorithm.onUpdateLightSensorEvent(2.0f)
- algorithm.onUpdateLightSensorEvent(3.0f)
- algorithm.onUpdateLightSensorEvent(4.0f)
-
- val expectedValues = listOf(1.0f, 1.0f, 2.0f, 3.0f, 4.0f)
-
- assertBundleContainsAll(algorithm.bundlesQueueLightMode[0], expectedValues)
- assertBundleContainsAll(algorithm.bundlesQueueLightMode[1], expectedValues)
- assertBundleContainsAll(algorithm.bundlesQueueDarkMode[0], expectedValues)
- assertBundleContainsAll(algorithm.bundlesQueueDarkMode[1], expectedValues)
- }
-
- @Test
- fun shouldCorrectlyEnqueueLightModeBundles() {
- assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(0)
-
- algorithm.enqueueLightModeBundle.run()
- assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(1)
-
- algorithm.enqueueLightModeBundle.run()
- assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(2)
-
- algorithm.enqueueLightModeBundle.run()
- assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(3)
-
- // Verifies dark mode bundles queue is not impacted.
- assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(0)
- }
-
- @Test
- fun shouldCorrectlyEnqueueDarkModeBundles() {
- assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(0)
-
- algorithm.enqueueDarkModeBundle.run()
- assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(1)
-
- algorithm.enqueueDarkModeBundle.run()
- assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(2)
-
- algorithm.enqueueDarkModeBundle.run()
- assertThat(algorithm.bundlesQueueDarkMode.size).isEqualTo(3)
-
- // Verifies light mode bundles queue is not impacted.
- assertThat(algorithm.bundlesQueueLightMode.size).isEqualTo(0)
- }
-
- @Test
- fun shouldCorrectlyDequeueLightModeBundles() {
- // Sets up the light mode bundles queue.
- val bundle1 = arrayListOf(1.0f, 3.0f, 6.0f, 9.0f)
- val bundle2 = arrayListOf(5.0f, 10f)
- val bundle3 = arrayListOf(2.0f, 4.0f)
- algorithm.bundlesQueueLightMode.add(bundle1)
- algorithm.bundlesQueueLightMode.add(bundle2)
- algorithm.bundlesQueueLightMode.add(bundle3)
-
- // The committed bundle should be the first one in queue.
- algorithm.dequeueLightModeBundle.run()
- assertBundleContainsAll(algorithm.bundleLightMode, bundle1)
-
- algorithm.dequeueLightModeBundle.run()
- assertBundleContainsAll(algorithm.bundleLightMode, bundle2)
-
- algorithm.dequeueLightModeBundle.run()
- assertBundleContainsAll(algorithm.bundleLightMode, bundle3)
-
- // Verifies that the dark mode bundle is not impacted.
- assertBundleContainsAll(algorithm.bundleDarkMode, listOf())
- }
-
- @Test
- fun shouldCorrectlyDequeueDarkModeBundles() {
- // Sets up the dark mode bundles queue.
- val bundle1 = arrayListOf(2.0f, 4.0f)
- val bundle2 = arrayListOf(5.0f, 10f)
- val bundle3 = arrayListOf(1.0f, 3.0f, 6.0f, 9.0f)
- algorithm.bundlesQueueDarkMode.add(bundle1)
- algorithm.bundlesQueueDarkMode.add(bundle2)
- algorithm.bundlesQueueDarkMode.add(bundle3)
-
- // The committed bundle should be the first one in queue.
- algorithm.dequeueDarkModeBundle.run()
- assertBundleContainsAll(algorithm.bundleDarkMode, bundle1)
-
- algorithm.dequeueDarkModeBundle.run()
- assertBundleContainsAll(algorithm.bundleDarkMode, bundle2)
-
- algorithm.dequeueDarkModeBundle.run()
- assertBundleContainsAll(algorithm.bundleDarkMode, bundle3)
-
- // Verifies that the light mode bundle is not impacted.
- assertBundleContainsAll(algorithm.bundleLightMode, listOf())
- }
-
- @Test
- fun shouldSetLightSensorLevelFromSensorEventUpdates() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- algorithm.start(callback)
-
- algorithm.onUpdateLightSensorEvent(1.0f)
- assertThat(algorithm.lightSensorLevel).isEqualTo(1.0f)
-
- algorithm.onUpdateLightSensorEvent(10.0f)
- assertThat(algorithm.lightSensorLevel).isEqualTo(10.0f)
-
- algorithm.onUpdateLightSensorEvent(0.0f)
- assertThat(algorithm.lightSensorLevel).isEqualTo(0.0f)
- }
-
- @Test
- fun shouldRippleFromSensorEventUpdatesDownToAmbientLightMode() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- algorithm.start(callback)
- executor.runAllReady()
-
- // Sensor event updates.
- algorithm.onUpdateLightSensorEvent(10.0f)
- algorithm.onUpdateLightSensorEvent(15.0f)
- algorithm.onUpdateLightSensorEvent(12.0f)
- algorithm.onUpdateLightSensorEvent(10.0f)
-
- // Advances time so both light and dark claims have been calculated.
- systemClock.advanceTime((mockLightModeSpan + 1).toLong())
-
- // Verifies the callback is triggered the ambient mode has changed LIGHT.
- verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT)
- }
-
- @Test
- fun shouldRippleFromSensorEventUpdatesDownToAmbientDarkMode() {
- val callback = mock(AmbientLightModeMonitor.Callback::class.java)
- algorithm.start(callback)
- executor.runAllReady()
-
- // Sensor event updates.
- algorithm.onUpdateLightSensorEvent(1.0f)
- algorithm.onUpdateLightSensorEvent(0.5f)
- algorithm.onUpdateLightSensorEvent(1.2f)
- algorithm.onUpdateLightSensorEvent(0.8f)
-
- // Advances time so both light and dark claims have been calculated.
- systemClock.advanceTime((mockLightModeSpan + 1).toLong())
-
- // Verifies the callback is triggered the ambient mode has changed DARK.
- verify(callback).onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK)
- }
-
- // Asserts that [bundle] contains the same elements as [expected], not necessarily in the same
- // order.
- private fun assertBundleContainsAll(bundle: ArrayList<Float>, expected: Collection<Float>) {
- assertThat(bundle.size).isEqualTo(expected.size)
- assertThat(bundle.containsAll(expected))
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 421ae03..11326e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -59,6 +59,7 @@
private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
private Icon mIcon = mock(Icon.class);
private IconCompat mIconCompat = mock(IconCompat.class);
+ private View mDialogLaunchView = mock(View.class);
private MediaOutputAdapter mMediaOutputAdapter;
private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
@@ -245,7 +246,7 @@
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
mViewHolder.mContainerLayout.performClick();
- verify(mMediaOutputController).launchBluetoothPairing();
+ verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 14afece..794bc09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -39,7 +39,6 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -49,7 +48,6 @@
import java.util.concurrent.Executor
@SmallTest
-@Ignore("b/216286227")
class MediaTttCommandLineHelperTest : SysuiTestCase() {
private val inlineExecutor = Executor { command -> command.run() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index f05d621..ea0a5a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -26,10 +25,13 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
@@ -39,10 +41,12 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@Ignore("b/216286227")
class MediaTttChipControllerCommonTest : SysuiTestCase() {
private lateinit var controllerCommon: MediaTttChipControllerCommon<MediaTttChipState>
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
+
private lateinit var appIconDrawable: Drawable
@Mock
private lateinit var windowManager: WindowManager
@@ -50,8 +54,11 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
- controllerCommon = TestControllerCommon(context, windowManager)
+ appIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
+ controllerCommon = TestControllerCommon(context, windowManager, fakeExecutor)
}
@Test
@@ -71,6 +78,58 @@
}
@Test
+ fun displayChip_chipDoesNotDisappearsBeforeTimeout() {
+ controllerCommon.displayChip(getState())
+ reset(windowManager)
+
+ fakeClock.advanceTime(TIMEOUT_MILLIS - 1)
+
+ verify(windowManager, never()).removeView(any())
+ }
+
+ @Test
+ fun displayChip_chipDisappearsAfterTimeout() {
+ controllerCommon.displayChip(getState())
+ reset(windowManager)
+
+ fakeClock.advanceTime(TIMEOUT_MILLIS + 1)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
+ fun displayChip_calledAgainBeforeTimeout_timeoutReset() {
+ // First, display the chip
+ controllerCommon.displayChip(getState())
+
+ // After some time, re-display the chip
+ val waitTime = 1000L
+ fakeClock.advanceTime(waitTime)
+ controllerCommon.displayChip(getState())
+
+ // Wait until the timeout for the first display would've happened
+ fakeClock.advanceTime(TIMEOUT_MILLIS - waitTime + 1)
+
+ // Verify we didn't hide the chip
+ verify(windowManager, never()).removeView(any())
+ }
+
+ @Test
+ fun displayChip_calledAgainBeforeTimeout_eventuallyTimesOut() {
+ // First, display the chip
+ controllerCommon.displayChip(getState())
+
+ // After some time, re-display the chip
+ fakeClock.advanceTime(1000L)
+ controllerCommon.displayChip(getState())
+
+ // Ensure we still hide the chip eventually
+ fakeClock.advanceTime(TIMEOUT_MILLIS + 1)
+
+ verify(windowManager).removeView(any())
+ }
+
+ @Test
fun removeChip_chipRemoved() {
// First, add the chip
controllerCommon.displayChip(getState())
@@ -93,10 +152,10 @@
controllerCommon.displayChip(getState())
val chipView = getChipView()
- val state = MediaTttChipState(PACKAGE_NAME)
+ val state = TestChipState(PACKAGE_NAME)
controllerCommon.setIcon(state, chipView)
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
assertThat(chipView.getAppIconView().contentDescription)
.isEqualTo(state.getAppName(context))
}
@@ -113,13 +172,18 @@
inner class TestControllerCommon(
context: Context,
- windowManager: WindowManager
- ) : MediaTttChipControllerCommon<MediaTttChipState>(
- context, windowManager, R.layout.media_ttt_chip
+ windowManager: WindowManager,
+ @Main mainExecutor: DelayableExecutor,
+ ) : MediaTttChipControllerCommon<MediaTttChipState>(
+ context, windowManager, mainExecutor, R.layout.media_ttt_chip
) {
override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) {
}
}
+
+ inner class TestChipState(appPackageName: String?) : MediaTttChipState(appPackageName) {
+ override fun getAppIcon(context: Context) = appIconDrawable
+ }
}
private const val PACKAGE_NAME = "com.android.systemui"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 44f691c..117a6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -17,7 +17,9 @@
package com.android.systemui.media.taptotransfer.receiver
import android.app.StatusBarManager
-import android.graphics.drawable.Icon
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
import android.media.MediaRoute2Info
import android.os.Handler
import android.view.View
@@ -28,33 +30,54 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
-@Ignore("b/216286227")
class MediaTttChipControllerReceiverTest : SysuiTestCase() {
private lateinit var controllerReceiver: MediaTttChipControllerReceiver
@Mock
+ private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var applicationInfo: ApplicationInfo
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
private lateinit var commandQueueCallback: CommandQueue.Callbacks
+ private lateinit var fakeAppIconDrawable: Drawable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
+ fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
+ whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+ whenever(packageManager.getApplicationInfo(
+ eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
+ )).thenReturn(applicationInfo)
+ context.setMockPackageManager(packageManager)
+
controllerReceiver = MediaTttChipControllerReceiver(
- commandQueue, context, windowManager, Handler.getMain())
+ commandQueue,
+ context,
+ windowManager,
+ FakeExecutor(FakeSystemClock()),
+ Handler.getMain()
+ )
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -113,13 +136,12 @@
controllerReceiver.displayChip(state)
- assertThat(getChipView().getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
-
+ assertThat(getChipView().getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
}
@Test
fun displayChip_hasAppIconDrawable_iconIsDrawable() {
- val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
+ val drawable = context.getDrawable(R.drawable.ic_alarm)!!
val state = ChipStateReceiver(PACKAGE_NAME, drawable, "appName")
controllerReceiver.displayChip(state)
@@ -133,13 +155,12 @@
controllerReceiver.displayChip(state)
- assertThat(getChipView().getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(APP_NAME)
}
@Test
fun displayChip_hasAppName_iconContentDescriptionIsAppNameOverride() {
- val appName = "FakeAppName"
+ val appName = "Override App Name"
val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName)
controllerReceiver.displayChip(state)
@@ -156,6 +177,7 @@
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
}
+private const val APP_NAME = "Fake app name"
private const val PACKAGE_NAME = "com.android.systemui"
private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index dc39893..b440064 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
import android.media.MediaRoute2Info
import android.view.View
import android.view.WindowManager
@@ -28,32 +31,50 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
-@Ignore("b/216286227")
class MediaTttChipControllerSenderTest : SysuiTestCase() {
private lateinit var controllerSender: MediaTttChipControllerSender
@Mock
+ private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var applicationInfo: ApplicationInfo
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var commandQueue: CommandQueue
private lateinit var commandQueueCallback: CommandQueue.Callbacks
+ private lateinit var fakeAppIconDrawable: Drawable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- controllerSender = MediaTttChipControllerSender(commandQueue, context, windowManager)
+
+ fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
+ whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
+ whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
+ whenever(packageManager.getApplicationInfo(
+ eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
+ )).thenReturn(applicationInfo)
+ context.setMockPackageManager(packageManager)
+
+ controllerSender = MediaTttChipControllerSender(
+ commandQueue, context, windowManager, FakeExecutor(FakeSystemClock())
+ )
val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
verify(commandQueue).addCallback(callbackCaptor.capture())
@@ -192,9 +213,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -207,9 +227,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -222,9 +241,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -237,9 +255,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -252,9 +269,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
@@ -314,9 +330,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
@@ -376,9 +391,8 @@
controllerSender.displayChip(state)
val chipView = getChipView()
- assertThat(chipView.getAppIconView().drawable).isEqualTo(state.getAppIcon(context))
- assertThat(chipView.getAppIconView().contentDescription)
- .isEqualTo(state.getAppName(context))
+ assertThat(chipView.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
+ assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_NAME)
assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
@@ -451,15 +465,15 @@
/** Helper method providing default parameters to not clutter up the tests. */
private fun almostCloseToStartCast() =
- AlmostCloseToStartCast(PACKAGE_NAME, DEVICE_NAME)
+ AlmostCloseToStartCast(PACKAGE_NAME, OTHER_DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun almostCloseToEndCast() =
- AlmostCloseToEndCast(PACKAGE_NAME, DEVICE_NAME)
+ AlmostCloseToEndCast(PACKAGE_NAME, OTHER_DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToReceiverTriggered() =
- TransferToReceiverTriggered(PACKAGE_NAME, DEVICE_NAME)
+ TransferToReceiverTriggered(PACKAGE_NAME, OTHER_DEVICE_NAME)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceTriggered() =
@@ -468,23 +482,24 @@
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToReceiverSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
TransferToReceiverSucceeded(
- PACKAGE_NAME, DEVICE_NAME, undoCallback
+ PACKAGE_NAME, OTHER_DEVICE_NAME, undoCallback
)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferToThisDeviceSucceeded(undoCallback: IUndoMediaTransferCallback? = null) =
TransferToThisDeviceSucceeded(
- PACKAGE_NAME, DEVICE_NAME, undoCallback
+ PACKAGE_NAME, OTHER_DEVICE_NAME, undoCallback
)
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferFailed() = TransferFailed(PACKAGE_NAME)
}
-private const val DEVICE_NAME = "My Tablet"
+private const val APP_NAME = "Fake app name"
+private const val OTHER_DEVICE_NAME = "My Tablet"
private const val PACKAGE_NAME = "com.android.systemui"
-private val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
+private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
.addFeature("feature")
.setPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 5a06048..6df56e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -19,6 +19,7 @@
import android.app.WallpaperColors;
import android.graphics.Color;
import android.testing.AndroidTestingRunner;
+import android.util.Log;
import androidx.test.filters.SmallTest;
@@ -29,7 +30,11 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -108,7 +113,7 @@
Style.SPRITZ /* style */);
int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2);
Cam cam = Cam.fromInt(primaryMid);
- Assert.assertEquals(cam.getChroma(), 4.0, 1.0);
+ Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
}
@Test
@@ -128,6 +133,41 @@
Style.EXPRESSIVE /* style */);
int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
Cam cam = Cam.fromInt(neutralMid);
- Assert.assertEquals(cam.getChroma(), 16.0, 1.0);
+ Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
+ }
+
+ /**
+ * Generate xml for SystemPaletteTest#testThemeStyles().
+ */
+ @Test
+ public void generateThemeStyles() {
+ StringBuilder xml = new StringBuilder();
+ for (int hue = 0; hue < 360; hue += 60) {
+ final int sourceColor = Cam.getInt(hue, 50f, 50f);
+ final String sourceColorHex = Integer.toHexString(sourceColor);
+
+ xml.append(" <theme color=\"").append(sourceColorHex).append("\">\n");
+
+ for (Style style : Style.values()) {
+ String styleName = style.name().toLowerCase();
+ ColorScheme colorScheme = new ColorScheme(sourceColor, false, style);
+ xml.append(" <").append(styleName).append(">");
+
+ List<String> colors = new ArrayList<>();
+ for (Stream<Integer> stream: Arrays.asList(colorScheme.getAccent1().stream(),
+ colorScheme.getAccent2().stream(),
+ colorScheme.getAccent3().stream(),
+ colorScheme.getNeutral1().stream(),
+ colorScheme.getNeutral2().stream())) {
+ colors.add("ffffff");
+ colors.addAll(stream.map(Integer::toHexString).map(s -> s.substring(2)).collect(
+ Collectors.toList()));
+ }
+ xml.append(String.join(",", colors));
+ xml.append("</").append(styleName).append(">\n");
+ }
+ xml.append(" </theme>\n");
+ }
+ Log.d("ColorSchemeXml", xml.toString());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 73d2b0b..bb42c12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -90,6 +91,7 @@
mock(NavBarHelper.class),
mock(TaskbarDelegate.class),
mNavigationBarFactory,
+ mock(StatusBarKeyguardViewManager.class),
mock(DumpManager.class),
mock(AutoHideController.class),
mock(LightBarController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 090ce43..48d3857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -28,6 +28,7 @@
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -228,7 +229,8 @@
@Test
public void testHomeLongPress() {
- mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+ mNavigationBar.onViewAttachedToWindow(mNavigationBar
+ .createView(null, /* initialVisibility= */ true));
mNavigationBar.onHomeLongClick(mNavigationBar.getView());
verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS);
@@ -241,7 +243,8 @@
.setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100)
.build());
when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true);
- mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+ mNavigationBar.onViewAttachedToWindow(mNavigationBar
+ .createView(null, /* initialVisibility= */ true));
mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
/*downTime=*/SystemClock.uptimeMillis(),
@@ -263,7 +266,8 @@
@Test
public void testRegisteredWithDispatcher() {
- mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
+ mNavigationBar.onViewAttachedToWindow(mNavigationBar
+ .createView(null, /* initialVisibility= */ true));
verify(mBroadcastDispatcher).registerReceiverWithHandler(
any(BroadcastReceiver.class),
any(IntentFilter.class),
@@ -283,8 +287,8 @@
doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
doNothing().when(defaultNavBar).checkNavBarModes();
doNothing().when(externalNavBar).checkNavBarModes();
- defaultNavBar.createView(null);
- externalNavBar.createView(null);
+ defaultNavBar.createView(null, /* initialVisibility= */ true);
+ externalNavBar.createView(null, /* initialVisibility= */ true);
defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
@@ -318,7 +322,7 @@
doReturn(mockShadeWindowView).when(mStatusBar).getNotificationShadeWindowView();
doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
doNothing().when(mNavigationBar).checkNavBarModes();
- mNavigationBar.createView(null);
+ mNavigationBar.createView(null, /* initialVisibility= */ true);
WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build();
doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
@@ -354,7 +358,7 @@
@Test
public void testA11yEventAfterDetach() {
- View v = mNavigationBar.createView(null);
+ View v = mNavigationBar.createView(null, /* initialVisibility= */ true);
mNavigationBar.onViewAttachedToWindow(v);
verify(mNavBarHelper).registerNavTaskStateUpdater(any(
NavBarHelper.NavbarTaskbarStateUpdater.class));
@@ -366,6 +370,20 @@
mNavigationBar.updateAccessibilityStateFlags();
}
+ @Test
+ public void testCreateView_initiallyVisible_viewIsVisible() {
+ mNavigationBar.createView(null, /* initialVisibility= */ true);
+
+ assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testCreateView_initiallyNotVisible_viewIsNotVisible() {
+ mNavigationBar.createView(null, /* initialVisibility= */ false);
+
+ assertThat(mNavigationBar.getView().getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
private NavigationBar createNavBar(Context context) {
DeviceProvisionedController deviceProvisionedController =
mock(DeviceProvisionedController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 511848d..3508226 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -24,7 +24,7 @@
import android.content.pm.UserInfo
import android.os.Process.SYSTEM_UID
import android.os.UserHandle
-import android.permission.PermGroupUsage
+import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -68,7 +68,8 @@
private const val ENT_USER_ID = 10
private const val TEST_PACKAGE_NAME = "test package name"
- private const val TEST_ATTRIBUTION = "test attribution"
+ private const val TEST_ATTRIBUTION_TAG = "test attribution tag"
+ private const val TEST_PROXY_LABEL = "test proxy label"
private const val PERM_CAMERA = android.Manifest.permission_group.CAMERA
private const val PERM_MICROPHONE = android.Manifest.permission_group.MICROPHONE
@@ -109,12 +110,12 @@
private val dialogProvider = object : PrivacyDialogController.DialogProvider {
var list: List<PrivacyDialog.PrivacyElement>? = null
- var starter: ((String, Int) -> Unit)? = null
+ var starter: ((String, Int, CharSequence?, Intent?) -> Unit)? = null
override fun makeDialog(
context: Context,
list: List<PrivacyDialog.PrivacyElement>,
- starter: (String, Int) -> Unit
+ starter: (String, Int, CharSequence?, Intent?) -> Unit
): PrivacyDialog {
this.list = list
this.starter = starter
@@ -125,13 +126,11 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
nextUid = 0
-
setUpDefaultMockResponses()
controller = PrivacyDialogController(
- permissionManager,
+ permissionManager,
packageManager,
privacyItemController,
userTracker,
@@ -248,40 +247,54 @@
val usage = createMockPermGroupUsage(
packageName = TEST_PACKAGE_NAME,
uid = generateUidForUser(USER_ID),
- permGroupName = PERM_CAMERA,
- lastAccess = 5L,
+ permissionGroupName = PERM_CAMERA,
+ lastAccessTimeMillis = 5L,
isActive = true,
isPhoneCall = false,
- attribution = TEST_ATTRIBUTION
+ attributionTag = null,
+ proxyLabel = TEST_PROXY_LABEL
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
controller.showDialog(context)
exhaustExecutors()
- val expected = PrivacyDialog.PrivacyElement(
- type = PrivacyType.TYPE_CAMERA,
- packageName = TEST_PACKAGE_NAME,
- userId = USER_ID,
- applicationName = TEST_PACKAGE_NAME,
- attribution = TEST_ATTRIBUTION,
- lastActiveTimestamp = 5L,
- active = true,
- phoneCall = false,
- enterprise = false
- )
- assertThat(dialogProvider.list).containsExactly(expected)
+ dialogProvider.list?.let { list ->
+ assertThat(list.get(0).type).isEqualTo(PrivacyType.TYPE_CAMERA)
+ assertThat(list.get(0).packageName).isEqualTo(TEST_PACKAGE_NAME)
+ assertThat(list.get(0).userId).isEqualTo(USER_ID)
+ assertThat(list.get(0).applicationName).isEqualTo(TEST_PACKAGE_NAME)
+ assertThat(list.get(0).attributionTag).isNull()
+ assertThat(list.get(0).attributionLabel).isNull()
+ assertThat(list.get(0).proxyLabel).isEqualTo(TEST_PROXY_LABEL)
+ assertThat(list.get(0).lastActiveTimestamp).isEqualTo(5L)
+ assertThat(list.get(0).active).isTrue()
+ assertThat(list.get(0).phoneCall).isFalse()
+ assertThat(list.get(0).enterprise).isFalse()
+ assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA)
+ assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+ controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+ .isTrue()
+ }
+ }
+
+ private fun isIntentEqual(actual: Intent, expected: Intent): Boolean {
+ return actual.action == expected.action &&
+ actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
+ expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) &&
+ actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
+ expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle
}
@Test
fun testTwoElementsDifferentType_sorted() {
val usage_camera = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_camera",
- permGroupName = PERM_CAMERA
+ permissionGroupName = PERM_CAMERA
)
val usage_microphone = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_microphone",
- permGroupName = PERM_MICROPHONE
+ permissionGroupName = PERM_MICROPHONE
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_microphone, usage_camera)
@@ -322,12 +335,12 @@
val usage_active = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_active",
isActive = true,
- lastAccess = 0L
+ lastAccessTimeMillis = 0L
)
val usage_active_moreRecent = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_active_recent",
isActive = true,
- lastAccess = 1L
+ lastAccessTimeMillis = 1L
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_active, usage_active_moreRecent)
@@ -344,17 +357,17 @@
val usage_recent = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_recent",
isActive = false,
- lastAccess = 0L
+ lastAccessTimeMillis = 0L
)
val usage_moreRecent = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_moreRecent",
isActive = false,
- lastAccess = 1L
+ lastAccessTimeMillis = 1L
)
val usage_mostRecent = createMockPermGroupUsage(
packageName = "${TEST_PACKAGE_NAME}_mostRecent",
isActive = false,
- lastAccess = 2L
+ lastAccessTimeMillis = 2L
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_recent, usage_mostRecent, usage_moreRecent)
@@ -370,13 +383,13 @@
@Test
fun testMicAndCameraDisabled() {
val usage_camera = createMockPermGroupUsage(
- permGroupName = PERM_CAMERA
+ permissionGroupName = PERM_CAMERA
)
val usage_microphone = createMockPermGroupUsage(
- permGroupName = PERM_MICROPHONE
+ permissionGroupName = PERM_MICROPHONE
)
val usage_location = createMockPermGroupUsage(
- permGroupName = PERM_LOCATION
+ permissionGroupName = PERM_LOCATION
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
@@ -394,13 +407,13 @@
@Test
fun testLocationDisabled() {
val usage_camera = createMockPermGroupUsage(
- permGroupName = PERM_CAMERA
+ permissionGroupName = PERM_CAMERA
)
val usage_microphone = createMockPermGroupUsage(
- permGroupName = PERM_MICROPHONE
+ permissionGroupName = PERM_MICROPHONE
)
val usage_location = createMockPermGroupUsage(
- permGroupName = PERM_LOCATION
+ permissionGroupName = PERM_LOCATION
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
@@ -420,13 +433,13 @@
@Test
fun testAllIndicatorsAvailable() {
val usage_camera = createMockPermGroupUsage(
- permGroupName = PERM_CAMERA
+ permissionGroupName = PERM_CAMERA
)
val usage_microphone = createMockPermGroupUsage(
- permGroupName = PERM_MICROPHONE
+ permissionGroupName = PERM_MICROPHONE
)
val usage_location = createMockPermGroupUsage(
- permGroupName = PERM_LOCATION
+ permissionGroupName = PERM_LOCATION
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
@@ -444,13 +457,13 @@
@Test
fun testNoIndicatorsAvailable() {
val usage_camera = createMockPermGroupUsage(
- permGroupName = PERM_CAMERA
+ permissionGroupName = PERM_CAMERA
)
val usage_microphone = createMockPermGroupUsage(
- permGroupName = PERM_MICROPHONE
+ permissionGroupName = PERM_MICROPHONE
)
val usage_location = createMockPermGroupUsage(
- permGroupName = PERM_LOCATION
+ permissionGroupName = PERM_LOCATION
)
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
@@ -500,7 +513,7 @@
controller.showDialog(context)
exhaustExecutors()
- dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID, null, null)
verify(activityStarter)
.startActivity(capture(intentCaptor), eq(true), any<ActivityStarter.Callback>())
@@ -518,7 +531,7 @@
controller.showDialog(context)
exhaustExecutors()
- dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, ENT_USER_ID)
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, ENT_USER_ID, null, null)
verify(activityStarter)
.startActivity(capture(intentCaptor), eq(true), any<ActivityStarter.Callback>())
@@ -533,7 +546,7 @@
controller.showDialog(context)
exhaustExecutors()
- dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID, null, null)
verify(activityStarter).startActivity(any(), eq(true), capture(activityStartedCaptor))
activityStartedCaptor.value.onActivityStarted(ActivityManager.START_DELIVERED_TO_TOP)
@@ -548,7 +561,7 @@
controller.showDialog(context)
exhaustExecutors()
- dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID, null, null)
verify(activityStarter).startActivity(any(), eq(true), capture(activityStartedCaptor))
activityStartedCaptor.value.onActivityStarted(ActivityManager.START_ABORTED)
@@ -578,7 +591,7 @@
controller.showDialog(context)
exhaustExecutors()
- dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID, null, null)
verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
USER_ID, TEST_PACKAGE_NAME)
}
@@ -599,6 +612,42 @@
verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
}
+ @Test
+ fun testInvalidAttributionTag() {
+ val usage = createMockPermGroupUsage(
+ packageName = TEST_PACKAGE_NAME,
+ uid = generateUidForUser(USER_ID),
+ permissionGroupName = PERM_CAMERA,
+ lastAccessTimeMillis = 5L,
+ isActive = true,
+ isPhoneCall = false,
+ attributionTag = "INVALID_ATTRIBUTION_TAG",
+ proxyLabel = TEST_PROXY_LABEL
+ )
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ dialogProvider.list?.let { list ->
+ assertThat(list.get(0).type).isEqualTo(PrivacyType.TYPE_CAMERA)
+ assertThat(list.get(0).packageName).isEqualTo(TEST_PACKAGE_NAME)
+ assertThat(list.get(0).userId).isEqualTo(USER_ID)
+ assertThat(list.get(0).applicationName).isEqualTo(TEST_PACKAGE_NAME)
+ assertThat(list.get(0).attributionTag).isEqualTo("INVALID_ATTRIBUTION_TAG")
+ assertThat(list.get(0).attributionLabel).isNull()
+ assertThat(list.get(0).proxyLabel).isEqualTo(TEST_PROXY_LABEL)
+ assertThat(list.get(0).lastActiveTimestamp).isEqualTo(5L)
+ assertThat(list.get(0).active).isTrue()
+ assertThat(list.get(0).phoneCall).isFalse()
+ assertThat(list.get(0).enterprise).isFalse()
+ assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA)
+ assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+ controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+ .isTrue()
+ }
+ }
+
private fun exhaustExecutors() {
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
}
@@ -608,9 +657,7 @@
`when`(appOpsController.isMicMuted).thenReturn(false)
`when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenAnswer {
- FakeApplicationInfo(it.getArgument(0))
- }
+ .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
`when`(privacyItemController.locationAvailable).thenReturn(true)
`when`(privacyItemController.micCameraAvailable).thenReturn(true)
@@ -636,21 +683,24 @@
private fun createMockPermGroupUsage(
packageName: String = TEST_PACKAGE_NAME,
uid: Int = generateUidForUser(USER_ID),
- permGroupName: String = PERM_CAMERA,
- lastAccess: Long = 0L,
+ permissionGroupName: String = PERM_CAMERA,
+ lastAccessTimeMillis: Long = 0L,
isActive: Boolean = false,
isPhoneCall: Boolean = false,
- attribution: CharSequence? = null
- ): PermGroupUsage {
- val usage = mock(PermGroupUsage::class.java)
+ attributionTag: CharSequence? = null,
+ attributionLabel: CharSequence? = null,
+ proxyLabel: CharSequence? = null
+ ): PermissionGroupUsage {
+ val usage = mock(PermissionGroupUsage::class.java)
`when`(usage.packageName).thenReturn(packageName)
`when`(usage.uid).thenReturn(uid)
- `when`(usage.permGroupName).thenReturn(permGroupName)
- `when`(usage.lastAccess).thenReturn(lastAccess)
+ `when`(usage.permissionGroupName).thenReturn(permissionGroupName)
+ `when`(usage.lastAccessTimeMillis).thenReturn(lastAccessTimeMillis)
`when`(usage.isActive).thenReturn(isActive)
`when`(usage.isPhoneCall).thenReturn(isPhoneCall)
- `when`(usage.attribution).thenReturn(attribution)
-
+ `when`(usage.attributionTag).thenReturn(attributionTag)
+ `when`(usage.attributionLabel).thenReturn(attributionLabel)
+ `when`(usage.proxyLabel).thenReturn(proxyLabel)
return usage
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
index 1baaf6b..c714fa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
@@ -34,6 +34,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import android.content.Intent
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -43,11 +44,11 @@
companion object {
private const val TEST_PACKAGE_NAME = "test_pkg"
private const val TEST_USER_ID = 0
+ private const val TEST_PERM_GROUP = "test_perm_group"
}
@Mock
- private lateinit var starter: (String, Int) -> Unit
-
+ private lateinit var starter: (String, Int, CharSequence?, Intent?) -> Unit
private lateinit var dialog: PrivacyDialog
@Before
@@ -71,16 +72,20 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
false,
false,
- false
+ false,
+ TEST_PERM_GROUP,
+ null
)
)
dialog = PrivacyDialog(context, list, starter)
dialog.show()
dialog.requireViewById<View>(R.id.privacy_item).callOnClick()
- verify(starter).invoke(TEST_PACKAGE_NAME, TEST_USER_ID)
+ verify(starter).invoke(TEST_PACKAGE_NAME, TEST_USER_ID, null, null)
}
@Test
@@ -115,10 +120,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
true,
false,
- false
+ false,
+ TEST_PERM_GROUP,
+ null
),
PrivacyDialog.PrivacyElement(
PrivacyType.TYPE_MICROPHONE,
@@ -126,10 +135,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
false,
false,
- false
+ false,
+ TEST_PERM_GROUP,
+ null
)
)
dialog = PrivacyDialog(context, list, starter)
@@ -145,10 +158,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
true,
false,
- false
+ false,
+ TEST_PERM_GROUP,
+ null
)
val list = listOf(element)
@@ -171,10 +188,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
false,
false,
- false
+ false,
+ TEST_PERM_GROUP,
+ null
)
val list = listOf(element)
@@ -197,10 +218,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
false,
true,
- false
+ false,
+ TEST_PERM_GROUP,
+ null
)
val list = listOf(element)
@@ -219,10 +244,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
false,
false,
- true
+ true,
+ TEST_PERM_GROUP,
+ null
)
val list = listOf(element)
@@ -241,10 +270,14 @@
TEST_USER_ID,
"App",
null,
+ null,
+ null,
0L,
false,
false,
- true
+ true,
+ TEST_PERM_GROUP,
+ null
)
val list = listOf(element)
@@ -255,17 +288,21 @@
}
@Test
- fun testAttribution() {
+ fun testProxyLabel() {
val element = PrivacyDialog.PrivacyElement(
PrivacyType.TYPE_MICROPHONE,
TEST_PACKAGE_NAME,
TEST_USER_ID,
"App",
- "attribution",
+ null,
+ null,
+ "proxyLabel",
0L,
false,
false,
- true
+ true,
+ TEST_PERM_GROUP,
+ null
)
val list = listOf(element)
@@ -274,7 +311,65 @@
assertThat(dialog.requireViewById<TextView>(R.id.text).text.toString()).contains(
context.getString(
R.string.ongoing_privacy_dialog_attribution_text,
- element.attribution
+ element.proxyLabel
+ )
+ )
+ }
+
+ @Test
+ fun testSubattribution() {
+ val element = PrivacyDialog.PrivacyElement(
+ PrivacyType.TYPE_MICROPHONE,
+ TEST_PACKAGE_NAME,
+ TEST_USER_ID,
+ "App",
+ null,
+ "For subattribution",
+ null,
+ 0L,
+ true,
+ false,
+ false,
+ TEST_PERM_GROUP,
+ null
+ )
+
+ val list = listOf(element)
+ dialog = PrivacyDialog(context, list, starter)
+ dialog.show()
+ assertThat(dialog.requireViewById<TextView>(R.id.text).text.toString()).contains(
+ context.getString(
+ R.string.ongoing_privacy_dialog_attribution_label,
+ element.attributionLabel
+ )
+ )
+ }
+
+ @Test
+ fun testSubattributionAndProxyLabel() {
+ val element = PrivacyDialog.PrivacyElement(
+ PrivacyType.TYPE_MICROPHONE,
+ TEST_PACKAGE_NAME,
+ TEST_USER_ID,
+ "App",
+ null,
+ "For subattribution",
+ "proxy label",
+ 0L,
+ true,
+ false,
+ false,
+ TEST_PERM_GROUP,
+ null
+ )
+
+ val list = listOf(element)
+ dialog = PrivacyDialog(context, list, starter)
+ dialog.show()
+ assertThat(dialog.requireViewById<TextView>(R.id.text).text.toString()).contains(
+ context.getString(
+ R.string.ongoing_privacy_dialog_attribution_proxy_label,
+ element.attributionLabel, element.proxyLabel
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index 92743ae5..30a5308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -1,31 +1,45 @@
package com.android.systemui.qs
import android.content.Context
+import android.permission.PermissionManager
+import android.provider.DeviceConfig
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyDialogController
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
+ companion object {
+ const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+ }
+
@Mock
private lateinit var privacyItemController: PrivacyItemController
@Mock
@@ -38,11 +52,20 @@
private lateinit var privacyLogger: PrivacyLogger
@Mock
private lateinit var iconContainer: StatusIconContainer
+ @Mock
+ private lateinit var permissionManager: PermissionManager
+ @Mock
+ private lateinit var backgroundExecutor: Executor
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var appOpsController: AppOpsController
+ private val uiExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var deviceConfigProxy: DeviceConfigProxy
private lateinit var cameraSlotName: String
private lateinit var microphoneSlotName: String
private lateinit var locationSlotName: String
-
private lateinit var controller: HeaderPrivacyIconsController
@Before
@@ -54,6 +77,7 @@
cameraSlotName = context.getString(com.android.internal.R.string.status_bar_camera)
microphoneSlotName = context.getString(com.android.internal.R.string.status_bar_microphone)
locationSlotName = context.getString(com.android.internal.R.string.status_bar_location)
+ deviceConfigProxy = DeviceConfigProxyFake()
controller = HeaderPrivacyIconsController(
privacyItemController,
@@ -61,7 +85,13 @@
privacyChip,
privacyDialogController,
privacyLogger,
- iconContainer
+ iconContainer,
+ permissionManager,
+ backgroundExecutor,
+ uiExecutor,
+ activityStarter,
+ appOpsController,
+ deviceConfigProxy
)
}
@@ -111,18 +141,38 @@
@Test
fun testPrivacyChipClicked() {
+ changeProperty(SAFETY_CENTER_ENABLED, false)
controller.onParentVisible()
val captor = argumentCaptor<View.OnClickListener>()
verify(privacyChip).setOnClickListener(capture(captor))
-
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
}
+ @Test
+ fun testSafetyCenterFlag() {
+ changeProperty(SAFETY_CENTER_ENABLED, true)
+ controller.onParentVisible()
+ val captor = argumentCaptor<View.OnClickListener>()
+ verify(privacyChip).setOnClickListener(capture(captor))
+ captor.value.onClick(privacyChip)
+ verify(privacyDialogController, never()).showDialog(any(Context::class.java))
+ }
+
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera)
whenever(privacyItemController.locationAvailable).thenReturn(location)
}
+
+ private fun changeProperty(name: String, value: Boolean?) {
+ deviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ name,
+ value?.toString(),
+ false
+ )
+ uiExecutor.runAllReady()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 0d65541..a2959e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -136,6 +136,8 @@
private LocationController mLocationController;
@Mock
private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock
+ private View mDialogLaunchView;
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -384,7 +386,8 @@
@Test
public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() {
- mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */);
+ mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */,
+ mDialogLaunchView);
verify(mActivityStarter, never())
.postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
@@ -392,9 +395,11 @@
@Test
public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() {
- mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key");
+ mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key",
+ mDialogLaunchView);
- verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt(),
+ any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index ed35dcb..cf97bda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -458,7 +458,8 @@
public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
mSeeAll.performClick();
- verify(mInternetDialogController).launchNetworkSetting();
+ verify(mInternetDialogController).launchNetworkSetting(
+ mDialogView.requireViewById(R.id.see_all_layout));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 8695b29..030c65a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -21,6 +21,7 @@
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.view.View
+import android.widget.Button
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
@@ -63,6 +64,8 @@
@Mock
private lateinit var launchView: View
@Mock
+ private lateinit var neutralButton: Button
+ @Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Mock
private lateinit var uiEventLogger: UiEventLogger
@@ -130,14 +133,17 @@
controller.showDialog(launchView)
- verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor))
+ verify(dialog)
+ .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
+ `when`(dialog.getButton(DialogInterface.BUTTON_NEUTRAL)).thenReturn(neutralButton)
clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
verify(activityStarter)
.postStartActivityDismissingKeyguard(
argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
- eq(0)
+ eq(0),
+ eq(null)
)
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
}
@@ -148,7 +154,8 @@
controller.showDialog(launchView)
- verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor))
+ verify(dialog)
+ .setNeutralButton(anyInt(), capture(clickCaptor), eq(false) /* dismissOnClick */)
clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index ecc2a1b..b4cae38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -63,6 +63,7 @@
commandRegistry, batteryController, configurationController,
featureFlags, context, windowManager, systemClock, uiEventLogger)
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
+ controller.registerCallbacks()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 9f152e1..b7b3088 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -56,6 +56,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
+ allowTestableLooperAsMainThread();
when(mWifiInfo.makeCopy(anyLong())).thenReturn(mWifiInfo);
when(mWifiInfo.isPrimary()).thenReturn(true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
index 3820b98..3b908b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt
@@ -20,10 +20,14 @@
import android.view.Choreographer
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
+import dagger.BindsInstance
+import dagger.Component
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,8 +45,10 @@
whenever(it.executeDelayed(any(), anyLong())).thenReturn(timeoueSubscription)
}
- val pipelineChoreographer: NotifPipelineChoreographer = NotifPipelineChoreographerModule
- .provideChoreographer(viewChoreographer, executor)
+ val pipelineChoreographer: NotifPipelineChoreographer =
+ DaggerNotifPipelineChoreographerTestComponent.factory()
+ .create(viewChoreographer, executor)
+ .choreographer
@Test
fun scheduleThenEvalFrameCallback() {
@@ -97,4 +103,19 @@
verify(viewChoreographer).removeFrameCallback(frameCallback)
verify(timeoueSubscription).run()
}
+}
+
+@SysUISingleton
+@Component(modules = [NotifPipelineChoreographerModule::class])
+interface NotifPipelineChoreographerTestComponent {
+
+ val choreographer: NotifPipelineChoreographer
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance viewChoreographer: Choreographer,
+ @BindsInstance @Main executor: DelayableExecutor
+ ): NotifPipelineChoreographerTestComponent
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 3f84c16..a2d8e3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -28,13 +28,16 @@
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import dagger.BindsInstance
+import dagger.Component
import org.junit.Test
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -50,9 +53,16 @@
val statusBarStateController: StatusBarStateController = mock()
val keyguardStateController: KeyguardStateController = mock()
- val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule
- .provideCoordinator(dynamicPrivacyController, lockscreenUserManager,
- keyguardUpdateMonitor, statusBarStateController, keyguardStateController)
+ val coordinator: SensitiveContentCoordinator =
+ DaggerTestSensitiveContentCoordinatorComponent
+ .factory()
+ .create(
+ dynamicPrivacyController,
+ lockscreenUserManager,
+ keyguardUpdateMonitor,
+ statusBarStateController,
+ keyguardStateController)
+ .coordinator
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -238,4 +248,21 @@
override fun getRepresentativeEntry(): NotificationEntry = mockEntry
}
}
+}
+
+@CoordinatorScope
+@Component(modules = [SensitiveContentCoordinatorModule::class])
+interface TestSensitiveContentCoordinatorComponent {
+ val coordinator: SensitiveContentCoordinator
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance dynamicPrivacyController: DynamicPrivacyController,
+ @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
+ @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ @BindsInstance statusBarStateController: StatusBarStateController,
+ @BindsInstance keyguardStateController: KeyguardStateController
+ ): TestSensitiveContentCoordinatorComponent
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index f6eff82..479c271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -100,8 +100,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.idle.IdleHostViewController;
-import com.android.systemui.idle.dagger.IdleViewComponent;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
@@ -266,12 +264,6 @@
@Mock
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
@Mock
- private IdleViewComponent.Factory mIdleViewComponentFactory;
- @Mock
- private IdleViewComponent mIdleViewComponent;
- @Mock
- private IdleHostViewController mIdleHostViewController;
- @Mock
private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
@Mock
private KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
@@ -475,10 +467,6 @@
.thenReturn(mCommunalViewComponent);
when(mCommunalViewComponent.getCommunalHostViewController())
.thenReturn(mCommunalHostViewController);
- when(mIdleViewComponentFactory.build(any()))
- .thenReturn(mIdleViewComponent);
- when(mIdleViewComponent.getIdleHostViewController())
- .thenReturn(mIdleHostViewController);
when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean()))
.thenReturn(mKeyguardStatusView);
when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean()))
@@ -523,7 +511,6 @@
mKeyguardUserSwitcherComponentFactory,
mKeyguardStatusBarViewComponentFactory,
mCommunalViewComponentFactory,
- mIdleViewComponentFactory,
mLockscreenShadeTransitionController,
mGroupManager,
mNotificationAreaController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 107ba81..8b93de5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
import org.junit.Before;
import org.junit.Test;
@@ -60,6 +61,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -100,6 +103,8 @@
@Mock
private ShadeController mShadeController;
@Mock
+ private SysUIUnfoldComponent mSysUiUnfoldComponent;
+ @Mock
private DreamOverlayStateController mDreamOverlayStateController;
@Mock
private LatencyTracker mLatencyTracker;
@@ -130,6 +135,7 @@
mock(NotificationMediaManager.class),
mKeyguardBouncerFactory,
mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker);
mStatusBarKeyguardViewManager.registerStatusBar(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index a7809c2..c7db9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -116,6 +116,7 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.charging.WiredChargingRippleController;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -288,6 +289,7 @@
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
@Mock private DreamOverlayStateController mDreamOverlayStateController;
+ @Mock private WiredChargingRippleController mWiredChargingRippleController;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -479,7 +481,8 @@
mNotifPipelineFlags,
mJankMonitor,
mDeviceStateManager,
- mDreamOverlayStateController);
+ mDreamOverlayStateController,
+ mWiredChargingRippleController);
when(mKeyguardViewMediator.registerStatusBar(
any(StatusBar.class),
any(NotificationPanelViewController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index f5554c5..9f9cb40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -205,6 +205,12 @@
mTestableLooper.processAllMessages();
verify(callback, times(1)).onLocationActiveChanged(false);
+
+ when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
+ mLocationController.onActiveStateChanged(AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, 0,
+ "com.google.android.googlequicksearchbox", true);
+ mTestableLooper.processAllMessages();
+ verify(callback, times(1)).onLocationActiveChanged(true);
}
@Test
diff --git a/services/Android.bp b/services/Android.bp
index e0ca8a6..2e4405f 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -54,7 +54,9 @@
SYSTEM_OPTIMIZE_JAVA: {
optimize: {
enabled: true,
- optimize: true,
+ // TODO(b/210510433): Enable optimizations after improving
+ // retracing infra.
+ optimize: false,
shrink: true,
proguard_flags_files: ["proguard.flags"],
},
diff --git a/services/autofill/OWNERS b/services/autofill/OWNERS
index c52751d..edfb211 100644
--- a/services/autofill/OWNERS
+++ b/services/autofill/OWNERS
@@ -1 +1 @@
-include /core/java/android/service/autofill/OWNERS
+include /core/java/android/view/autofill/OWNERS
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 76ee728..e0fa67f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -103,7 +103,6 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.view.KeyEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -367,8 +366,6 @@
@Nullable
private ClientSuggestionsSession mClientSuggestionsSession;
- private final AccessibilityManager mAccessibilityManager;
-
// TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
// new one per Session.
private final BroadcastReceiver mDelayedFillBroadcastReceiver =
@@ -518,10 +515,7 @@
return;
}
- // If a11y touch exploration is enabled, then we do not send an inline fill request
- // to the regular af service, because dropdown UI is easier to use.
- if (mPendingInlineSuggestionsRequest.isServiceSupported()
- && !mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
mPendingFillRequest.getFillContexts(),
mPendingFillRequest.getClientState(),
@@ -1064,7 +1058,6 @@
mRemoteFillService = serviceComponentName == null ? null
: new RemoteFillService(context, serviceComponentName, userId, this,
bindInstantServiceAllowed);
- mAccessibilityManager = AccessibilityManager.getInstance(context);
mActivityToken = activityToken;
mHasCallback = hasCallback;
mUiLatencyHistory = uiLatencyHistory;
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index e122993..75d9b7e 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -275,12 +275,10 @@
if (index >= 0) {
RemoteViews presentation = dataset.getFieldDialogPresentation(index);
if (presentation == null) {
- Slog.w(TAG, "fallback to presentation");
- presentation = dataset.getFieldPresentation(index);
- }
- if (presentation == null) {
- Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
- + "service didn't provide a presentation for it on " + dataset);
+ if (sDebug) {
+ Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+ + "service didn't provide a presentation for it on " + dataset);
+ }
continue;
}
final View view;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index efa026b..e10151d 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -137,6 +137,8 @@
import com.android.server.backup.utils.BackupObserverUtils;
import com.android.server.backup.utils.SparseArrayUtils;
+import dalvik.annotation.optimization.NeverCompile;
+
import com.google.android.collect.Sets;
import java.io.BufferedInputStream;
@@ -4072,6 +4074,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
private void dumpInternal(PrintWriter pw) {
// Add prefix for only non-system users so that system user dumpsys is the same as before
String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index be1bc79..c39b59a 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.os.Handler;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -120,6 +121,12 @@
mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
}
+ if (serviceConnectors.isEmpty()) {
+ Slog.e(TAG, "Can't find CompanionDeviceService implementer in package: "
+ + packageName + ". Please check if they are correctly declared.");
+ return;
+ }
+
// The first connector in the list is always the primary connector: set a listener to it.
serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied);
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index a771e7b..33301b1 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -228,11 +228,23 @@
if (DEBUG) Log.i(TAG, "stopScan()");
if (!mScanning) {
- Log.d(TAG, " > not scanning.");
+ if (DEBUG) Log.d(TAG, " > not scanning.");
return;
}
+ // mScanCallback is non-null here - it cannot be null when mScanning is true.
- mBleScanner.stopScan(mScanCallback);
+ // BluetoothLeScanner will throw an IllegalStateException if stopScan() is called while LE
+ // is not enabled.
+ if (mBtAdapter.isLeEnabled()) {
+ try {
+ mBleScanner.stopScan(mScanCallback);
+ } catch (RuntimeException e) {
+ // Just to be sure not to crash system server here if BluetoothLeScanner throws
+ // another RuntimeException.
+ Slog.w(TAG, "Exception while stopping BLE scanning", e);
+ }
+ }
+
mScanning = false;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 4afa96c..bc1f28d 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -37,6 +37,7 @@
import android.view.Display;
import android.window.DisplayWindowPolicyController;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.BlockedAppStreamingActivity;
import java.util.List;
@@ -75,9 +76,11 @@
private final ArraySet<ComponentName> mAllowedActivities;
@Nullable
private final ArraySet<ComponentName> mBlockedActivities;
+ private final Object mGenericWindowPolicyControllerLock = new Object();
private Consumer<ActivityInfo> mActivityBlockedCallback;
@NonNull
+ @GuardedBy("mGenericWindowPolicyControllerLock")
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -149,11 +152,13 @@
@Override
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
- mRunningUids.clear();
- mRunningUids.addAll(runningUids);
- if (mActivityListener != null && mRunningUids.isEmpty()) {
- // Post callback on the main thread so it doesn't block activity launching
- mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY));
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mRunningUids.clear();
+ mRunningUids.addAll(runningUids);
+ if (mActivityListener != null && mRunningUids.isEmpty()) {
+ // Post callback on the main thread so it doesn't block activity launching
+ mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY));
+ }
}
if (mRunningAppsChangedListener != null) {
mRunningAppsChangedListener.onRunningAppsChanged(runningUids);
@@ -165,7 +170,9 @@
* this controller.
*/
boolean containsUid(int uid) {
- return mRunningUids.contains(uid);
+ synchronized (mGenericWindowPolicyControllerLock) {
+ return mRunningUids.contains(uid);
+ }
}
private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 387d911..c0a904f 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -235,6 +235,10 @@
});
mPerDisplayWakelocks.clear();
}
+ if (mVirtualAudioController != null) {
+ mVirtualAudioController.stopListening();
+ mVirtualAudioController = null;
+ }
}
mListener.onClose(mAssociationInfo.getId());
mAppToken.unlinkToDeath(this, 0);
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index e335a16..09ef03c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -52,6 +52,7 @@
import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ParsedMainComponent;
import com.android.server.pm.pkg.mutate.PackageStateMutator;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -718,7 +719,8 @@
/**
* Returns a package object for the disabled system package name.
*/
- public abstract @Nullable PackageSetting getDisabledSystemPackage(@NonNull String packageName);
+ public abstract @Nullable PackageStateInternal getDisabledSystemPackage(
+ @NonNull String packageName);
/**
* Returns the package name for the disabled system package.
@@ -1334,4 +1336,12 @@
public abstract PackageStateMutator.Result commitPackageStateMutation(
@Nullable PackageStateMutator.InitialState state,
@NonNull Consumer<PackageStateMutator> consumer);
+
+ /**
+ * @return package data snapshot for use with other PackageManager infrastructure. This should
+ * only be used as a parameter passed to another PM related class. Do not call methods on this
+ * directly.
+ */
+ @NonNull
+ public abstract PackageDataSnapshot snapshot();
}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index d218af3..6986d3b 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -36,11 +36,8 @@
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -434,7 +431,7 @@
entry.setValue(packageInfo.lastUpdateTime);
// compute the digest for the updated package
- String sha256digest = computeSha256DigestOfFile(
+ String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
packageInfo.applicationInfo.sourceDir);
if (sha256digest == null) {
Slog.e(TAG, "Failed to compute SHA256sum for file at "
@@ -471,7 +468,7 @@
ApplicationInfo appInfo = packageInfo.applicationInfo;
// compute SHA256 for these APEXs
- String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir);
+ String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir);
if (sha256digest == null) {
Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
packageInfo.packageName));
@@ -506,7 +503,8 @@
ApplicationInfo appInfo = packageInfo.applicationInfo;
// compute SHA256 digest for these modules
- String sha256digest = computeSha256DigestOfFile(appInfo.sourceDir);
+ String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
+ appInfo.sourceDir);
if (sha256digest == null) {
Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
packageName));
@@ -525,16 +523,4 @@
}
}
- @Nullable
- private String computeSha256DigestOfFile(@NonNull String pathToFile) {
- File apexFile = new File(pathToFile);
-
- try {
- byte[] apexFileBytes = Files.readAllBytes(apexFile.toPath());
- return PackageUtils.computeSha256Digest(apexFileBytes);
- } catch (IOException e) {
- Slog.e(TAG, String.format("I/O error occurs when reading from %s", pathToFile));
- return null;
- }
- }
}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index aae1cc0..c375c73 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -17,6 +17,8 @@
package com.android.server;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
@@ -32,6 +34,9 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FastPrintWriter;
+import com.android.server.pm.Computer;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -51,7 +56,7 @@
final private static boolean localLOGV = DEBUG || false;
final private static boolean localVerificationLOGV = DEBUG || false;
- public void addFilter(F f) {
+ public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) {
IntentFilter intentFilter = getIntentFilter(f);
if (localLOGV) {
Slog.v(TAG, "Adding filter: " + f);
@@ -420,8 +425,9 @@
return Collections.unmodifiableSet(mFilters);
}
- public List<R> queryIntentFromList(Intent intent, String resolvedType, boolean defaultOnly,
- ArrayList<F[]> listCut, int userId) {
+ public List<R> queryIntentFromList(@NonNull Computer computer, Intent intent,
+ String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, int userId,
+ long customFlags) {
ArrayList<R> resultList = new ArrayList<R>();
final boolean debug = localLOGV ||
@@ -431,16 +437,21 @@
final String scheme = intent.getScheme();
int N = listCut.size();
for (int i = 0; i < N; ++i) {
- buildResolveList(intent, categories, debug, defaultOnly, resolvedType, scheme,
- listCut.get(i), resultList, userId);
+ buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType, scheme,
+ listCut.get(i), resultList, userId, customFlags);
}
filterResults(resultList);
sortResults(resultList);
return resultList;
}
- public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
- int userId) {
+ public List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ return queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, 0);
+ }
+
+ protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) {
String scheme = intent.getScheme();
ArrayList<R> finalList = new ArrayList<R>();
@@ -512,21 +523,22 @@
}
FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
+ Computer computer = (Computer) snapshot;
if (firstTypeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
- scheme, firstTypeCut, finalList, userId);
+ buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
+ scheme, firstTypeCut, finalList, userId, customFlags);
}
if (secondTypeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
- scheme, secondTypeCut, finalList, userId);
+ buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
+ scheme, secondTypeCut, finalList, userId, customFlags);
}
if (thirdTypeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
- scheme, thirdTypeCut, finalList, userId);
+ buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
+ scheme, thirdTypeCut, finalList, userId, customFlags);
}
if (schemeCut != null) {
- buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
- scheme, schemeCut, finalList, userId);
+ buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
+ scheme, schemeCut, finalList, userId, customFlags);
}
filterResults(finalList);
sortResults(finalList);
@@ -554,7 +566,7 @@
* "stopped", that is whether it should not be included in the result
* if the intent requests to excluded stopped objects.
*/
- protected boolean isFilterStopped(F filter, int userId) {
+ protected boolean isFilterStopped(PackageStateInternal packageState, @UserIdInt int userId) {
return false;
}
@@ -584,7 +596,8 @@
protected abstract F[] newArray(int size);
@SuppressWarnings("unchecked")
- protected R newResult(F filter, int match, int userId) {
+ protected R newResult(@NonNull Computer computer, F filter, int match, int userId,
+ long customFlags) {
return (R)filter;
}
@@ -764,9 +777,10 @@
return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()]));
}
- private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
- boolean debug, boolean defaultOnly, String resolvedType, String scheme,
- F[] src, List<R> dest, int userId) {
+ private void buildResolveList(@NonNull Computer computer, Intent intent,
+ FastImmutableArraySet<String> categories, boolean debug, boolean defaultOnly,
+ String resolvedType, String scheme, F[] src, List<R> dest, int userId,
+ long customFlags) {
final String action = intent.getAction();
final Uri data = intent.getData();
final String packageName = intent.getPackage();
@@ -791,7 +805,8 @@
int match;
if (debug) Slog.v(TAG, "Matching against filter " + filter);
- if (excludingStopped && isFilterStopped(filter, userId)) {
+ if (excludingStopped && isFilterStopped(computer.getPackageStateInternal(packageName),
+ userId)) {
if (debug) {
Slog.v(TAG, " Filter's target is stopped; skipping");
}
@@ -833,7 +848,7 @@
Integer.toHexString(match) + " hasDefault="
+ intentFilter.hasCategory(Intent.CATEGORY_DEFAULT));
if (!defaultOnly || intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) {
- final R oneResult = newResult(filter, match, userId);
+ final R oneResult = newResult(computer, filter, match, userId, customFlags);
if (debug) Slog.v(TAG, " Created result: " + oneResult);
if (oneResult != null) {
dest.add(oneResult);
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 8e53101..16ff167 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
+import static android.os.Process.SYSTEM_UID;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -261,6 +262,37 @@
}
}
+ /** Returns information about pinned files and sizes for StatsPullAtomService. */
+ public List<PinnedFileStats> dumpDataForStatsd() {
+ List<PinnedFileStats> pinnedFileStats = new ArrayList<>();
+ synchronized (PinnerService.this) {
+ for (PinnedFile pinnedFile : mPinnedFiles) {
+ pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile));
+ }
+
+ for (int key : mPinnedApps.keySet()) {
+ PinnedApp app = mPinnedApps.get(key);
+ for (PinnedFile pinnedFile : mPinnedApps.get(key).mFiles) {
+ pinnedFileStats.add(new PinnedFileStats(app.uid, pinnedFile));
+ }
+ }
+ }
+ return pinnedFileStats;
+ }
+
+ /** Wrapper class for statistics for a pinned file. */
+ public static class PinnedFileStats {
+ public final int uid;
+ public final String filename;
+ public final int sizeKb;
+
+ protected PinnedFileStats(int uid, PinnedFile file) {
+ this.uid = uid;
+ this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1);
+ this.sizeKb = file.bytesPinned / 1024;
+ }
+ }
+
/**
* Handler for on start pinning message
*/
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 811f2f5..efbc4de 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -103,6 +103,8 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.am.BatteryStatsService;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -2879,6 +2881,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -3016,14 +3019,32 @@
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
+
// Send the broadcast twice -- once for all apps with READ_PHONE_STATE, then again
- // for all apps with READ_PRIV but not READ_PHONE_STATE. This ensures that any app holding
- // either READ_PRIV or READ_PHONE get this broadcast exactly once.
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.READ_PHONE_STATE);
- mContext.createContextAsUser(UserHandle.ALL, 0)
- .sendBroadcastMultiplePermissions(intent,
- new String[] { Manifest.permission.READ_PRIVILEGED_PHONE_STATE },
- new String[] { Manifest.permission.READ_PHONE_STATE });
+ // for all apps with READ_PRIVILEGED_PHONE_STATE but not READ_PHONE_STATE.
+ // Do this again twice, the first time for apps with ACCESS_FINE_LOCATION, then again with
+ // the location-sanitized service state for all apps without ACCESS_FINE_LOCATION.
+ // This ensures that any app holding either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE
+ // get this broadcast exactly once, and we are not exposing location without permission.
+ mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent,
+ new String[] {Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.ACCESS_FINE_LOCATION});
+ mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent,
+ new String[] {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ Manifest.permission.ACCESS_FINE_LOCATION},
+ new String[] {Manifest.permission.READ_PHONE_STATE});
+
+ // Replace bundle with location-sanitized ServiceState
+ data = new Bundle();
+ state.createLocationInfoSanitizedCopy(true).fillInNotifierBundle(data);
+ intent.putExtras(data);
+ mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent,
+ new String[] {Manifest.permission.READ_PHONE_STATE},
+ new String[] {Manifest.permission.ACCESS_FINE_LOCATION});
+ mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(intent,
+ new String[] {Manifest.permission.READ_PRIVILEGED_PHONE_STATE},
+ new String[] {Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.ACCESS_FINE_LOCATION});
}
private void broadcastSignalStrengthChanged(SignalStrength signalStrength, int phoneId,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index e2e53cb..5fe8719 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -46,6 +46,8 @@
import com.android.internal.annotations.GuardedBy;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
@@ -1430,6 +1432,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
void dump(PrintWriter pw) {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c87943d..6813f3f8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -24,6 +24,7 @@
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
@@ -395,12 +396,14 @@
import com.android.server.graphics.fonts.FontManagerInternal;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.os.NativeTombstoneManager;
+import com.android.server.pm.Computer;
import com.android.server.pm.Installer;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.SELinuxUtil;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -415,6 +418,7 @@
import com.android.server.wm.WindowManagerService;
import com.android.server.wm.WindowProcessController;
+import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.VMRuntime;
import libcore.util.EmptyArray;
@@ -1104,10 +1108,11 @@
}
@Override
- protected BroadcastFilter newResult(BroadcastFilter filter, int match, int userId) {
+ protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
+ int match, int userId, long customFlags) {
if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
|| userId == filter.owningUserId) {
- return super.newResult(filter, match, userId);
+ return super.newResult(computer, filter, match, userId, customFlags);
}
return null;
}
@@ -5615,15 +5620,29 @@
synchronized (mProcLock) {
synchronized (mPidsSelfLocked) {
+ int newestTimeIndex = -1;
+ long newestTime = Long.MIN_VALUE;
for (int i = 0; i < pids.length; i++) {
ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
if (pr != null) {
- final boolean isPendingTop =
- mPendingStartActivityUids.isPendingTopPid(pr.uid, pids[i]);
- states[i] = isPendingTop ? PROCESS_STATE_TOP : pr.mState.getCurProcState();
- if (scores != null) {
- scores[i] = isPendingTop
- ? (ProcessList.FOREGROUND_APP_ADJ - 1) : pr.mState.getCurAdj();
+ final long pendingTopTime =
+ mPendingStartActivityUids.getPendingTopPidTime(pr.uid, pids[i]);
+ if (pendingTopTime != PendingStartActivityUids.INVALID_TIME) {
+ // The uid in mPendingStartActivityUids gets the TOP process state.
+ states[i] = PROCESS_STATE_TOP;
+ if (scores != null) {
+ // The uid in mPendingStartActivityUids gets a better score.
+ scores[i] = ProcessList.FOREGROUND_APP_ADJ - 1;
+ }
+ if (pendingTopTime > newestTime) {
+ newestTimeIndex = i;
+ newestTime = pendingTopTime;
+ }
+ } else {
+ states[i] = pr.mState.getCurProcState();
+ if (scores != null) {
+ scores[i] = pr.mState.getCurAdj();
+ }
}
} else {
states[i] = PROCESS_STATE_NONEXISTENT;
@@ -5632,6 +5651,13 @@
}
}
}
+ // The uid with the newest timestamp in mPendingStartActivityUids gets the best
+ // score.
+ if (newestTimeIndex != -1) {
+ if (scores != null) {
+ scores[newestTimeIndex] = ProcessList.FOREGROUND_APP_ADJ - 2;
+ }
+ }
}
}
}
@@ -6659,6 +6685,7 @@
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
mOomAdjProfiler.onWakefulnessChanged(wakefulness);
+ mOomAdjuster.onWakefulnessChanged(wakefulness);
}
updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
}
@@ -9154,6 +9181,7 @@
/**
* Wrapper function to print out debug data filtered by specified arguments.
*/
+ @NeverCompile // Avoid size overhead of debugging code.
private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
@@ -9688,6 +9716,7 @@
return needSep;
}
+ @NeverCompile // Avoid size overhead of debugging code.
@GuardedBy({"this", "mProcLock"})
void dumpOtherProcessesInfoLSP(FileDescriptor fd, PrintWriter pw,
boolean dumpAll, String dumpPackage, int dumpAppId, int numPers, boolean needSep) {
@@ -10809,6 +10838,7 @@
boolean dumpProto;
}
+ @NeverCompile // Avoid size overhead of debugging code.
final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix,
String[] args, boolean brief, PrintWriter categoryPw, boolean asProto) {
MemoryUsageDumpOptions opts = new MemoryUsageDumpOptions();
@@ -10891,6 +10921,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
private final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix,
MemoryUsageDumpOptions opts, final String[] innerArgs, boolean brief,
ArrayList<ProcessRecord> procs, PrintWriter categoryPw) {
@@ -11540,6 +11571,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
private final void dumpApplicationMemoryUsage(FileDescriptor fd,
MemoryUsageDumpOptions opts, final String[] innerArgs, boolean brief,
ArrayList<ProcessRecord> procs) {
@@ -13011,7 +13043,7 @@
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadcast");
}
- mReceiverResolver.addFilter(bf);
+ mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
}
// Enqueue broadcasts for all existing stickies that match
@@ -13833,6 +13865,7 @@
intent, resolvedType, callingUid, users, broadcastAllowList);
}
if (intent.getComponent() == null) {
+ final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
// Query one target user at a time, excluding shell-restricted users
for (int i = 0; i < users.length; i++) {
@@ -13841,7 +13874,7 @@
continue;
}
List<BroadcastFilter> registeredReceiversForUser =
- mReceiverResolver.queryIntent(intent,
+ mReceiverResolver.queryIntent(snapshot, intent,
resolvedType, false /*defaultOnly*/, users[i]);
if (registeredReceivers == null) {
registeredReceivers = registeredReceiversForUser;
@@ -13850,7 +13883,7 @@
}
}
} else {
- registeredReceivers = mReceiverResolver.queryIntent(intent,
+ registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
resolvedType, false /*defaultOnly*/, userId);
}
}
@@ -14311,14 +14344,18 @@
return false;
}
- if (!Build.IS_DEBUGGABLE) {
- int match = mContext.getPackageManager().checkSignatures(
- ii.targetPackage, ii.packageName);
- if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
+ int match = mContext.getPackageManager().checkSignatures(
+ ii.targetPackage, ii.packageName);
+ if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
+ if (Build.IS_DEBUGGABLE && (flags & INSTR_FLAG_ALWAYS_CHECK_SIGNATURE) == 0) {
+ Slog.w(TAG, "Instrumentation test " + ii.packageName
+ + " doesn't have a signature matching the target " + ii.targetPackage
+ + ", which would not be allowed on the production Android builds");
+ } else {
String msg = "Permission Denial: starting instrumentation "
+ className + " from pid="
+ Binder.getCallingPid()
- + ", uid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ " not allowed because package " + ii.packageName
+ " does not have a signature matching the target "
+ ii.targetPackage;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 08508b2..28b807c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -34,6 +34,7 @@
import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -107,6 +108,8 @@
import com.android.server.compat.PlatformCompat;
import com.android.server.utils.Slogf;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -3309,6 +3312,7 @@
dumpHelp(pw, mDumping);
}
+ @NeverCompile // Avoid size overhead of debugging code.
static void dumpHelp(PrintWriter pw, boolean dumping) {
if (dumping) {
pw.println("Activity manager dump options:");
@@ -3346,7 +3350,11 @@
pw.println(" --checkin: output checkin format, resetting data.");
pw.println(" --C: output checkin format, not resetting data.");
pw.println(" --proto: output dump in protocol buffer format.");
- pw.println(" --autofill: dump just the autofill-related state of an activity");
+ pw.printf(" %s: dump just the DUMPABLE-related state of an activity. Use the %s "
+ + "option to list the supported DUMPABLEs\n", Activity.DUMP_ARG_DUMP_DUMPABLE,
+ Activity.DUMP_ARG_LIST_DUMPABLES);
+ pw.printf(" %s: show the available dumpables in an activity\n",
+ Activity.DUMP_ARG_LIST_DUMPABLES);
} else {
pw.println("Activity manager (activity) commands:");
pw.println(" help");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 6c1a00d..97dd323 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -1018,6 +1018,7 @@
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
0,
mService.mUserController.getCurrentUserId()) != 0;
+ final String packageName = proc.info.packageName;
final boolean crashSilenced = mAppsNotReportingCrashes != null
&& mAppsNotReportingCrashes.contains(proc.info.packageName);
final long now = SystemClock.uptimeMillis();
@@ -1026,6 +1027,7 @@
if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
&& !crashSilenced && !shouldThottle
&& (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
+ Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
errState.getDialogController().showCrashDialogs(data);
if (!proc.isolated) {
mProcessCrashShowDialogTimes.put(proc.processName, proc.uid, now);
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java
index 7f48d52..69f70ca 100644
--- a/services/core/java/com/android/server/am/AppPermissionTracker.java
+++ b/services/core/java/com/android/server/am/AppPermissionTracker.java
@@ -64,6 +64,8 @@
@GuardedBy("mLock")
private SparseArray<ArraySet<String>> mUidGrantedPermissionsInMonitor = new SparseArray<>();
+ private volatile boolean mLockedBootCompleted = false;
+
AppPermissionTracker(Context context, AppRestrictionController controller) {
this(context, controller, null, null);
}
@@ -85,20 +87,20 @@
final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal();
final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
+ final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor;
for (int userId : allUsers) {
final List<ApplicationInfo> apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID);
if (apps == null) {
continue;
}
- synchronized (mLock) {
- final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor;
- final long now = SystemClock.elapsedRealtime();
- for (int i = 0, size = apps.size(); i < size; i++) {
- final ApplicationInfo ai = apps.get(i);
- for (String permission : permissions) {
- if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) {
- continue;
- }
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = 0, size = apps.size(); i < size; i++) {
+ final ApplicationInfo ai = apps.get(i);
+ for (String permission : permissions) {
+ if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) {
+ continue;
+ }
+ synchronized (mLock) {
ArraySet<String> grantedPermissions = uidPerms.get(ai.uid);
if (grantedPermissions == null) {
grantedPermissions = new ArraySet<String>();
@@ -132,25 +134,30 @@
private void handlePermissionsChanged(int uid) {
final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
if (permissions != null && permissions.length > 0) {
+ final PermissionManagerServiceInternal pm =
+ mInjector.getPermissionManagerServiceInternal();
+ final boolean[] states = new boolean[permissions.length];
+ for (int i = 0; i < permissions.length; i++) {
+ states[i] = pm.checkUidPermission(uid, permissions[i]) == PERMISSION_GRANTED;
+ if (DEBUG_PERMISSION_TRACKER) {
+ Slog.i(TAG, UserHandle.formatUid(uid) + " " + permissions[i] + "=" + states[i]);
+ }
+ }
synchronized (mLock) {
- handlePermissionsChangedLocked(uid);
+ handlePermissionsChangedLocked(uid, permissions, states);
}
}
}
@GuardedBy("mLock")
- private void handlePermissionsChangedLocked(int uid) {
- final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal();
+ private void handlePermissionsChangedLocked(int uid, String[] permissions, boolean[] states) {
final int index = mUidGrantedPermissionsInMonitor.indexOfKey(uid);
ArraySet<String> grantedPermissions = index >= 0
? mUidGrantedPermissionsInMonitor.valueAt(index) : null;
- final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
final long now = SystemClock.elapsedRealtime();
- for (String permission: permissions) {
- boolean granted = pm.checkUidPermission(uid, permission) == PERMISSION_GRANTED;
- if (DEBUG_PERMISSION_TRACKER) {
- Slog.i(TAG, UserHandle.formatUid(uid) + " " + permission + "=" + granted);
- }
+ for (int i = 0; i < permissions.length; i++) {
+ final String permission = permissions[i];
+ final boolean granted = states[i];
boolean changed = false;
if (granted) {
if (grantedPermissions == null) {
@@ -200,6 +207,10 @@
}
private void onPermissionTrackerEnabled(boolean enabled) {
+ if (!mLockedBootCompleted) {
+ // Not ready, bail out.
+ return;
+ }
final PermissionManager pm = mInjector.getPermissionManager();
if (enabled) {
pm.addOnPermissionsChangeListener(this);
@@ -211,6 +222,12 @@
}
@Override
+ void onLockedBootCompleted() {
+ mLockedBootCompleted = true;
+ onPermissionTrackerEnabled(mInjector.getPolicy().isEnabled());
+ }
+
+ @Override
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("APP PERMISSIONS TRACKER:");
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 1129c19..2ffd487 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -71,6 +71,7 @@
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.reasonCodeToString;
import static android.os.Process.SYSTEM_UID;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -792,7 +793,7 @@
mInjector = injector;
mContext = injector.getContext();
mActivityManagerService = service;
- mBgHandlerThread = new HandlerThread("bgres-controller");
+ mBgHandlerThread = new HandlerThread("bgres-controller", THREAD_PRIORITY_BACKGROUND);
mBgHandlerThread.start();
mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector);
mBgExecutor = new HandlerExecutor(mBgHandler);
@@ -816,9 +817,11 @@
mInjector.getAppStandbyInternal().addListener(mAppIdleStateChangeListener);
mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mBgExecutor,
mRoleHolderChangedListener, UserHandle.ALL);
- for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
- mAppStateTrackers.get(i).onSystemReady();
- }
+ mInjector.scheduleInitTrackers(mBgHandler, () -> {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onSystemReady();
+ }
+ });
}
@VisibleForTesting
@@ -2137,6 +2140,10 @@
}
return null;
}
+
+ void scheduleInitTrackers(Handler handler, Runnable initializers) {
+ handler.post(initializers);
+ }
}
private void registerForSystemBroadcasts() {
@@ -2221,6 +2228,21 @@
userFilter.addAction(Intent.ACTION_USER_REMOVED);
userFilter.addAction(Intent.ACTION_UID_REMOVED);
mContext.registerReceiverForAllUsers(broadcastReceiver, userFilter, null, mBgHandler);
+ final BroadcastReceiver bootReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ switch (intent.getAction()) {
+ case Intent.ACTION_LOCKED_BOOT_COMPLETED: {
+ onLockedBootCompleted();
+ } break;
+ }
+ }
+ };
+ final IntentFilter bootFilter = new IntentFilter();
+ bootFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+ mContext.registerReceiverAsUser(bootReceiver, UserHandle.SYSTEM,
+ bootFilter, null, mBgHandler);
}
void forEachTracker(Consumer<BaseAppStateTracker> sink) {
@@ -2275,6 +2297,12 @@
mRestrictionSettings.removeUid(uid);
}
+ private void onLockedBootCompleted() {
+ for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
+ mAppStateTrackers.get(i).onLockedBootCompleted();
+ }
+ }
+
boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
return mConstantsObserver.mBgAutoRestrictedBucket;
}
diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java
index 482d697..0fada53 100644
--- a/services/core/java/com/android/server/am/BaseAppStateTracker.java
+++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java
@@ -204,6 +204,12 @@
}
/**
+ * Called when the system sends LOCKED_BOOT_COMPLETED.
+ */
+ void onLockedBootCompleted() {
+ }
+
+ /**
* Called when a device config property in the activity manager namespace
* has changed.
*/
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 7af73eb..86ca699 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -28,6 +28,7 @@
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
+import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
@@ -1051,6 +1052,15 @@
}
}
+ void onWakefulnessChanged(int wakefulness) {
+ if(wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ // Remove any pending compaction we may have scheduled to happen while screen was off
+ Slog.e(TAG_AM, "Cancel pending or running compactions as system is awake");
+ mPendingCompactionProcesses.clear();
+ cancelCompaction();
+ }
+ }
+
@GuardedBy({"mService", "mProcLock"})
void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
// Cancel any currently executing compactions
@@ -1105,6 +1115,9 @@
int lastOomAdj = msg.arg1;
int procState = msg.arg2;
synchronized (mProcLock) {
+ if(mPendingCompactionProcesses.isEmpty()) {
+ return;
+ }
proc = mPendingCompactionProcesses.remove(0);
opt = proc.mOptRecord;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 6c9187a..8d77eda 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2470,6 +2470,10 @@
}
}
+ void onWakefulnessChanged(int wakefulness) {
+ mCachedAppOptimizer.onWakefulnessChanged(wakefulness);
+ }
+
/** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
@GuardedBy({"mService", "mProcLock"})
private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java
index 6bf9d4e..802ea00 100644
--- a/services/core/java/com/android/server/am/PendingStartActivityUids.java
+++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java
@@ -35,6 +35,8 @@
final class PendingStartActivityUids {
static final String TAG = ActivityManagerService.TAG;
+ public static final long INVALID_TIME = 0;
+
// Key is uid, value is Pair of pid and SystemClock.elapsedRealtime() when the
// uid is added.
private final SparseArray<Pair<Integer, Long>> mPendingUids = new SparseArray();
@@ -63,13 +65,20 @@
}
}
- synchronized boolean isPendingTopPid(int uid, int pid) {
+ /**
+ * Return the elapsedRealtime when the uid is added to the mPendingUids map.
+ * @param uid
+ * @param pid
+ * @return elapsedRealtime if the uid is in the mPendingUids map;
+ * INVALID_TIME if the uid is not in the mPendingUids map.
+ */
+ synchronized long getPendingTopPidTime(int uid, int pid) {
+ long ret = INVALID_TIME;
final Pair<Integer, Long> pendingPid = mPendingUids.get(uid);
- if (pendingPid != null) {
- return pendingPid.first == pid;
- } else {
- return false;
+ if (pendingPid != null && pendingPid.first == pid) {
+ ret = pendingPid.second;
}
+ return ret;
}
synchronized boolean isPendingTopUid(int uid) {
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 84d2b1f..7371d07 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -44,6 +44,8 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -932,6 +934,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
private void dumpInner(PrintWriter pw, String[] args) {
final long now = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 90aefe0..d239c02 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -230,7 +230,9 @@
"Caller did not have permission while calling " + methodName);
userId = handleIncomingUser(userId, methodName);
synchronized (mLock) {
- if (!checkUserStatesExist(userId, methodName)) {
+ // Don't log as this method can be called before user states exist as part of the
+ // force-stop check.
+ if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ false)) {
return false;
}
final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
@@ -238,8 +240,6 @@
if (pkgState == null
|| !mPackageManagerInternal.canQueryPackage(
Binder.getCallingUid(), packageName)) {
- Slog.e(TAG, TextUtils.formatSimple("Package %s is not installed for user %s",
- packageName, userId));
return false;
}
return pkgState.hibernated;
@@ -289,7 +289,7 @@
"Caller does not have MANAGE_APP_HIBERNATION permission.");
final int realUserId = handleIncomingUser(userId, methodName);
synchronized (mLock) {
- if (!checkUserStatesExist(realUserId, methodName)) {
+ if (!checkUserStatesExist(realUserId, methodName, /* shouldLog= */ true)) {
return;
}
final Map<String, UserLevelState> packageStates = mUserStates.get(realUserId);
@@ -382,7 +382,7 @@
"Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, methodName);
synchronized (mLock) {
- if (!checkUserStatesExist(userId, methodName)) {
+ if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ true)) {
return hibernatingPackages;
}
Map<String, UserLevelState> userStates = mUserStates.get(userId);
@@ -419,7 +419,7 @@
"Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, methodName);
synchronized (mLock) {
- if (!checkUserStatesExist(userId, methodName)) {
+ if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ true)) {
return statsMap;
}
final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId);
@@ -431,7 +431,7 @@
}
if (!mGlobalHibernationStates.containsKey(pkgName)
|| !userPackageStates.containsKey(pkgName)) {
- Slog.w(TAG, String.format(
+ Slog.w(TAG, TextUtils.formatSimple(
"No hibernation state associated with package %s user %d. Maybe"
+ "the package was uninstalled? ", pkgName, userId));
continue;
@@ -585,7 +585,7 @@
PackageInfo pkgInfo = installedPackages.get(packageName);
UserLevelState currentState = diskStates.get(i);
if (pkgInfo == null) {
- Slog.w(TAG, String.format(
+ Slog.w(TAG, TextUtils.formatSimple(
"No hibernation state associated with package %s user %d. Maybe"
+ "the package was uninstalled? ", packageName, userId));
continue;
@@ -633,7 +633,7 @@
for (int i = 0, size = diskStates.size(); i < size; i++) {
GlobalLevelState state = diskStates.get(i);
if (!installedPackages.contains(state.packageName)) {
- Slog.w(TAG, String.format(
+ Slog.w(TAG, TextUtils.formatSimple(
"No hibernation state associated with package %s. Maybe the "
+ "package was uninstalled? ", state.packageName));
continue;
@@ -742,18 +742,24 @@
*
* @param userId user to check
* @param methodName method name that is calling. Used for logging purposes.
+ * @param shouldLog whether we should log why the user state doesn't exist
* @return true if user states exist
*/
@GuardedBy("mLock")
- private boolean checkUserStatesExist(int userId, String methodName) {
+ private boolean checkUserStatesExist(int userId, String methodName, boolean shouldLog) {
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- Slog.e(TAG, String.format(
- "Attempt to call %s on stopped or nonexistent user %d", methodName, userId));
+ if (shouldLog) {
+ Slog.w(TAG, TextUtils.formatSimple(
+ "Attempt to call %s on stopped or nonexistent user %d",
+ methodName, userId));
+ }
return false;
}
if (!mUserStates.contains(userId)) {
- Slog.w(TAG, String.format(
- "Attempt to call %s before states have been read from disk", methodName));
+ if (shouldLog) {
+ Slog.w(TAG, TextUtils.formatSimple(
+ "Attempt to call %s before states have been read from disk", methodName));
+ }
return false;
}
return true;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index cebcc64f..e2d00f7 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -179,6 +179,8 @@
import com.android.server.pm.PackageList;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import dalvik.annotation.optimization.NeverCompile;
+
import libcore.util.EmptyArray;
import org.json.JSONException;
@@ -5902,6 +5904,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index d2fa386..567d1ae 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -30,7 +30,7 @@
import android.app.ActivityThread;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
-import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -59,7 +59,7 @@
import android.service.attention.AttentionService.AttentionSuccessCodes;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
-import android.service.attention.IProximityCallback;
+import android.service.attention.IProximityUpdateCallback;
import android.text.TextUtils;
import android.util.Slog;
@@ -336,7 +336,7 @@
* @return {@code true} if the framework was able to dispatch the request
*/
@VisibleForTesting
- boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) {
+ boolean onStartProximityUpdates(ProximityUpdateCallbackInternal callbackInternal) {
Objects.requireNonNull(callbackInternal);
if (!mIsServiceEnabled) {
Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device.");
@@ -385,7 +385,7 @@
/** Cancels the specified proximity registration. */
@VisibleForTesting
- void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) {
+ void onStopProximityUpdates(ProximityUpdateCallbackInternal callbackInternal) {
synchronized (mLock) {
if (mCurrentProximityUpdate == null
|| !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal)
@@ -506,12 +506,12 @@
@Override
public boolean onStartProximityUpdates(
- ProximityCallbackInternal callback) {
+ ProximityUpdateCallbackInternal callback) {
return AttentionManagerService.this.onStartProximityUpdates(callback);
}
@Override
- public void onStopProximityUpdates(ProximityCallbackInternal callback) {
+ public void onStopProximityUpdates(ProximityUpdateCallbackInternal callback) {
AttentionManagerService.this.onStopProximityUpdates(callback);
}
}
@@ -635,13 +635,13 @@
@VisibleForTesting
final class ProximityUpdate {
- private final ProximityCallbackInternal mCallbackInternal;
- private final IProximityCallback mIProximityCallback;
+ private final ProximityUpdateCallbackInternal mCallbackInternal;
+ private final IProximityUpdateCallback mIProximityUpdateCallback;
private boolean mStartedUpdates;
- ProximityUpdate(ProximityCallbackInternal callbackInternal) {
+ ProximityUpdate(ProximityUpdateCallbackInternal callbackInternal) {
mCallbackInternal = callbackInternal;
- mIProximityCallback = new IProximityCallback.Stub() {
+ mIProximityUpdateCallback = new IProximityUpdateCallback.Stub() {
@Override
public void onProximityUpdate(double distance) {
synchronized (mLock) {
@@ -664,7 +664,7 @@
return false;
}
try {
- mService.onStartProximityUpdates(mIProximityCallback);
+ mService.onStartProximityUpdates(mIProximityUpdateCallback);
mStartedUpdates = true;
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
@@ -758,7 +758,8 @@
if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) {
if (mService != null) {
try {
- mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback);
+ mService.onStartProximityUpdates(
+ mCurrentProximityUpdate.mIProximityUpdateCallback);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Cannot call into the AttentionService", e);
}
@@ -913,7 +914,7 @@
}
}
- class TestableProximityCallbackInternal extends ProximityCallbackInternal {
+ class TestableProximityUpdateCallbackInternal extends ProximityUpdateCallbackInternal {
private double mLastCallbackCode = PROXIMITY_UNKNOWN;
@Override
@@ -932,8 +933,8 @@
final TestableAttentionCallbackInternal mTestableAttentionCallback =
new TestableAttentionCallbackInternal();
- final TestableProximityCallbackInternal mTestableProximityCallback =
- new TestableProximityCallbackInternal();
+ final TestableProximityUpdateCallbackInternal mTestableProximityUpdateCallback =
+ new TestableProximityUpdateCallbackInternal();
@Override
public int onCommand(@Nullable final String cmd) {
@@ -964,8 +965,8 @@
return cmdClearTestableAttentionService();
case "getLastTestCallbackCode":
return cmdGetLastTestCallbackCode();
- case "getLastTestProximityCallbackCode":
- return cmdGetLastTestProximityCallbackCode();
+ case "getLastTestProximityUpdateCallbackCode":
+ return cmdGetLastTestProximityUpdateCallbackCode();
default:
return handleDefaultCommands(cmd);
}
@@ -990,7 +991,7 @@
private int cmdClearTestableAttentionService() {
sTestAttentionServicePackage = "";
mTestableAttentionCallback.reset();
- mTestableProximityCallback.reset();
+ mTestableProximityUpdateCallback.reset();
resetStates();
return 0;
}
@@ -1011,14 +1012,14 @@
private int cmdCallOnStartProximityUpdates() {
final PrintWriter out = getOutPrintWriter();
- boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback);
+ boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityUpdateCallback);
out.println(calledSuccessfully ? "true" : "false");
return 0;
}
private int cmdCallOnStopProximityUpdates() {
final PrintWriter out = getOutPrintWriter();
- onStopProximityUpdates(mTestableProximityCallback);
+ onStopProximityUpdates(mTestableProximityUpdateCallback);
out.println("true");
return 0;
}
@@ -1036,9 +1037,9 @@
return 0;
}
- private int cmdGetLastTestProximityCallbackCode() {
+ private int cmdGetLastTestProximityUpdateCallbackCode() {
final PrintWriter out = getOutPrintWriter();
- out.println(mTestableProximityCallback.getLastCallbackCode());
+ out.println(mTestableProximityUpdateCallback.getLastCallbackCode());
return 0;
}
@@ -1081,7 +1082,7 @@
out.println(
" := true, if the request was successfully dispatched to the service "
+ "implementation."
- + " (to see the result, call getLastTestProximityCallbackCode)");
+ + " (to see the result, call getLastTestProximityUpdateCallbackCode)");
out.println(" := false, otherwise");
out.println(" call onStopProximityUpdates: Cancels proximity updates");
out.println(" getLastTestCallbackCode");
@@ -1089,7 +1090,7 @@
out.println(
" := An integer, representing the last callback code received from the "
+ "bounded implementation. If none, it will return -1");
- out.println(" getLastTestProximityCallbackCode");
+ out.println(" getLastTestProximityUpdateCallbackCode");
out.println(" ---returns:");
out.println(
" := A double, representing the last proximity value received from the "
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 807293f..0b9fb1a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3330,6 +3330,13 @@
}
}
+ private void enforceAccessUltrasoundPermission() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing ACCESS_ULTRASOUND permission");
+ }
+ }
+
private void enforceQueryStatePermission() {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3462,6 +3469,12 @@
attributionTag, Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
}
+ /** @see AudioManager#isUltrasoundSupported() */
+ public boolean isUltrasoundSupported() {
+ enforceAccessUltrasoundPermission();
+ return AudioSystem.isUltrasoundSupported();
+ }
+
private boolean canChangeAccessibilityVolume() {
synchronized (mAccessibilityServiceUidsLock) {
if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index f4aa88f..73afa60 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -116,6 +116,8 @@
import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
import com.android.server.job.JobSchedulerInternal;
+import dalvik.annotation.optimization.NeverCompile;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -2169,6 +2171,7 @@
return true;
}
+ @NeverCompile // Avoid size overhead of debugging code.
protected void dumpSyncState(PrintWriter pw, SyncAdapterStateFetcher buckets) {
final StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 2c2a2bf..17215e5 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -131,6 +131,7 @@
private static final int MSG_STOP_SENSOR_LISTENER = 2;
private static final int MSG_START_SENSOR_LISTENER = 3;
private static final int MSG_BRIGHTNESS_CONFIG_CHANGED = 4;
+ private static final int MSG_SENSOR_CHANGED = 5;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -158,6 +159,7 @@
// These members should only be accessed on the mBgHandler thread.
private BroadcastReceiver mBroadcastReceiver;
private SensorListener mSensorListener;
+ private Sensor mLightSensor;
private SettingsObserver mSettingsObserver;
private DisplayListener mDisplayListener;
private boolean mSensorRegistered;
@@ -327,6 +329,14 @@
m.sendToTarget();
}
+ /**
+ * Updates the light sensor to use.
+ */
+ public void setLightSensor(Sensor lightSensor) {
+ mBgHandler.obtainMessage(MSG_SENSOR_CHANGED, 0 /*unused*/, 0/*unused*/, lightSensor)
+ .sendToTarget();
+ }
+
private void handleBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId) {
@@ -428,13 +438,28 @@
}
}
+ private void handleSensorChanged(Sensor lightSensor) {
+ if (mLightSensor != lightSensor) {
+ mLightSensor = lightSensor;
+ stopSensorListener();
+ synchronized (mDataCollectionLock) {
+ mLastSensorReadings.clear();
+ }
+ // Attempt to restart the sensor listener. It will check to see if it should be running
+ // so there is no need to also check here.
+ startSensorListener();
+ }
+ }
+
private void startSensorListener() {
if (!mSensorRegistered
+ && mLightSensor != null
+ && mAmbientBrightnessStatsTracker != null
&& mInjector.isInteractive(mContext)
&& mInjector.isBrightnessModeAutomatic(mContentResolver)) {
mAmbientBrightnessStatsTracker.start();
mSensorRegistered = true;
- mInjector.registerSensorListener(mContext, mSensorListener,
+ mInjector.registerSensorListener(mContext, mSensorListener, mLightSensor,
mInjector.getBackgroundHandler());
}
}
@@ -736,6 +761,7 @@
pw.println("BrightnessTracker state:");
synchronized (mDataCollectionLock) {
pw.println(" mStarted=" + mStarted);
+ pw.println(" mLightSensor=" + mLightSensor);
pw.println(" mLastBatteryLevel=" + mLastBatteryLevel);
pw.println(" mLastBrightness=" + mLastBrightness);
pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
@@ -1017,6 +1043,9 @@
disableColorSampling();
}
break;
+ case MSG_SENSOR_CHANGED:
+ handleSensorChanged((Sensor) msg.obj);
+ break;
}
}
@@ -1045,9 +1074,8 @@
@VisibleForTesting
static class Injector {
public void registerSensorListener(Context context,
- SensorEventListener sensorListener, Handler handler) {
+ SensorEventListener sensorListener, Sensor lightSensor, Handler handler) {
SensorManager sensorManager = context.getSystemService(SensorManager.class);
- Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorManager.registerListener(sensorListener,
lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 6866137..064e307 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -42,7 +42,6 @@
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HighBrightnessMode;
-import com.android.server.display.config.Interpolation;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.Point;
import com.android.server.display.config.RefreshRateRange;
@@ -1254,19 +1253,17 @@
}
}
- private int convertInterpolationType(Interpolation value) {
- if (value == null) {
+ private int convertInterpolationType(String value) {
+ if (TextUtils.isEmpty(value)) {
return INTERPOLATION_DEFAULT;
}
- switch (value) {
- case _default:
- return INTERPOLATION_DEFAULT;
- case linear:
- return INTERPOLATION_LINEAR;
- default:
- Slog.wtf(TAG, "Unexpected Interpolation Type: " + value);
- return INTERPOLATION_DEFAULT;
+
+ if ("linear".equals(value)) {
+ return INTERPOLATION_LINEAR;
}
+
+ Slog.wtf(TAG, "Unexpected Interpolation Type: " + value);
+ return INTERPOLATION_DEFAULT;
}
private void loadAmbientHorizonFromDdc(DisplayConfiguration config) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 418e91d..9067f2e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -826,7 +826,6 @@
private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
- loadAmbientLightSensor();
loadBrightnessRampRates();
loadProximitySensor();
loadNitsRange(mContext.getResources());
@@ -972,6 +971,9 @@
}
loadAmbientLightSensor();
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker.setLightSensor(mLightSensor);
+ }
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
index cb93cc8..f529c4c 100644
--- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
+++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
@@ -98,6 +98,13 @@
return ColorDisplayManager.isColorTransformAccelerated(context);
}
+ @Override
+ public void setActivated(Boolean isActivated) {
+ super.setActivated(isActivated);
+ Slog.i(ColorDisplayService.TAG, (isActivated != null && isActivated)
+ ? "Turning on reduce bright colors" : "Turning off reduce bright colors");
+ }
+
public int getStrength() {
return mStrength;
}
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index 1139d28..bb8a744 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -24,6 +24,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Environment;
import android.os.FileObserver;
import android.os.Handler;
@@ -38,6 +39,8 @@
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.IntentResolver;
+import com.android.server.LocalServices;
+import com.android.server.pm.Computer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -75,6 +78,9 @@
private final RuleObserver mObserver;
+ @NonNull
+ private PackageManagerInternal mPackageManager;
+
private FirewallIntentResolver mActivityResolver = new FirewallIntentResolver();
private FirewallIntentResolver mBroadcastResolver = new FirewallIntentResolver();
private FirewallIntentResolver mServiceResolver = new FirewallIntentResolver();
@@ -123,6 +129,13 @@
mObserver.startWatching();
}
+ private PackageManagerInternal getPackageManager() {
+ if (mPackageManager == null) {
+ mPackageManager = LocalServices.getService(PackageManagerInternal.class);
+ }
+ return mPackageManager;
+ }
+
/**
* This is called from ActivityManager to check if a start activity intent should be allowed.
* It is assumed the caller is already holding the global ActivityManagerService lock.
@@ -154,7 +167,8 @@
// For the first pass, find all the rules that have at least one intent-filter or
// component-filter that matches this intent
List<Rule> candidateRules;
- candidateRules = resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, 0);
+ candidateRules = resolver.queryIntent(getPackageManager().snapshot(), intent, resolvedType,
+ false /*defaultOnly*/, 0);
if (candidateRules == null) {
candidateRules = new ArrayList<Rule>();
}
@@ -375,7 +389,7 @@
for (int ruleIndex=0; ruleIndex<rules.size(); ruleIndex++) {
Rule rule = rules.get(ruleIndex);
for (int i=0; i<rule.getIntentFilterCount(); i++) {
- resolver.addFilter(rule.getIntentFilter(i));
+ resolver.addFilter(null, rule.getIntentFilter(i));
}
for (int i=0; i<rule.getComponentFilterCount(); i++) {
resolver.addComponentFilter(rule.getComponentFilter(i), rule);
@@ -512,7 +526,8 @@
}
@Override
- protected Rule newResult(FirewallIntentFilter filter, int match, int userId) {
+ protected Rule newResult(@NonNull Computer computer, FirewallIntentFilter filter,
+ int match, int userId, long customFlags) {
return filter.rule;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7068ed1..0b7e391 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -130,6 +130,7 @@
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InlineSuggestionsRequest;
@@ -287,6 +288,9 @@
private final InputMethodMenuController mMenuController;
private final InputMethodBindingController mBindingController;
+ // TODO(b/219056452): Use AccessibilityManagerInternal instead.
+ private final AccessibilityManager mAccessibilityManager;
+
/**
* Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
*
@@ -1627,6 +1631,7 @@
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mUserManager = mContext.getSystemService(UserManager.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
mHasFeature = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_INPUT_METHODS);
mPlatformCompat = IPlatformCompat.Stub.asInterface(
@@ -1995,12 +2000,13 @@
@GuardedBy("ImfLock.class")
private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId,
- InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
+ InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback,
+ boolean touchExplorationEnabled) {
final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
try {
IInputMethodInvoker curMethod = getCurMethodLocked();
- if (userId == mSettings.getCurrentUserId() && imi != null
- && imi.isInlineSuggestionsEnabled() && curMethod != null) {
+ if (userId == mSettings.getCurrentUserId() && curMethod != null
+ && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
final IInlineSuggestionsRequestCallback callbackImpl =
new InlineSuggestionsRequestCallbackDecorator(callback,
imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(),
@@ -2014,6 +2020,13 @@
}
}
+ private static boolean isInlineSuggestionsEnabled(InputMethodInfo imi,
+ boolean touchExplorationEnabled) {
+ return imi.isInlineSuggestionsEnabled()
+ && (!touchExplorationEnabled
+ || imi.supportsInlineSuggestionsWithTouchExploration());
+ }
+
/**
* The decorator which validates the host package name in the
* {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
@@ -5197,8 +5210,12 @@
@Override
public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
+ // Get the device global touch exploration state before lock to avoid deadlock.
+ boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
+
synchronized (ImfLock.class) {
- onCreateInlineSuggestionsRequestLocked(userId, requestInfo, cb);
+ onCreateInlineSuggestionsRequestLocked(userId, requestInfo, cb,
+ touchExplorationEnabled);
}
}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 34614d5..490e00e 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -329,6 +329,20 @@
if (mStart) {
+ ActivityManagerInternal ami = LocalServices.getService(
+ ActivityManagerInternal.class);
+ boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
+
+ // The instrumented apks only run for testing, so we don't check user permission.
+ if (isCallerInstrumented) {
+ try {
+ getLogdService().approve(mUid, mGid, mPid, mFd);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+
// TODO Temporarily approve all the requests to unblock testing failures.
try {
getLogdService().approve(mUid, mGid, mPid, mFd);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8d05415..0052df3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -257,6 +257,8 @@
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import dalvik.annotation.optimization.NeverCompile;
+
import libcore.io.IoUtils;
import java.io.File;
@@ -3748,6 +3750,7 @@
return 0;
}
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 70e968f..66c7c50 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -69,6 +69,8 @@
import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
@@ -467,6 +469,7 @@
rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString());
}
+ @NeverCompile // Avoid size overhead of debugging code.
void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
final Notification notification = getSbn().getNotification();
pw.println(prefix + this);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 39ee0f4..9b10058 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -40,6 +40,7 @@
import android.os.ServiceManager;
import android.os.Trace;
import android.sysprop.ApexProperties;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.PrintWriterPrinter;
@@ -608,18 +609,21 @@
continue;
}
- String name = service.getName();
- for (ApexSystemServiceInfo info : mApexSystemServices) {
- if (info.getName().equals(name)) {
- throw new IllegalStateException(String.format(
- "Duplicate apex-system-service %s from %s, %s",
- name, info.mJarPath, service.getJarPath()));
+ if (ai.isActive) {
+ String name = service.getName();
+ for (int j = 0; j < mApexSystemServices.size(); j++) {
+ ApexSystemServiceInfo info = mApexSystemServices.get(j);
+ if (info.getName().equals(name)) {
+ throw new IllegalStateException(TextUtils.formatSimple(
+ "Duplicate apex-system-service %s from %s, %s", name,
+ info.mJarPath, service.getJarPath()));
+ }
}
+ ApexSystemServiceInfo info = new ApexSystemServiceInfo(
+ service.getName(), service.getJarPath(),
+ service.getInitOrder());
+ mApexSystemServices.add(info);
}
-
- ApexSystemServiceInfo info = new ApexSystemServiceInfo(
- service.getName(), service.getJarPath(), service.getInitOrder());
- mApexSystemServices.add(info);
}
Collections.sort(mApexSystemServices);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index d745a23..0b0d1458 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -377,6 +377,12 @@
+ Integer.toHexString(flags) + " migrateAppData=" + migrateAppData);
List<String> result = onlyCoreApps ? new ArrayList<>() : null;
+ try {
+ mInstaller.cleanupInvalidPackageDirs(volumeUuid, userId, flags);
+ } catch (Installer.InstallerException e) {
+ logCriticalInfo(Log.WARN, "Failed to cleanup deleted dirs: " + e);
+ }
+
final File ceDir = Environment.getDataUserCeDirectory(volumeUuid, userId);
final File deDir = Environment.getDataUserDeDirectory(volumeUuid, userId);
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 0d9ccd2..2a4882a 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -51,6 +51,8 @@
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.pm.resolution.ComponentResolverApi;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
@@ -98,7 +100,7 @@
* {@link ComputerEngine} and {@link ComputerLocked}.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-public interface Computer {
+public interface Computer extends PackageDataSnapshot {
/**
* Every method must be annotated.
@@ -178,6 +180,18 @@
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
List<CrossProfileIntentFilter> getMatchingCrossProfileIntentFilters(Intent intent,
String resolvedType, int userId);
+
+ /**
+ * Filters out ephemeral activities.
+ * <p>When resolving for an ephemeral app, only activities that 1) are defined in the
+ * ephemeral app or 2) marked with {@code visibleToEphemeral} are returned.
+ *
+ * @param resolveInfos The pre-filtered list of resolved activities
+ * @param ephemeralPkgName The ephemeral package name. If {@code null}, no filtering
+ * is performed.
+ * @param intent
+ * @return A filtered list of resolved activities.
+ */
@Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
@@ -648,4 +662,16 @@
@Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
@NonNull
ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId);
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @NonNull
+ ComponentResolverApi getComponentResolver();
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @Nullable
+ PackageStateInternal getDisabledSystemPackage(@NonNull String packageName);
+
+ @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
+ @Nullable
+ ResolveInfo getInstantAppInstallerInfo();
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index c437697..9e87898 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -49,7 +49,6 @@
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
-import static com.android.server.pm.ComponentResolver.RESOLVE_PRIORITY_SORTER;
import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
@@ -59,6 +58,7 @@
import static com.android.server.pm.PackageManagerService.HIDE_EPHEMERAL_APIS;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.resolution.ComponentResolver.RESOLVE_PRIORITY_SORTER;
import android.Manifest;
import android.annotation.NonNull;
@@ -143,6 +143,7 @@
import com.android.server.pm.pkg.component.ParsedProvider;
import com.android.server.pm.pkg.component.ParsedService;
import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.resolution.ComponentResolverApi;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationUtils;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -362,7 +363,7 @@
private final PermissionManagerServiceInternal mPermissionManager;
private final ApexManager mApexManager;
private final PackageManagerServiceInjector mInjector;
- private final ComponentResolver mComponentResolver;
+ private final ComponentResolverApi mComponentResolver;
private final InstantAppResolverConnection mInstantAppResolverConnection;
private final DefaultAppProvider mDefaultAppProvider;
private final DomainVerificationManagerInternal mDomainVerificationManager;
@@ -644,7 +645,7 @@
// reader
String pkgName = intent.getPackage();
if (pkgName == null) {
- final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
+ final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
resolvedType, flags, userId);
if (resolveInfos == null) {
return Collections.emptyList();
@@ -654,7 +655,7 @@
}
final AndroidPackage pkg = mPackages.get(pkgName);
if (pkg != null) {
- final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
+ final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(this, intent,
resolvedType, flags, pkg.getServices(),
userId);
if (resolveInfos == null) {
@@ -691,7 +692,7 @@
}
// Check for results in the current profile.
- result = filterIfNotSystemUser(mComponentResolver.queryActivities(
+ result = filterIfNotSystemUser(mComponentResolver.queryActivities(this,
intent, resolvedType, flags, userId), userId);
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
false /*skipPackageCheck*/, flags);
@@ -753,7 +754,7 @@
result = null;
if (setting != null && setting.getAndroidPackage() != null && (resolveForStart
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
- result = filterIfNotSystemUser(mComponentResolver.queryActivities(
+ result = filterIfNotSystemUser(mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
userId), userId);
}
@@ -1181,7 +1182,7 @@
sourceUserId)) {
return null;
}
- List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
+ List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent,
resolvedType, flags, parentUserId);
if (resultTargetUser == null || resultTargetUser.isEmpty()) {
@@ -1232,7 +1233,7 @@
Intent intent, String resolvedType, int userId) {
CrossProfileIntentResolver resolver = mSettings.getCrossProfileIntentResolver(userId);
if (resolver != null) {
- return resolver.queryIntent(intent, resolvedType, false /*defaultOnly*/, userId);
+ return resolver.queryIntent(this, intent, resolvedType, false /*defaultOnly*/, userId);
}
return null;
}
@@ -1447,7 +1448,7 @@
ResolveInfo localInstantApp = null;
boolean blockResolution = false;
if (!alreadyResolvedLocally) {
- final List<ResolveInfo> instantApps = mComponentResolver.queryActivities(
+ final List<ResolveInfo> instantApps = mComponentResolver.queryActivities(this,
intent,
resolvedType,
flags
@@ -1493,8 +1494,8 @@
null /*callingFeatureId*/, isRequesterInstantApp, userId,
null /*verificationBundle*/, resolveForStart,
digest.getDigestPrefixSecure(), token);
- auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
- mInstantAppResolverConnection, requestObject);
+ auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(this,
+ mUserManager, mInstantAppResolverConnection, requestObject);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
} else {
// we have an instant application locally, but, we can't admit that since
@@ -1830,7 +1831,7 @@
return null;
}
- List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
+ List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(this, intent,
resolvedType, flags, targetUserId);
if (CollectionUtils.isEmpty(resultTargetUser)) {
return null;
@@ -2586,7 +2587,7 @@
mSettings.getPersistentPreferredActivities(userId);
//TODO(b/158003772): Remove double query
List<PersistentPreferredActivity> pprefs = ppir != null
- ? ppir.queryIntent(intent, resolvedType,
+ ? ppir.queryIntent(this, intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
userId)
: new ArrayList<>();
@@ -3244,7 +3245,7 @@
// Get the list of preferred activities that handle the intent
if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
List<PreferredActivity> prefs = pir != null
- ? pir.queryIntent(intent, resolvedType,
+ ? pir.queryIntent(this, intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
userId)
: null;
@@ -3376,7 +3377,7 @@
pa.mPref.mComponent,
pa.mPref.mAlways);
pir.removeFilter(pa);
- pir.addFilter(freshPa);
+ pir.addFilter(this, freshPa);
result.mChanged = true;
} else {
if (DEBUG_PREFERRED) {
@@ -3399,7 +3400,7 @@
PreferredActivity lastChosen = new PreferredActivity(
pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
false);
- pir.addFilter(lastChosen);
+ pir.addFilter(this, lastChosen);
result.mChanged = true;
}
result.mPreferredResolveInfo = null;
@@ -3454,7 +3455,7 @@
Slog.v(TAG, "Looking for persistent preferred activities...");
}
List<PersistentPreferredActivity> pprefs = ppir != null
- ? ppir.queryIntent(intent, resolvedType,
+ ? ppir.queryIntent(this, intent, resolvedType,
(flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
userId)
: null;
@@ -4661,7 +4662,8 @@
int callingUid) {
if (!mUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId);
- final ProviderInfo providerInfo = mComponentResolver.queryProvider(name, flags, userId);
+ final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,
+ userId);
boolean checkedGrants = false;
if (providerInfo != null) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
@@ -4739,7 +4741,7 @@
final List<String> names = new ArrayList<>();
final List<ProviderInfo> infos = new ArrayList<>();
final int callingUserId = UserHandle.getCallingUserId();
- mComponentResolver.querySyncProviders(names, infos, safeMode, callingUserId);
+ mComponentResolver.querySyncProviders(this, names, infos, safeMode, callingUserId);
for (int i = infos.size() - 1; i >= 0; i--) {
final ProviderInfo providerInfo = infos.get(i);
final PackageStateInternal ps = mSettings.getPackage(providerInfo.packageName);
@@ -4771,8 +4773,8 @@
if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForComponent(flags, userId);
ArrayList<ProviderInfo> finalList = null;
- final List<ProviderInfo> matchList =
- mComponentResolver.queryProviders(processName, metaDataKey, uid, flags, userId);
+ final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName,
+ metaDataKey, uid, flags, userId);
final int listSize = (matchList == null ? 0 : matchList.size());
for (int i = 0; i < listSize; i++) {
final ProviderInfo providerInfo = matchList.get(i);
@@ -5674,4 +5676,22 @@
public ArraySet<PackageStateInternal> getSharedUserPackages(int sharedUserAppId) {
return mSettings.getSharedUserPackages(sharedUserAppId);
}
+
+ @NonNull
+ @Override
+ public ComponentResolverApi getComponentResolver() {
+ return mComponentResolver;
+ }
+
+ @Nullable
+ @Override
+ public PackageStateInternal getDisabledSystemPackage(@NonNull String packageName) {
+ return mSettings.getDisabledSystemPkg(packageName);
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo getInstantAppInstallerInfo() {
+ return mInstantAppInstallerInfo;
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java
index 583348b..5d89c7d 100644
--- a/services/core/java/com/android/server/pm/ComputerLocked.java
+++ b/services/core/java/com/android/server/pm/ComputerLocked.java
@@ -48,6 +48,7 @@
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.pm.resolution.ComponentResolverApi;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
@@ -868,4 +869,28 @@
return super.getSharedUserPackages(sharedUserAppId);
}
}
+
+ @NonNull
+ @Override
+ public ComponentResolverApi getComponentResolver() {
+ synchronized (mLock) {
+ return super.getComponentResolver();
+ }
+ }
+
+ @Nullable
+ @Override
+ public PackageStateInternal getDisabledSystemPackage(@NonNull String packageName) {
+ synchronized (mLock) {
+ return super.getDisabledSystemPackage(packageName);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo getInstantAppInstallerInfo() {
+ synchronized (mLock) {
+ return super.getInstantAppInstallerInfo();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java
index 72e67da..216ad71 100644
--- a/services/core/java/com/android/server/pm/ComputerTracker.java
+++ b/services/core/java/com/android/server/pm/ComputerTracker.java
@@ -49,6 +49,7 @@
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedUserApi;
+import com.android.server.pm.resolution.ComponentResolverApi;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
@@ -1299,4 +1300,28 @@
return current.mComputer.getSharedUserPackages(sharedUserAppId);
}
}
+
+ @NonNull
+ @Override
+ public ComponentResolverApi getComponentResolver() {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getComponentResolver();
+ }
+ }
+
+ @Nullable
+ @Override
+ public PackageStateInternal getDisabledSystemPackage(@NonNull String packageName) {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getDisabledSystemPackage(packageName);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ResolveInfo getInstantAppInstallerInfo() {
+ try (ThreadComputer current = snapshot()) {
+ return current.mComputer.getInstantAppInstallerInfo();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index 5ab0c4c..2b46ed4 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -37,6 +37,8 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.BiConsumer;
@@ -51,6 +53,7 @@
mPm = pm;
}
+ @NeverCompile // Avoid size overhead of debugging code.
public void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
DumpState dumpState = new DumpState();
ArraySet<String> permissionNames = null;
@@ -402,7 +405,8 @@
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
- mPm.mComponentResolver.dumpContentProviders(pw, dumpState, packageName);
+ mPm.mComponentResolver.dumpContentProviders(mPm.snapshotComputer(), pw, dumpState,
+ packageName);
}
if (!checkin
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2ef092e..234a230 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -36,6 +36,7 @@
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
+import static android.content.pm.PackageManagerInternal.PACKAGE_SETUP_WIZARD;
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED;
@@ -454,7 +455,8 @@
KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
ksms.addScannedPackageLPw(pkg);
- mPm.mComponentResolver.addAllComponents(pkg, chatty);
+ mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage,
+ mPm.snapshotComputer());
mPm.mAppsFilter.addPackage(pkgSetting, isReplace);
mPm.addAllPackageProperties(pkg);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index c4389a7..742cc02 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -357,6 +357,20 @@
}
}
+ /**
+ * Remove all invalid dirs under app data folder.
+ * All dirs are supposed to be valid file and package names.
+ */
+ public void cleanupInvalidPackageDirs(String uuid, int userId, int flags)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.cleanupInvalidPackageDirs(uuid, userId, flags);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
int appId, String seInfo, int targetSdkVersion,
String fromCodePath) throws InstallerException {
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 79f8dc1..92d6a82 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -57,6 +57,7 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.server.pm.InstantAppResolverConnection.ConnectionException;
import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback;
+import com.android.server.pm.resolution.ComponentResolver;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -134,8 +135,9 @@
}
}
- public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
- InstantAppResolverConnection connection, InstantAppRequest requestObj) {
+ public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(@NonNull Computer computer,
+ @NonNull UserManagerService userManager, InstantAppResolverConnection connection,
+ InstantAppRequest requestObj) {
final long startTime = System.currentTimeMillis();
final String token = requestObj.token;
if (DEBUG_INSTANT) {
@@ -149,7 +151,7 @@
final List<InstantAppResolveInfo> instantAppResolveInfoList =
connection.getInstantAppResolveInfoList(buildRequestInfo(requestObj));
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
- resolveInfo = InstantAppResolver.filterInstantAppIntent(
+ resolveInfo = InstantAppResolver.filterInstantAppIntent(computer, userManager,
instantAppResolveInfoList, origIntent, requestObj.resolvedType,
requestObj.userId, origIntent.getPackage(), token,
requestObj.hostDigestPrefixSecure);
@@ -187,9 +189,10 @@
return resolveInfo;
}
- public static void doInstantAppResolutionPhaseTwo(Context context,
- InstantAppResolverConnection connection, InstantAppRequest requestObj,
- ActivityInfo instantAppInstaller, Handler callbackHandler) {
+ public static void doInstantAppResolutionPhaseTwo(Context context, @NonNull Computer computer,
+ @NonNull UserManagerService userManager, InstantAppResolverConnection connection,
+ InstantAppRequest requestObj, ActivityInfo instantAppInstaller,
+ Handler callbackHandler) {
final long startTime = System.currentTimeMillis();
final String token = requestObj.token;
if (DEBUG_INSTANT) {
@@ -205,7 +208,7 @@
final Intent failureIntent;
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
final AuxiliaryResolveInfo instantAppIntentInfo =
- InstantAppResolver.filterInstantAppIntent(
+ InstantAppResolver.filterInstantAppIntent(computer, userManager,
instantAppResolveInfoList, origIntent, null /*resolvedType*/,
0 /*userId*/, origIntent.getPackage(),
token, requestObj.hostDigestPrefixSecure);
@@ -386,7 +389,8 @@
);
}
- private static AuxiliaryResolveInfo filterInstantAppIntent(
+ private static AuxiliaryResolveInfo filterInstantAppIntent(@NonNull Computer computer,
+ @NonNull UserManagerService userManager,
List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent,
String resolvedType, int userId, String packageName, String token,
int[] hostDigestPrefixSecure) {
@@ -421,7 +425,8 @@
}
// We matched a resolve info; resolve the filters to see if anything matches completely.
List<AuxiliaryResolveInfo.AuxiliaryFilter> matchFilters = computeResolveFilters(
- origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo);
+ computer, userManager, origIntent, resolvedType, userId, packageName, token,
+ instantAppResolveInfo);
if (matchFilters != null) {
if (matchFilters.isEmpty()) {
requiresSecondPhase = true;
@@ -464,7 +469,8 @@
*
*/
private static List<AuxiliaryResolveInfo.AuxiliaryFilter> computeResolveFilters(
- Intent origIntent, String resolvedType, int userId, String packageName, String token,
+ @NonNull Computer computer, @NonNull UserManagerService userManager, Intent origIntent,
+ String resolvedType, int userId, String packageName, String token,
InstantAppResolveInfo instantAppInfo) {
if (instantAppInfo.shouldLetInstallerDecide()) {
return Collections.singletonList(
@@ -490,7 +496,7 @@
return Collections.emptyList();
}
final ComponentResolver.InstantAppIntentResolver instantAppResolver =
- new ComponentResolver.InstantAppIntentResolver();
+ new ComponentResolver.InstantAppIntentResolver(userManager);
for (int j = instantAppFilters.size() - 1; j >= 0; --j) {
final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j);
final List<IntentFilter> splitFilters = instantAppFilter.getFilters();
@@ -508,7 +514,7 @@
&& filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
continue;
}
- instantAppResolver.addFilter(
+ instantAppResolver.addFilter(computer,
new AuxiliaryResolveInfo.AuxiliaryFilter(
filter,
instantAppInfo,
@@ -518,8 +524,8 @@
}
}
List<AuxiliaryResolveInfo.AuxiliaryFilter> matchedResolveInfoList =
- instantAppResolver.queryIntent(
- origIntent, resolvedType, false /*defaultOnly*/, userId);
+ instantAppResolver.queryIntent(computer, origIntent, resolvedType,
+ false /*defaultOnly*/, userId);
if (!matchedResolveInfoList.isEmpty()) {
if (DEBUG_INSTANT) {
Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList);
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index ec71940..46e2aa3 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -313,6 +313,8 @@
}
case INSTANT_APP_RESOLUTION_PHASE_TWO: {
InstantAppResolver.doInstantAppResolutionPhaseTwo(mPm.mContext,
+ mPm.snapshotComputer(),
+ mPm.mUserManager,
mPm.mInstantAppResolverConnection,
(InstantAppRequest) msg.obj,
mPm.mInstantAppInstallerActivity,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d230004..ced1c7d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -469,16 +469,16 @@
@Nullable
final StagedSession mStagedSession;
+ /**
+ * The callback to run when pre-reboot verification has ended. Used by {@link #abandon()}
+ * to delay session clean-up until it is safe to do so.
+ */
+ @GuardedBy("mLock")
+ @Nullable
+ private Runnable mPendingAbandonCallback;
+
@VisibleForTesting
public class StagedSession implements StagingManager.StagedSession {
- /**
- * The callback to run when pre-reboot verification has ended. Used by {@link #abandon()}
- * to delay session clean-up until it is safe to do so.
- */
- @GuardedBy("mLock")
- @Nullable
- private Runnable mPendingAbandonCallback;
-
@Override
public List<StagingManager.StagedSession> getChildSessions() {
if (!params.isMultiPackage) {
@@ -575,9 +575,7 @@
@Override
public boolean isInTerminalState() {
- synchronized (mLock) {
- return mSessionApplied || mSessionFailed;
- }
+ return PackageInstallerSession.this.isInTerminalState();
}
@Override
@@ -612,48 +610,7 @@
@Override
public void abandon() {
- final Runnable r;
- synchronized (mLock) {
- assertNotChild("StagedSession#abandon");
- assertCallerIsOwnerOrRootOrSystem();
- if (isInTerminalState()) {
- // We keep the session in the database if it's in a finalized state. It will be
- // removed by PackageInstallerService when the last update time is old enough.
- // Also, in such cases cleanStageDir() has already been executed so no need to
- // do it now.
- return;
- }
- mDestroyed = true;
- r = () -> {
- assertNotLocked("abandonStaged");
- if (mCommitted.get()) {
- mStagingManager.abortCommittedSession(this);
- }
- destroy();
- dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
- maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
- "Session was abandoned because the parent session is abandoned");
- };
- if (mStageDirInUse) {
- // Pre-reboot verification is ongoing, not safe to clean up the session yet.
- mPendingAbandonCallback = r;
- mCallback.onSessionChanged(PackageInstallerSession.this);
- return;
- }
- }
- r.run();
- }
-
- /**
- * Called when pre-reboot verification has ended.
- * Now it is safe to clean up the session if {@link #abandon()} has been called previously.
- */
- private void notifyEndPreRebootVerification() {
- synchronized (mLock) {
- Preconditions.checkState(mStageDirInUse);
- mStageDirInUse = false;
- }
- dispatchPendingAbandonCallback();
+ PackageInstallerSession.this.abandon();
}
/**
@@ -669,17 +626,6 @@
Preconditions.checkArgument(!isInTerminalState());
verify();
}
-
- private void dispatchPendingAbandonCallback() {
- final Runnable callback;
- synchronized (mLock) {
- callback = mPendingAbandonCallback;
- mPendingAbandonCallback = null;
- }
- if (callback != null) {
- callback.run();
- }
- }
}
/**
@@ -1138,9 +1084,15 @@
}
}
+ private boolean isInTerminalState() {
+ synchronized (mLock) {
+ return mSessionApplied || mSessionFailed;
+ }
+ }
+
/** Returns true if a staged session has reached a final state and can be forgotten about */
public boolean isStagedAndInTerminalState() {
- return params.isStaged && mStagedSession.isInTerminalState();
+ return params.isStaged && isInTerminalState();
}
private void assertNotLocked(String cookie) {
@@ -1968,9 +1920,6 @@
private void onSessionVerificationFailure(int error, String msg) {
Slog.e(TAG, "Failed to verify session " + sessionId);
- if (isStaged()) {
- mStagedSession.notifyEndPreRebootVerification();
- }
// Dispatch message to remove session from PackageInstallerService.
dispatchSessionFinished(error, msg, null);
maybeFinishChildSessions(error, msg);
@@ -2279,6 +2228,17 @@
}
}
+ @GuardedBy("mLock")
+ private void markStageDirInUseLocked() throws PackageManagerException {
+ if (mDestroyed) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Session destroyed");
+ }
+ // Set this flag to prevent abandon() from deleting staging files when verification or
+ // installation is about to start.
+ mStageDirInUse = true;
+ }
+
private void parseApkAndExtractNativeLibraries() throws PackageManagerException {
synchronized (mLock) {
if (mStageDirInUse) {
@@ -2324,19 +2284,14 @@
private void verifyNonStaged()
throws PackageManagerException {
synchronized (mLock) {
- if (mDestroyed) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
- "Session destroyed");
- }
- // Set this flag to prevent abandon() from deleting staging files while verification is
- // in progress. For staged sessions, we will reset this flag when verification is done
- // so abandon() can take effect. For non-staged sessions, the staging files will be
- // deleted when install is completed (no matter success or not). No need to reset
- // the flag.
- mStageDirInUse = true;
+ markStageDirInUseLocked();
}
mSessionProvider.getSessionVerifier().verify(this, (error, msg) -> {
mHandler.post(() -> {
+ if (dispatchPendingAbandonCallback()) {
+ // No need to continue if abandoned
+ return;
+ }
if (error == INSTALL_SUCCEEDED) {
onVerificationComplete();
} else {
@@ -2426,7 +2381,6 @@
@WorkerThread
private void onVerificationComplete() {
if (isStaged()) {
- mStagedSession.notifyEndPreRebootVerification();
mStagingManager.commitSession(mStagedSession);
sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
return;
@@ -2444,14 +2398,11 @@
private InstallParams makeInstallParams(CompletableFuture<Void> future)
throws PackageManagerException {
synchronized (mLock) {
- if (mDestroyed) {
- throw new PackageManagerException(
- INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
- }
if (!mSealed) {
throw new PackageManagerException(
INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
}
+ markStageDirInUseLocked();
}
if (isMultiPackage()) {
@@ -3523,22 +3474,6 @@
}
}
- private void abandonNonStaged() {
- synchronized (mLock) {
- assertNotChild("abandonNonStaged");
- assertCallerIsOwnerOrRootOrSystem();
- if (mStageDirInUse) {
- if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
- return;
- }
- mDestroyed = true;
- }
- destroy();
- dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
- maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
- "Session was abandoned because the parent session is abandoned");
- }
-
private void assertNotChild(String cookie) {
if (hasParentSessionId()) {
throw new IllegalStateException(cookie + " can't be called on a child session, id="
@@ -3546,13 +3481,56 @@
}
}
+ /**
+ * Called when verification has completed. Now it is safe to clean up the session
+ * if {@link #abandon()} has been called previously.
+ *
+ * @return True if this session has been abandoned.
+ */
+ private boolean dispatchPendingAbandonCallback() {
+ final Runnable callback;
+ synchronized (mLock) {
+ Preconditions.checkState(mStageDirInUse);
+ mStageDirInUse = false;
+ callback = mPendingAbandonCallback;
+ mPendingAbandonCallback = null;
+ }
+ if (callback != null) {
+ callback.run();
+ return true;
+ }
+ return false;
+ }
+
@Override
public void abandon() {
- if (params.isStaged) {
- mStagedSession.abandon();
- } else {
- abandonNonStaged();
+ final Runnable r;
+ synchronized (mLock) {
+ assertNotChild("abandon");
+ assertCallerIsOwnerOrRootOrSystem();
+ if (isInTerminalState()) {
+ // Finalized sessions have been properly cleaned up. No need to abandon them.
+ return;
+ }
+ mDestroyed = true;
+ r = () -> {
+ assertNotLocked("abandonStaged");
+ if (isStaged() && mCommitted.get()) {
+ mStagingManager.abortCommittedSession(mStagedSession);
+ }
+ destroy();
+ dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
+ maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
+ "Session was abandoned because the parent session is abandoned");
+ };
+ if (mStageDirInUse) {
+ // Verification is ongoing, not safe to clean up the session yet.
+ mPendingAbandonCallback = r;
+ mCallback.onSessionChanged(this);
+ return;
+ }
}
+ r.run();
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 725e92a..0b7a6a7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -111,7 +111,6 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.PackageManager.PropertyLocation;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
import android.content.pm.PackagePartitions;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo;
@@ -242,6 +241,8 @@
import com.android.server.pm.pkg.mutate.PackageStateWrite;
import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.resolution.ComponentResolver;
+import com.android.server.pm.resolution.ComponentResolverApi;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationService;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
@@ -791,9 +792,6 @@
private final AtomicInteger mNextMoveId = new AtomicInteger();
final MovePackageHelper.MoveCallbacks mMoveCallbacks;
- // Cache of users who need badging.
- private final SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray();
-
/**
* Token for keys in mPendingVerification.
* Handler thread only!
@@ -912,6 +910,8 @@
final UserManagerService mUserManager;
+ final UserNeedsBadgingCache mUserNeedsBadging;
+
// Stores a list of users whose package restrictions file needs to be updated
final ArraySet<Integer> mDirtyUsers = new ArraySet<>();
@@ -992,7 +992,7 @@
public final ApplicationInfo androidApplication;
public final String appPredictionServicePackage;
public final AppsFilter appsFilter;
- public final ComponentResolver componentResolver;
+ public final ComponentResolverApi componentResolver;
public final PackageManagerService service;
public final WatchedArrayMap<String, Integer> frozenPackages;
public final SharedLibrariesRead sharedLibraries;
@@ -1081,17 +1081,10 @@
private final SnapshotStatistics mSnapshotStatistics;
/**
- * Return the live computer.
- */
- Computer liveComputer() {
- return mLiveComputer;
- }
-
- /**
* Return the cached computer. The method will rebuild the cached computer if necessary.
* The live computer will be returned if snapshots are disabled.
*/
- Computer snapshotComputer() {
+ public Computer snapshotComputer() {
if (Thread.holdsLock(mLock)) {
// If the current thread holds mLock then it may have modified state but not
// yet invalidated the snapshot. Always give the thread the live computer.
@@ -1171,8 +1164,8 @@
@Override
public void notifyPackagesReplacedReceived(String[] packages) {
- ArraySet<String> packagesToNotify =
- mComputer.getNotifyPackagesForReplacedReceived(packages);
+ Computer computer = snapshotComputer();
+ ArraySet<String> packagesToNotify = computer.getNotifyPackagesForReplacedReceived(packages);
for (int index = 0; index < packagesToNotify.size(); index++) {
notifyInstallObserver(packagesToNotify.valueAt(index));
}
@@ -1441,7 +1434,7 @@
context, lock, installer, installLock, new PackageAbiHelperImpl(),
backgroundHandler,
SYSTEM_PARTITIONS,
- (i, pm) -> new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
+ (i, pm) -> new ComponentResolver(i.getUserManagerService(), pm.mUserNeedsBadging),
(i, pm) -> PermissionManagerService.create(context,
i.getSystemConfig().getAvailableFeatures()),
(i, pm) -> new UserManagerService(context, pm,
@@ -1619,6 +1612,7 @@
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
mUserManager = injector.getUserManagerService();
+ mUserNeedsBadging = new UserNeedsBadgingCache(mUserManager);
mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
mHandler = injector.getHandler();
mSharedLibraries = injector.getSharedLibrariesImpl();
@@ -1744,6 +1738,7 @@
mTestUtilityService = LocalServices.getService(TestUtilityService.class);
LocalServices.addService(PackageManagerInternal.class, mPmInternal);
mUserManager = injector.getUserManagerService();
+ mUserNeedsBadging = new UserNeedsBadgingCache(mUserManager);
mComponentResolver = injector.getComponentResolver();
mPermissionManager = injector.getPermissionManagerServiceInternal();
mSettings = injector.getSettings();
@@ -1846,7 +1841,9 @@
mAppDataHelper);
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
mPreferredActivityHelper = new PreferredActivityHelper(this);
- mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
+ mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
+ injector.getCompatibility(), mUserManager, mDomainVerificationManager,
+ mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity);
mDexOptHelper = new DexOptHelper(this);
mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper,
mProtectedPackages);
@@ -1862,6 +1859,7 @@
registerObservers(true);
}
+ Computer computer = mLiveComputer;
// CHECKSTYLE:OFF IndentationCheck
synchronized (mInstallLock) {
// writer
@@ -1975,12 +1973,12 @@
userIds, startTime);
// Resolve the storage manager.
- mStorageManagerPackage = getStorageManagerPackageName();
+ mStorageManagerPackage = getStorageManagerPackageName(computer);
// Resolve protected action filters. Only the setup wizard is allowed to
// have a high priority filter for these actions.
- mSetupWizardPackage = getSetupWizardPackageNameImpl();
- mComponentResolver.fixProtectedFilterPriorities();
+ mSetupWizardPackage = getSetupWizardPackageNameImpl(computer);
+ mComponentResolver.fixProtectedFilterPriorities(mPmInternal.getSetupWizardPackageName());
mDefaultTextClassifierPackage = getDefaultTextClassifierPackageName();
mSystemTextClassifierPackageName = getSystemTextClassifierPackageName();
@@ -2108,13 +2106,13 @@
SystemClock.uptimeMillis());
if (!mOnlyCore) {
- mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
- mRequiredInstallerPackage = getRequiredInstallerLPr();
- mRequiredUninstallerPackage = getRequiredUninstallerLPr();
+ mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr(computer);
+ mRequiredInstallerPackage = getRequiredInstallerLPr(computer);
+ mRequiredUninstallerPackage = getRequiredUninstallerLPr(computer);
ComponentName intentFilterVerifierComponent =
- getIntentFilterVerifierComponentNameLPr();
+ getIntentFilterVerifierComponentNameLPr(computer);
ComponentName domainVerificationAgent =
- getDomainVerificationAgentComponentNameLPr();
+ getDomainVerificationAgentComponentNameLPr(computer);
DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
intentFilterVerifierComponent, domainVerificationAgent, mContext,
@@ -2137,7 +2135,7 @@
// PermissionController hosts default permission granting and role management, so it's a
// critical part of the core system.
- mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr();
+ mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(computer);
mSettings.setPermissionControllerVersion(
getPackageInfo(mRequiredPermissionControllerPackage, 0,
@@ -2170,7 +2168,8 @@
mInstantAppResolverConnection =
mInjector.getInstantAppResolverConnection(instantAppResolverComponent);
mInstantAppResolverSettingsComponent =
- getInstantAppResolverSettingsLPr(instantAppResolverComponent);
+ getInstantAppResolverSettingsLPr(computer,
+ instantAppResolverComponent);
} else {
mInstantAppResolverConnection = null;
mInstantAppResolverSettingsComponent = null;
@@ -2258,11 +2257,13 @@
"persist.pm.mock-upgrade", false /* default */);
}
- private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() {
+ @Nullable
+ private String getRequiredButNotReallyRequiredVerifierLPr(@NonNull Computer computer) {
final Intent intent = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
final List<ResolveInfo> matches =
- mResolveIntentHelper.queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
+ mResolveIntentHelper.queryIntentReceiversInternal(computer, intent,
+ PACKAGE_MIME_TYPE,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.USER_SYSTEM, Binder.getCallingUid());
if (matches.size() == 1) {
@@ -2300,12 +2301,13 @@
return servicesExtensionPackage;
}
- private @NonNull String getRequiredInstallerLPr() {
+ private @NonNull String getRequiredInstallerLPr(@NonNull Computer computer) {
final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.parse("content://com.example/foo.apk"), PACKAGE_MIME_TYPE);
- final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
+ final List<ResolveInfo> matches = computer.queryIntentActivitiesInternal(intent,
+ PACKAGE_MIME_TYPE,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.USER_SYSTEM);
if (matches.size() == 1) {
@@ -2319,14 +2321,14 @@
}
}
- private @NonNull String getRequiredUninstallerLPr() {
+ private @NonNull String getRequiredUninstallerLPr(@NonNull Computer computer) {
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.fromParts(PACKAGE_SCHEME, "foo.bar", null));
- final ResolveInfo resolveInfo = resolveIntent(intent, null,
- MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.USER_SYSTEM);
+ final ResolveInfo resolveInfo = mResolveIntentHelper.resolveIntentInternal(computer, intent,
+ null, MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ 0 /*privateResolveFlags*/, UserHandle.USER_SYSTEM, false, Binder.getCallingUid());
if (resolveInfo == null ||
mResolveActivity.name.equals(resolveInfo.getComponentInfo().name)) {
throw new RuntimeException("There must be exactly one uninstaller; found "
@@ -2335,11 +2337,11 @@
return resolveInfo.getComponentInfo().packageName;
}
- private @NonNull String getRequiredPermissionControllerLPr() {
+ private @NonNull String getRequiredPermissionControllerLPr(@NonNull Computer computer) {
final Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSIONS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
- final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+ final List<ResolveInfo> matches = computer.queryIntentActivitiesInternal(intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.USER_SYSTEM);
if (matches.size() == 1) {
@@ -2354,11 +2356,13 @@
}
}
- private @NonNull ComponentName getIntentFilterVerifierComponentNameLPr() {
+ @NonNull
+ private ComponentName getIntentFilterVerifierComponentNameLPr(@NonNull Computer computer) {
final Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
final List<ResolveInfo> matches =
- mResolveIntentHelper.queryIntentReceiversInternal(intent, PACKAGE_MIME_TYPE,
+ mResolveIntentHelper.queryIntentReceiversInternal(computer, intent,
+ PACKAGE_MIME_TYPE,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.USER_SYSTEM, Binder.getCallingUid());
ResolveInfo best = null;
@@ -2384,10 +2388,10 @@
}
@Nullable
- private ComponentName getDomainVerificationAgentComponentNameLPr() {
+ private ComponentName getDomainVerificationAgentComponentNameLPr(@NonNull Computer computer) {
Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
List<ResolveInfo> matches =
- mResolveIntentHelper.queryIntentReceiversInternal(intent, null,
+ mResolveIntentHelper.queryIntentReceiversInternal(computer, intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.USER_SYSTEM, Binder.getCallingUid());
ResolveInfo best = null;
@@ -2496,13 +2500,14 @@
| MATCH_DIRECT_BOOT_UNAWARE
| Intent.FLAG_IGNORE_EPHEMERAL
| (mIsEngBuild ? 0 : MATCH_SYSTEM_ONLY);
+ final Computer computer = snapshotComputer();
final Intent intent = new Intent();
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
List<ResolveInfo> matches = null;
for (String action : orderedActions) {
intent.setAction(action);
- matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
+ matches = computer.queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE,
resolveFlags, UserHandle.USER_SYSTEM);
if (matches.isEmpty()) {
if (DEBUG_INSTANT) {
@@ -2532,14 +2537,14 @@
}
}
- private @Nullable ComponentName getInstantAppResolverSettingsLPr(
+ private @Nullable ComponentName getInstantAppResolverSettingsLPr(@NonNull Computer computer,
@NonNull ComponentName resolver) {
final Intent intent = new Intent(Intent.ACTION_INSTANT_APP_RESOLVER_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.setPackage(resolver.getPackageName());
final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
- List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, resolveFlags,
- UserHandle.USER_SYSTEM);
+ List<ResolveInfo> matches = computer.queryIntentActivitiesInternal(intent, null,
+ resolveFlags, UserHandle.USER_SYSTEM);
if (matches.isEmpty()) {
return null;
}
@@ -2942,27 +2947,6 @@
}
/**
- * Update given flags when being used to request {@link PackageInfo}.
- */
- private long updateFlagsForPackage(long flags, int userId) {
- return mComputer.updateFlagsForPackage(flags, userId);
- }
-
- /**
- * Update given flags when being used to request {@link ApplicationInfo}.
- */
- private long updateFlagsForApplication(long flags, int userId) {
- return mComputer.updateFlagsForApplication(flags, userId);
- }
-
- /**
- * Update given flags when being used to request {@link ComponentInfo}.
- */
- private long updateFlagsForComponent(long flags, int userId) {
- return mComputer.updateFlagsForComponent(flags, userId);
- }
-
- /**
* Update given flags when being used to request {@link ResolveInfo}.
* <p>Instant apps are resolved specially, depending upon context. Minimally,
* {@code}flags{@code} must have the {@link PackageManager#MATCH_INSTANT}
@@ -3314,8 +3298,8 @@
@Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags, int userId) {
- return mResolveIntentHelper.resolveIntentInternal(intent, resolvedType, flags,
- 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
+ return mResolveIntentHelper.resolveIntentInternal(snapshotComputer(), intent, resolvedType,
+ flags, 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
}
@Override
@@ -3414,8 +3398,8 @@
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
- return new ParceledListSlice<>(
- queryIntentActivitiesInternal(intent, resolvedType, flags, userId));
+ return new ParceledListSlice<>(snapshotComputer().queryIntentActivitiesInternal(intent,
+ resolvedType, flags, userId));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -3429,68 +3413,28 @@
return mComputer.getInstantAppPackageName(callingUid);
}
- @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
- String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
- return mComputer.queryIntentActivitiesInternal(intent,
- resolvedType, flags, userId);
- }
-
- @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
- String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- @PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId,
- boolean resolveForStart, boolean allowDynamicSplits) {
- return mComputer.queryIntentActivitiesInternal(intent,
- resolvedType, flags, privateResolveFlags,
- filterCallingUid, userId, resolveForStart, allowDynamicSplits);
- }
-
- private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
- String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId,
- int parentUserId) {
- return mComputer.getCrossProfileDomainPreferredLpr(intent,
- resolvedType, flags, sourceUserId, parentUserId);
- }
-
- /**
- * Filters out ephemeral activities.
- * <p>When resolving for an ephemeral app, only activities that 1) are defined in the
- * ephemeral app or 2) marked with {@code visibleToEphemeral} are returned.
- *
- * @param resolveInfos The pre-filtered list of resolved activities
- * @param ephemeralPkgName The ephemeral package name. If {@code null}, no filtering
- * is performed.
- * @param intent
- * @return A filtered list of resolved activities.
- */
- List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
- String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
- boolean resolveForStart, int userId, Intent intent) {
- return mComputer.applyPostResolutionFilter(resolveInfos,
- ephemeralPkgName, allowDynamicSplits, filterCallingUid,
- resolveForStart, userId, intent);
- }
-
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
Intent[] specifics, String[] specificTypes, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal(
- caller, specifics, specificTypes, intent, resolvedType, flags, userId));
+ snapshotComputer(), caller, specifics, specificTypes, intent, resolvedType, flags,
+ userId));
}
@Override
public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
- return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal(intent,
- resolvedType, flags, userId, Binder.getCallingUid()));
+ return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal(
+ snapshotComputer(), intent, resolvedType, flags, userId, Binder.getCallingUid()));
}
@Override
public ResolveInfo resolveService(Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags, int userId) {
final int callingUid = Binder.getCallingUid();
- return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
- callingUid);
+ return mResolveIntentHelper.resolveServiceInternal(snapshotComputer(), intent, resolvedType,
+ flags, userId, callingUid);
}
@Override
@@ -3513,7 +3457,7 @@
public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal(
- intent, resolvedType, flags, userId));
+ snapshotComputer(), intent, resolvedType, flags, userId));
}
@Override
@@ -3907,15 +3851,9 @@
synchronized (mLock) {
mPackageUsage.writeNow(mSettings.getPackagesLocked());
- // This is the last chance to write out pending restriction settings
- if (mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
- mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS);
- synchronized (mDirtyUsers) {
- for (int userId : mDirtyUsers) {
- mSettings.writePackageRestrictionsLPr(userId);
- }
- mDirtyUsers.clear();
- }
+ if (mHandler.hasMessages(WRITE_SETTINGS)) {
+ mHandler.removeMessages(WRITE_SETTINGS);
+ writeSettings();
}
}
}
@@ -5409,7 +5347,7 @@
}
}
}
- resolver.addFilter(newFilter);
+ resolver.addFilter(snapshotComputer(), newFilter);
}
scheduleWritePackageRestrictions(sourceUserId);
}
@@ -5498,11 +5436,11 @@
mPreferredActivityHelper.setHomeActivity(comp, userId);
}
- private @Nullable String getSetupWizardPackageNameImpl() {
+ private @Nullable String getSetupWizardPackageNameImpl(@NonNull Computer computer) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
- final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+ final List<ResolveInfo> matches = computer.queryIntentActivitiesInternal(intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
| MATCH_DISABLED_COMPONENTS,
UserHandle.myUserId());
@@ -5515,10 +5453,10 @@
}
}
- private @Nullable String getStorageManagerPackageName() {
+ private @Nullable String getStorageManagerPackageName(@NonNull Computer computer) {
final Intent intent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
- final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+ final List<ResolveInfo> matches = computer.queryIntentActivitiesInternal(intent, null,
MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
| MATCH_DISABLED_COMPONENTS,
UserHandle.myUserId());
@@ -6784,21 +6722,7 @@
}
boolean userNeedsBadging(int userId) {
- int index = mUserNeedsBadging.indexOfKey(userId);
- if (index < 0) {
- final UserInfo userInfo;
- final long token = Binder.clearCallingIdentity();
- try {
- userInfo = mUserManager.getUserInfo(userId);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- final boolean b;
- b = userInfo != null && userInfo.isManagedProfile();
- mUserNeedsBadging.put(userId, b);
- return b;
- }
- return mUserNeedsBadging.valueAt(index);
+ return mUserNeedsBadging.get(userId);
}
@Nullable
@@ -7034,16 +6958,14 @@
}
@Override
- public PackageSetting getDisabledSystemPackage(@NonNull String packageName) {
- synchronized (mLock) {
- return mSettings.getDisabledSystemPkgLPr(packageName);
- }
+ public PackageStateInternal getDisabledSystemPackage(@NonNull String packageName) {
+ return snapshotComputer().getDisabledSystemPackage(packageName);
}
@Override
public @Nullable
String getDisabledSystemPackageName(@NonNull String packageName) {
- PackageSetting disabledPkgSetting = getDisabledSystemPackage(
+ PackageStateInternal disabledPkgSetting = getDisabledSystemPackage(
packageName);
AndroidPackage disabledPkg = disabledPkgSetting == null
? null : disabledPkgSetting.getPkg();
@@ -7183,9 +7105,8 @@
public List<ResolveInfo> queryIntentActivities(
Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
int filterCallingUid, int userId) {
- return PackageManagerService.this
- .queryIntentActivitiesInternal(intent, resolvedType, flags, 0, filterCallingUid,
- userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
+ return snapshotComputer().queryIntentActivitiesInternal(intent, resolvedType, flags,
+ userId);
}
@Override
@@ -7193,7 +7114,7 @@
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
int filterCallingUid, int userId) {
return PackageManagerService.this.mResolveIntentHelper.queryIntentReceiversInternal(
- intent, resolvedType, flags, userId, filterCallingUid);
+ snapshotComputer(), intent, resolvedType, flags, userId, filterCallingUid);
}
@Override
@@ -7436,7 +7357,7 @@
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
boolean resolveForStart, int filterCallingUid) {
- return mResolveIntentHelper.resolveIntentInternal(
+ return mResolveIntentHelper.resolveIntentInternal(snapshotComputer(),
intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart,
filterCallingUid);
}
@@ -7444,8 +7365,8 @@
@Override
public ResolveInfo resolveService(Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid) {
- return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
- callingUid);
+ return mResolveIntentHelper.resolveServiceInternal(snapshotComputer(), intent,
+ resolvedType, flags, userId, callingUid);
}
@Override
@@ -7900,6 +7821,12 @@
@NonNull Consumer<PackageStateMutator> consumer) {
return PackageManagerService.this.commitPackageStateMutation(state, consumer);
}
+
+ @NonNull
+ @Override
+ public Computer snapshot() {
+ return snapshotComputer();
+ }
}
private boolean setEnabledOverlayPackages(@UserIdInt int userId,
@@ -8346,16 +8273,6 @@
}
}
- private void applyMimeGroupChanges(String packageName, String mimeGroup) {
- if (mComponentResolver.updateMimeGroup(packageName, mimeGroup)) {
- Binder.withCleanCallingIdentity(() ->
- mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
- UserHandle.USER_ALL));
- }
-
- mPmInternal.writeSettings(false);
- }
-
@Override
public void setMimeGroup(String packageName, String mimeGroup, List<String> mimeTypes) {
enforceOwnerRights(packageName, Binder.getCallingUid());
@@ -8375,7 +8292,13 @@
commitPackageStateMutation(null, packageName, packageStateWrite -> {
packageStateWrite.setMimeGroup(mimeGroup, mimeTypesSet);
});
- applyMimeGroupChanges(packageName, mimeGroup);
+ if (mComponentResolver.updateMimeGroup(snapshotComputer(), packageName, mimeGroup)) {
+ Binder.withCleanCallingIdentity(() ->
+ mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
+ UserHandle.USER_ALL));
+ }
+
+ scheduleWriteSettings();
}
@Override
@@ -8607,8 +8530,8 @@
@Override
public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
String featureId, int userId) throws RemoteException {
- return mResolveIntentHelper.getLaunchIntentSenderForPackage(packageName, callingPackage,
- featureId, userId);
+ return mResolveIntentHelper.getLaunchIntentSenderForPackage(snapshotComputer(),
+ packageName, callingPackage, featureId, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 05bb01e..a02237f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -34,6 +34,7 @@
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.resolution.ComponentResolver;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import java.util.List;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 2f56bfe..a9471cf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -93,6 +93,7 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.resolution.ComponentResolverApi;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import dalvik.system.VMRuntime;
@@ -1064,7 +1065,7 @@
// Static to give access to ComputeEngine
public static void applyEnforceIntentFilterMatching(
- PlatformCompat compat, ComponentResolver resolver,
+ PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
// Do not enforce filter matching when the caller is system or root.
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index bb82e6a..8c49baf 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -140,8 +140,8 @@
return false;
}
final Intent intent = mPm.getHomeIntent();
- final List<ResolveInfo> resolveInfos = mPm.queryIntentActivitiesInternal(intent, null,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+ final List<ResolveInfo> resolveInfos = mPm.snapshotComputer().queryIntentActivitiesInternal(
+ intent, null, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
intent, null, 0, resolveInfos, true, false, false, userId);
final String packageName = preferredResolveInfo != null
@@ -209,7 +209,8 @@
if (removeExisting && existing != null) {
Settings.removeFilters(pir, filter, existing);
}
- pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
+ pir.addFilter(mPm.snapshotComputer(),
+ new PreferredActivity(filter, match, set, activity, always));
mPm.scheduleWritePackageRestrictions(userId);
}
if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(userId))) {
@@ -394,6 +395,7 @@
}
synchronized (mPm.mLock) {
mPm.mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
+ mPm.snapshotComputer(),
new PersistentPreferredActivity(filter, activity, true));
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -672,8 +674,8 @@
0, userId, callingUid, false /*includeInstantApps*/,
mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
0));
- final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
- flags, userId);
+ final List<ResolveInfo> query = mPm.snapshotComputer().queryIntentActivitiesInternal(intent,
+ resolvedType, flags, userId);
synchronized (mPm.mLock) {
return mPm.findPersistentPreferredActivityLP(intent, resolvedType, flags, query, false,
userId);
@@ -699,8 +701,8 @@
filter.dump(new PrintStreamPrinter(System.out), " ");
}
intent.setComponent(null);
- final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
- flags, userId);
+ final List<ResolveInfo> query = mPm.snapshotComputer().queryIntentActivitiesInternal(intent,
+ resolvedType, flags, userId);
// Find any earlier preferred or last chosen entries and nuke them
findPreferredActivityNotLocked(
intent, resolvedType, flags, query, false, true, false, userId);
@@ -715,8 +717,8 @@
}
final int userId = UserHandle.getCallingUserId();
if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
- final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
- flags, userId);
+ final List<ResolveInfo> query = mPm.snapshotComputer().queryIntentActivitiesInternal(intent,
+ resolvedType, flags, userId);
return findPreferredActivityNotLocked(
intent, resolvedType, flags, query, false, false, false, userId);
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 2aff90f..25356a4 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -27,6 +27,8 @@
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
@@ -50,23 +52,52 @@
import com.android.internal.app.ResolverActivity;
import com.android.internal.util.ArrayUtils;
+import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.resolution.ComponentResolverApi;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.function.Supplier;
final class ResolveIntentHelper {
- private final PackageManagerService mPm;
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final PlatformCompat mPlatformCompat;
+ @NonNull
+ private final UserManagerService mUserManager;
+ @NonNull
private final PreferredActivityHelper mPreferredActivityHelper;
+ @NonNull
+ private final DomainVerificationManagerInternal mDomainVerificationManager;
+ @NonNull
+ private final UserNeedsBadgingCache mUserNeedsBadging;
+ @NonNull
+ private final Supplier<ResolveInfo> mResolveInfoSupplier;
+ @NonNull
+ private final Supplier<ActivityInfo> mInstantAppInstallerActivitySupplier;
- // TODO(b/198166813): remove PMS dependency
- ResolveIntentHelper(PackageManagerService pm, PreferredActivityHelper preferredActivityHelper) {
- mPm = pm;
+ ResolveIntentHelper(@NonNull Context context,
+ @NonNull PreferredActivityHelper preferredActivityHelper,
+ @NonNull PlatformCompat platformCompat, @NonNull UserManagerService userManager,
+ @NonNull DomainVerificationManagerInternal domainVerificationManager,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache,
+ @NonNull Supplier<ResolveInfo> resolveInfoSupplier,
+ @NonNull Supplier<ActivityInfo> instantAppInstallerActivitySupplier) {
+ mContext = context;
mPreferredActivityHelper = preferredActivityHelper;
+ mPlatformCompat = platformCompat;
+ mUserManager = userManager;
+ mDomainVerificationManager = domainVerificationManager;
+ mUserNeedsBadging = userNeedsBadgingCache;
+ mResolveInfoSupplier = resolveInfoSupplier;
+ mInstantAppInstallerActivitySupplier = instantAppInstallerActivitySupplier;
}
/**
@@ -74,35 +105,33 @@
* However, if {@code resolveForStart} is {@code true}, all instant apps are visible
* since we need to allow the system to start any installed application.
*/
- public ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
+ public ResolveInfo resolveIntentInternal(Computer computer, Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
boolean resolveForStart, int filterCallingUid) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
- if (!mPm.mUserManager.exists(userId)) return null;
+ if (!mUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
- flags = mPm.updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
- mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ flags = computer.updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
+ computer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
resolvedType, flags));
- mPm.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+ computer.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "resolve intent");
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
- final List<ResolveInfo> query = mPm.queryIntentActivitiesInternal(intent, resolvedType,
- flags, privateResolveFlags, filterCallingUid, userId, resolveForStart,
- true /*allowDynamicSplits*/);
+ final List<ResolveInfo> query = computer.queryIntentActivitiesInternal(intent,
+ resolvedType, flags, privateResolveFlags, filterCallingUid, userId,
+ resolveForStart, true /*allowDynamicSplits*/);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
final boolean queryMayBeFiltered =
UserHandle.getAppId(filterCallingUid) >= Process.FIRST_APPLICATION_UID
&& !resolveForStart;
- final ResolveInfo bestChoice =
- chooseBestActivity(
- intent, resolvedType, flags, privateResolveFlags, query, userId,
- queryMayBeFiltered);
+ final ResolveInfo bestChoice = chooseBestActivity(computer, intent, resolvedType, flags,
+ privateResolveFlags, query, userId, queryMayBeFiltered);
final boolean nonBrowserOnly =
(privateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
if (nonBrowserOnly && bestChoice != null && bestChoice.handleAllWebDataURI) {
@@ -114,7 +143,7 @@
}
}
- private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
+ private ResolveInfo chooseBestActivity(Computer computer, Intent intent, String resolvedType,
@PackageManager.ResolveInfoFlagsBits long flags,
@PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
List<ResolveInfo> query, int userId, boolean queryMayBeFiltered) {
@@ -156,9 +185,10 @@
// If we have an ephemeral app, use it
if (ri.activityInfo.applicationInfo.isInstantApp()) {
final String packageName = ri.activityInfo.packageName;
- final PackageStateInternal ps = mPm.getPackageStateInternal(packageName);
+ final PackageStateInternal ps =
+ computer.getPackageStateInternal(packageName);
if (ps != null && PackageManagerServiceUtils.hasAnyDomainApproval(
- mPm.mDomainVerificationManager, ps, intent, flags, userId)) {
+ mDomainVerificationManager, ps, intent, flags, userId)) {
return ri;
}
}
@@ -167,7 +197,7 @@
& PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
return null;
}
- ri = new ResolveInfo(mPm.getResolveInfo());
+ ri = new ResolveInfo(mResolveInfoSupplier.get());
// if all resolve options are browsers, mark the resolver's info as if it were
// also a browser.
ri.handleAllWebDataURI = browserCount == n;
@@ -184,7 +214,7 @@
if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
ri.resolvePackageName = intentPackage;
- if (mPm.userNeedsBadging(userId)) {
+ if (mUserNeedsBadging.get(userId)) {
ri.noResourceId = true;
} else {
ri.icon = appi.icon;
@@ -225,13 +255,14 @@
return true;
}
- public IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage,
- String featureId, int userId) throws RemoteException {
+ public IntentSender getLaunchIntentSenderForPackage(@NonNull Computer computer,
+ String packageName, String callingPackage, String featureId, int userId)
+ throws RemoteException {
Objects.requireNonNull(packageName);
final int callingUid = Binder.getCallingUid();
- mPm.enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+ computer.enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
false /* checkShell */, "get launch intent sender for package");
- final int packageUid = mPm.getPackageUid(callingPackage, 0 /* flags */, userId);
+ final int packageUid = computer.getPackageUid(callingPackage, 0 /* flags */, userId);
if (!UserHandle.isSameApp(callingUid, packageUid)) {
throw new SecurityException("getLaunchIntentSenderForPackage() from calling uid: "
+ callingUid + " does not own package: " + callingPackage);
@@ -242,17 +273,17 @@
final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
- String resolvedType = intentToResolve.resolveTypeIfNeeded(
- mPm.mContext.getContentResolver());
- List<ResolveInfo> ris = mPm.queryIntentActivitiesInternal(intentToResolve, resolvedType,
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ String resolvedType = intentToResolve.resolveTypeIfNeeded(contentResolver);
+ List<ResolveInfo> ris = computer.queryIntentActivitiesInternal(intentToResolve, resolvedType,
0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
true /* resolveForStart */, false /* allowDynamicSplits */);
if (ris == null || ris.size() <= 0) {
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
- resolvedType = intentToResolve.resolveTypeIfNeeded(mPm.mContext.getContentResolver());
- ris = mPm.queryIntentActivitiesInternal(intentToResolve, resolvedType,
+ resolvedType = intentToResolve.resolveTypeIfNeeded(contentResolver);
+ ris = computer.queryIntentActivitiesInternal(intentToResolve, resolvedType,
0 /* flags */, 0 /* privateResolveFlags */, callingUid, userId,
true /* resolveForStart */, false /* allowDynamicSplits */);
}
@@ -277,17 +308,17 @@
// In this method, we have to know the actual calling UID, but in some cases Binder's
// call identity is removed, so the UID has to be passed in explicitly.
- public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
+ public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
int filterCallingUid) {
- if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
- mPm.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
+ if (!mUserManager.exists(userId)) return Collections.emptyList();
+ computer.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "query intent receivers");
- final String instantAppPkgName = mPm.getInstantAppPackageName(filterCallingUid);
- flags = mPm.updateFlagsForResolve(
- flags, userId, filterCallingUid, false /*includeInstantApps*/,
- mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
+ final String instantAppPkgName = computer.getInstantAppPackageName(filterCallingUid);
+ flags = computer.updateFlagsForResolve(flags, userId, filterCallingUid,
+ false /*includeInstantApps*/,
+ computer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ resolvedType, flags));
Intent originalIntent = null;
ComponentName comp = intent.getComponent();
if (comp == null) {
@@ -297,9 +328,10 @@
comp = intent.getComponent();
}
}
+ final ComponentResolverApi componentResolver = computer.getComponentResolver();
List<ResolveInfo> list = Collections.emptyList();
if (comp != null) {
- final ActivityInfo ai = mPm.getReceiverInfo(comp, flags, userId);
+ final ActivityInfo ai = computer.getReceiverInfo(comp, flags, userId);
if (ai != null) {
// When specifying an explicit component, we prevent the activity from being
// used when either 1) the calling package is normal and the activity is within
@@ -335,28 +367,25 @@
list = new ArrayList<>(1);
list.add(ri);
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mPm.mInjector.getCompatibility(), mPm.mComponentResolver,
- list, true, intent, resolvedType, filterCallingUid);
+ mPlatformCompat, componentResolver, list, true, intent,
+ resolvedType, filterCallingUid);
}
}
} else {
- // reader
- synchronized (mPm.mLock) {
- String pkgName = intent.getPackage();
- if (pkgName == null) {
- final List<ResolveInfo> result = mPm.mComponentResolver.queryReceivers(
- intent, resolvedType, flags, userId);
- if (result != null) {
- list = result;
- }
+ String pkgName = intent.getPackage();
+ if (pkgName == null) {
+ final List<ResolveInfo> result = componentResolver
+ .queryReceivers(computer, intent, resolvedType, flags, userId);
+ if (result != null) {
+ list = result;
}
- final AndroidPackage pkg = mPm.mPackages.get(pkgName);
- if (pkg != null) {
- final List<ResolveInfo> result = mPm.mComponentResolver.queryReceivers(
- intent, resolvedType, flags, pkg.getReceivers(), userId);
- if (result != null) {
- list = result;
- }
+ }
+ final AndroidPackage pkg = computer.getPackage(pkgName);
+ if (pkg != null) {
+ final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
+ intent, resolvedType, flags, pkg.getReceivers(), userId);
+ if (result != null) {
+ list = result;
}
}
}
@@ -364,21 +393,22 @@
if (originalIntent != null) {
// We also have to ensure all components match the original intent
PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
- mPm.mInjector.getCompatibility(), mPm.mComponentResolver,
+ mPlatformCompat, componentResolver,
list, true, originalIntent, resolvedType, filterCallingUid);
}
- return mPm.applyPostResolutionFilter(
- list, instantAppPkgName, false, filterCallingUid, false, userId, intent);
+ return computer.applyPostResolutionFilter(list, instantAppPkgName, false, filterCallingUid,
+ false, userId, intent);
}
- public ResolveInfo resolveServiceInternal(Intent intent, String resolvedType,
- @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid) {
- if (!mPm.mUserManager.exists(userId)) return null;
- flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+ public ResolveInfo resolveServiceInternal(@NonNull Computer computer, Intent intent,
+ String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
+ int callingUid) {
+ if (!mUserManager.exists(userId)) return null;
+ flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
- List<ResolveInfo> query = mPm.queryIntentServicesInternal(
+ List<ResolveInfo> query = computer.queryIntentServicesInternal(
intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);
if (query != null) {
if (query.size() >= 1) {
@@ -391,12 +421,12 @@
}
public @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
- Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
- int userId) {
- if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+ @NonNull Computer computer, Intent intent, String resolvedType,
+ @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
+ if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
- final String instantAppPkgName = mPm.getInstantAppPackageName(callingUid);
- flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
+ final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);
+ flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
ComponentName comp = intent.getComponent();
if (comp == null) {
@@ -407,7 +437,7 @@
}
if (comp != null) {
final List<ResolveInfo> list = new ArrayList<>(1);
- final ProviderInfo pi = mPm.getProviderInfo(comp, flags, userId);
+ final ProviderInfo pi = computer.getProviderInfo(comp, flags, userId);
if (pi != null) {
// When specifying an explicit component, we prevent the provider from being
// used when either 1) the provider is in an instant application and the
@@ -432,8 +462,8 @@
|| (matchVisibleToInstantAppOnly && isCallerInstantApp
&& isTargetHiddenFromInstantApp));
final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
- && mPm.shouldFilterApplication(
- mPm.getPackageStateInternal(pi.applicationInfo.packageName,
+ && computer.shouldFilterApplication(
+ computer.getPackageStateInternal(pi.applicationInfo.packageName,
Process.SYSTEM_UID), callingUid, userId);
if (!blockResolution && !blockNormalResolution) {
final ResolveInfo ri = new ResolveInfo();
@@ -444,46 +474,40 @@
return list;
}
- // reader
- synchronized (mPm.mLock) {
- String pkgName = intent.getPackage();
- if (pkgName == null) {
- final List<ResolveInfo> resolveInfos = mPm.mComponentResolver.queryProviders(intent,
- resolvedType, flags, userId);
- if (resolveInfos == null) {
- return Collections.emptyList();
- }
- return applyPostContentProviderResolutionFilter(
- resolveInfos, instantAppPkgName, userId, callingUid);
+ final ComponentResolverApi componentResolver = computer.getComponentResolver();
+ String pkgName = intent.getPackage();
+ if (pkgName == null) {
+ final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
+ intent, resolvedType, flags, userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
}
- final AndroidPackage pkg = mPm.mPackages.get(pkgName);
- if (pkg != null) {
- final List<ResolveInfo> resolveInfos = mPm.mComponentResolver.queryProviders(intent,
- resolvedType, flags,
- pkg.getProviders(), userId);
- if (resolveInfos == null) {
- return Collections.emptyList();
- }
- return applyPostContentProviderResolutionFilter(
- resolveInfos, instantAppPkgName, userId, callingUid);
- }
- return Collections.emptyList();
+ return applyPostContentProviderResolutionFilter(computer, resolveInfos,
+ instantAppPkgName, userId, callingUid);
}
+ final AndroidPackage pkg = computer.getPackage(pkgName);
+ if (pkg != null) {
+ final List<ResolveInfo> resolveInfos = componentResolver.queryProviders(computer,
+ intent, resolvedType, flags, pkg.getProviders(), userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
+ return applyPostContentProviderResolutionFilter(computer, resolveInfos,
+ instantAppPkgName, userId, callingUid);
+ }
+ return Collections.emptyList();
}
- private List<ResolveInfo> applyPostContentProviderResolutionFilter(
+ private List<ResolveInfo> applyPostContentProviderResolutionFilter(@NonNull Computer computer,
List<ResolveInfo> resolveInfos, String instantAppPkgName,
@UserIdInt int userId, int callingUid) {
for (int i = resolveInfos.size() - 1; i >= 0; i--) {
final ResolveInfo info = resolveInfos.get(i);
if (instantAppPkgName == null) {
- SettingBase callingSetting =
- mPm.mSettings.getSettingLPr(UserHandle.getAppId(callingUid));
- PackageStateInternal resolvedSetting =
- mPm.getPackageStateInternal(info.providerInfo.packageName, 0);
- if (!mPm.mAppsFilter.shouldFilterApplication(
- callingUid, callingSetting, resolvedSetting, userId)) {
+ PackageStateInternal resolvedSetting = computer.getPackageStateInternal(
+ info.providerInfo.packageName, 0);
+ if (!computer.shouldFilterApplication(resolvedSetting, callingUid, userId)) {
continue;
}
}
@@ -494,7 +518,7 @@
if (info.providerInfo.splitName != null
&& !ArrayUtils.contains(info.providerInfo.applicationInfo.splitNames,
info.providerInfo.splitName)) {
- if (mPm.mInstantAppInstallerActivity == null) {
+ if (mInstantAppInstallerActivitySupplier.get() == null) {
if (DEBUG_INSTANT) {
Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
}
@@ -507,7 +531,7 @@
Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
}
final ResolveInfo installerInfo = new ResolveInfo(
- mPm.getInstantAppInstallerInfo());
+ computer.getInstantAppInstallerInfo());
installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
null /*failureActivity*/,
info.providerInfo.packageName,
@@ -531,20 +555,21 @@
return resolveInfos;
}
- public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller,
- Intent[] specifics, String[] specificTypes, Intent intent,
+ public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(Computer computer,
+ ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent,
String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
- if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
+ if (!mUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
- flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
- mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- mPm.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
+ flags = computer.updateFlagsForResolve(flags, userId, callingUid,
+ false /*includeInstantApps*/,
+ computer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ resolvedType, flags));
+ computer.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
false /*checkShell*/, "query intent activity options");
final String resultsAction = intent.getAction();
- final List<ResolveInfo> results = mPm.queryIntentActivitiesInternal(intent, resolvedType,
- flags | PackageManager.GET_RESOLVED_FILTER, userId);
+ final List<ResolveInfo> results = computer.queryIntentActivitiesInternal(intent,
+ resolvedType, flags | PackageManager.GET_RESOLVED_FILTER, userId);
if (DEBUG_INTENT_MATCHING) {
Log.v(TAG, "Query " + intent + ": " + results);
@@ -584,21 +609,20 @@
ComponentName comp = sintent.getComponent();
if (comp == null) {
- ri = mPm.resolveIntent(
- sintent,
- specificTypes != null ? specificTypes[i] : null,
- flags, userId);
+ ri = resolveIntentInternal(computer, sintent,
+ specificTypes != null ? specificTypes[i] : null, flags,
+ 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
if (ri == null) {
continue;
}
- if (ri == mPm.getResolveInfo()) {
+ if (ri == mResolveInfoSupplier.get()) {
// ACK! Must do something better with this.
}
ai = ri.activityInfo;
comp = new ComponentName(ai.applicationInfo.packageName,
ai.name);
} else {
- ai = mPm.getActivityInfo(comp, flags, userId);
+ ai = computer.getActivityInfo(comp, flags, userId);
if (ai == null) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 936da4a..2ad35b7 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -120,6 +120,7 @@
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedProcess;
import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.resolution.ComponentResolver;
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.pm.verify.domain.DomainVerificationPersistence;
@@ -137,6 +138,8 @@
import com.android.server.utils.WatchedSparseIntArray;
import com.android.server.utils.Watcher;
+import dalvik.annotation.optimization.NeverCompile;
+
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -1549,7 +1552,7 @@
if (pa.mPref.getParseError() == null) {
final PreferredIntentResolver resolver = editPreferredActivitiesLPw(userId);
if (resolver.shouldAddPreferredActivity(pa)) {
- resolver.addFilter(pa);
+ resolver.addFilter(null, pa);
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -1577,7 +1580,7 @@
String tagName = parser.getName();
if (tagName.equals(TAG_ITEM)) {
PersistentPreferredActivity ppa = new PersistentPreferredActivity(parser);
- editPersistentPreferredActivitiesLPw(userId).addFilter(ppa);
+ editPersistentPreferredActivitiesLPw(userId).addFilter(null, ppa);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <" + TAG_PERSISTENT_PREFERRED_ACTIVITIES + ">: "
@@ -1599,7 +1602,7 @@
final String tagName = parser.getName();
if (tagName.equals(TAG_ITEM)) {
CrossProfileIntentFilter cpif = new CrossProfileIntentFilter(parser);
- editCrossProfileIntentResolverLPw(userId).addFilter(cpif);
+ editCrossProfileIntentResolverLPw(userId).addFilter(null, cpif);
} else {
String msg = "Unknown element under " + TAG_CROSS_PROFILE_INTENT_FILTERS + ": " +
tagName;
@@ -1866,8 +1869,7 @@
.build();
}
if (suspended && suspendParamsMap == null) {
- final SuspendParams suspendParams =
- SuspendParams.getInstanceOrNull(
+ final SuspendParams suspendParams = new SuspendParams(
oldSuspendDialogInfo,
suspendedAppExtras,
suspendedLauncherExtras);
@@ -3515,7 +3517,7 @@
removeFilters(pir, filter, existing);
}
PreferredActivity pa = new PreferredActivity(filter, systemMatch, set, cn, true);
- pir.addFilter(pa);
+ pir.addFilter(null, pa);
} else if (haveNonSys == null) {
StringBuilder sb = new StringBuilder();
sb.append("No component ");
@@ -4518,6 +4520,7 @@
pw.decreaseIndent();
}
+ @NeverCompile // Avoid size overhead of debugging code.
void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag,
ArraySet<String> permissionNames, PackageSetting ps,
LegacyPermissionState permissionsState, SimpleDateFormat sdf, Date date,
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 85d1367..bd1c9c7 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -112,7 +112,7 @@
}
final SuspendParams newSuspendParams =
- SuspendParams.getInstanceOrNull(dialogInfo, appExtras, launcherExtras);
+ new SuspendParams(dialogInfo, appExtras, launcherExtras);
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
@@ -148,13 +148,12 @@
final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
packageState.getUserStateOrDefault(userId).getSuspendParams();
- final SuspendParams suspendParams = suspendParamsMap == null
- ? null : suspendParamsMap.get(packageName);
- boolean hasSuspension = suspendParams != null;
if (suspended) {
- if (hasSuspension) {
+ if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) {
+ final SuspendParams suspendParams = suspendParamsMap.get(packageName);
// Skip if there's no changes
- if (Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
+ if (suspendParams != null
+ && Objects.equals(suspendParams.getDialogInfo(), dialogInfo)
&& Objects.equals(suspendParams.getAppExtras(), appExtras)
&& Objects.equals(suspendParams.getLauncherExtras(),
launcherExtras)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5c4d011..b7c55c5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -36,6 +36,7 @@
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
import android.app.PendingIntent;
+import android.app.StatsManager;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
@@ -98,6 +99,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.StatsEvent;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.TypedXmlPullParser;
@@ -608,6 +610,8 @@
if (mUms.mPm.isDeviceUpgrading()) {
mUms.cleanupPreCreatedUsers();
}
+
+ mUms.registerStatsCallbacks();
}
}
@@ -4279,6 +4283,56 @@
: FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE);
}
+ /** Register callbacks for statsd pulled atoms. */
+ private void registerStatsCallbacks() {
+ final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.USER_INFO,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ this::onPullAtom);
+ }
+
+ /** Writes a UserInfo pulled atom for each user on the device. */
+ private int onPullAtom(int atomTag, List<StatsEvent> data) {
+ if (atomTag != FrameworkStatsLog.USER_INFO) {
+ Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
+ return android.app.StatsManager.PULL_SKIP;
+ }
+ final List<UserInfo> users = getUsersInternal(true, true, true);
+ final int size = users.size();
+ for (int idx = 0; idx < size; idx++) {
+ final UserInfo user = users.get(idx);
+ if (user.id == UserHandle.USER_SYSTEM) {
+ // Skip user 0. It's not interesting. We already know it exists, is running, and (if
+ // we know the device configuration) its userType.
+ continue;
+ }
+
+ final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+ final String userTypeCustom = (userTypeStandard ==
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN) ?
+ user.userType : null;
+
+ boolean isUserRunningUnlocked;
+ synchronized (mUserStates) {
+ isUserRunningUnlocked =
+ mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+ }
+
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
+ user.id,
+ userTypeStandard,
+ userTypeCustom,
+ user.flags,
+ user.creationTime,
+ user.lastLoggedInTime,
+ isUserRunningUnlocked
+ ));
+ }
+ return android.app.StatsManager.PULL_SUCCESS;
+ }
+
@VisibleForTesting
UserData putUserInfo(UserInfo userInfo) {
final UserData userData = new UserData();
diff --git a/services/core/java/com/android/server/pm/UserNeedsBadgingCache.java b/services/core/java/com/android/server/pm/UserNeedsBadgingCache.java
new file mode 100644
index 0000000..90c9228
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserNeedsBadgingCache.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.Binder;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+public class UserNeedsBadgingCache {
+
+ private final Object mLock = new Object();
+
+ // Cache of users who need badging.
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseBooleanArray mUserCache = new SparseBooleanArray();
+
+ @NonNull
+ private final UserManagerService mUserManager;
+
+ public UserNeedsBadgingCache(@NonNull UserManagerService userManager) {
+ mUserManager = userManager;
+ }
+
+ public void delete(@UserIdInt int userId) {
+ synchronized (mLock) {
+ mUserCache.delete(userId);
+ }
+ }
+
+ public boolean get(@UserIdInt int userId) {
+ synchronized (mLock) {
+ int index = mUserCache.indexOfKey(userId);
+ if (index >= 0) {
+ return mUserCache.valueAt(index);
+ }
+ }
+
+ final UserInfo userInfo;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ userInfo = mUserManager.getUserInfo(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ final boolean b;
+ b = userInfo != null && userInfo.isManagedProfile();
+ synchronized (mLock) {
+ mUserCache.put(userId, b);
+ }
+ return b;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/WatchedIntentResolver.java b/services/core/java/com/android/server/pm/WatchedIntentResolver.java
index 1c3d884..daa50ab 100644
--- a/services/core/java/com/android/server/pm/WatchedIntentResolver.java
+++ b/services/core/java/com/android/server/pm/WatchedIntentResolver.java
@@ -18,8 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.IntentFilter;
import com.android.server.IntentResolver;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.Snappable;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
@@ -99,8 +101,8 @@
}
@Override
- public void addFilter(F f) {
- super.addFilter(f);
+ public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) {
+ super.addFilter(snapshot, f);
f.registerObserver(mWatcher);
onChanged();
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 60d2fc1..49553f4 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -740,7 +740,7 @@
grantPermissionsToSystemPackage(pm,
getDefaultSystemHandlerActivityPackage(pm,
DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, userId),
- userId, CONTACTS_PERMISSIONS);
+ userId, CONTACTS_PERMISSIONS, NOTIFICATION_PERMISSIONS);
// Maps
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) {
@@ -775,7 +775,7 @@
grantPermissionsToSystemPackage(pm, voiceInteractPackageName, userId,
CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS, MICROPHONE_PERMISSIONS,
PHONE_PERMISSIONS, SMS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
- NEARBY_DEVICES_PERMISSIONS);
+ NEARBY_DEVICES_PERMISSIONS, NOTIFICATION_PERMISSIONS);
}
}
@@ -784,7 +784,8 @@
grantPermissionsToSystemPackage(pm,
getDefaultSystemHandlerActivityPackage(pm,
SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId),
- userId, MICROPHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS);
+ userId, MICROPHONE_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
+ NOTIFICATION_PERMISSIONS);
}
// Voice recognition
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ed47bfb7..87494a6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -3364,7 +3364,7 @@
// For updated system applications, a privileged/oem permission
// is granted only if it had been defined by the original application.
if (pkgSetting.getTransientState().isUpdatedSystemApp()) {
- final PackageSetting disabledPs = mPackageManagerInt
+ final PackageStateInternal disabledPs = mPackageManagerInt
.getDisabledSystemPackage(pkg.getPackageName());
final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg();
if (disabledPkg != null
@@ -3762,7 +3762,7 @@
return;
}
- PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
+ PackageStateInternal disabledPs = mPackageManagerInt.getDisabledSystemPackage(
pkg.getPackageName());
boolean isShadowingSystemPkg = disabledPs != null && disabledPs.getAppId() == pkg.getUid();
@@ -4104,7 +4104,7 @@
}
private boolean isPermissionDeclaredByDisabledSystemPkg(@NonNull Permission permission) {
- final PackageSetting disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage(
+ final PackageStateInternal disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage(
permission.getPackageName());
if (disabledSourcePs != null && disabledSourcePs.getPkg() != null) {
final String permissionName = permission.getName();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index 410fa97..da22b17 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -37,6 +37,8 @@
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedArraySet;
+import java.util.Collections;
+import java.util.Map;
import java.util.Objects;
@DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
@@ -540,6 +542,13 @@
return this;
}
+ @NonNull
+ @Override
+ public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
+ return mSharedLibraryOverlayPaths == null
+ ? Collections.emptyMap() : mSharedLibraryOverlayPaths;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
@@ -703,11 +712,6 @@
}
@DataClass.Generated.Member
- public @Nullable WatchedArrayMap<String,OverlayPaths> getSharedLibraryOverlayPaths() {
- return mSharedLibraryOverlayPaths;
- }
-
- @DataClass.Generated.Member
public @Nullable String getSplashScreenTheme() {
return mSplashScreenTheme;
}
@@ -774,10 +778,10 @@
}
@DataClass.Generated(
- time = 1644638242940L,
+ time = 1645040852569L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index d24ce96..0926ba2 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -46,27 +46,13 @@
private final PersistableBundle appExtras;
private final PersistableBundle launcherExtras;
- private SuspendParams(SuspendDialogInfo dialogInfo, PersistableBundle appExtras,
+ public SuspendParams(SuspendDialogInfo dialogInfo, PersistableBundle appExtras,
PersistableBundle launcherExtras) {
this.dialogInfo = dialogInfo;
this.appExtras = appExtras;
this.launcherExtras = launcherExtras;
}
- /**
- * Returns a {@link SuspendParams} object with the given fields. Returns {@code null} if all
- * the fields are {@code null}.
- *
- * @return A {@link SuspendParams} object or {@code null}.
- */
- public static SuspendParams getInstanceOrNull(SuspendDialogInfo dialogInfo,
- PersistableBundle appExtras, PersistableBundle launcherExtras) {
- if (dialogInfo == null && appExtras == null && launcherExtras == null) {
- return null;
- }
- return new SuspendParams(dialogInfo, appExtras, launcherExtras);
- }
-
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
@@ -170,7 +156,7 @@
Slog.e(LOG_TAG, "Exception while trying to parse SuspendParams,"
+ " some fields may default", e);
}
- return getInstanceOrNull(readDialogInfo, readAppExtras, readLauncherExtras);
+ return new SuspendParams(readDialogInfo, readAppExtras, readLauncherExtras);
}
public SuspendDialogInfo getDialogInfo() {
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
similarity index 70%
rename from services/core/java/com/android/server/pm/ComponentResolver.java
rename to services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index aa393d2..cf9370d 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.server.pm;
+package com.android.server.pm.resolution;
import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
-import static android.content.pm.PackageManagerInternal.PACKAGE_SETUP_WIZARD;
import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,11 +32,9 @@
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.InstantAppResolveInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
@@ -46,14 +44,18 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.IntentResolver;
+import com.android.server.pm.Computer;
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerException;
+import com.android.server.pm.UserManagerService;
+import com.android.server.pm.UserNeedsBadgingCache;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageInfoUtils.CachedApplicationInfoGenerator;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageStateUtils;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedActivity;
@@ -63,12 +65,13 @@
import com.android.server.pm.pkg.component.ParsedProvider;
import com.android.server.pm.pkg.component.ParsedProviderImpl;
import com.android.server.pm.pkg.component.ParsedService;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
-import com.android.server.utils.WatchableImpl;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -79,9 +82,7 @@
import java.util.function.Function;
/** Resolves all Android component types [activities, services, providers and receivers]. */
-public class ComponentResolver
- extends WatchableImpl
- implements Snappable {
+public class ComponentResolver extends ComponentResolverLocked implements Snappable {
private static final boolean DEBUG = false;
private static final String TAG = "PackageManager";
private static final boolean DEBUG_FILTERS = false;
@@ -104,7 +105,7 @@
PROTECTED_ACTIONS.add(Intent.ACTION_VIEW);
}
- static final Comparator<ResolveInfo> RESOLVE_PRIORITY_SORTER = (r1, r2) -> {
+ public static final Comparator<ResolveInfo> RESOLVE_PRIORITY_SORTER = (r1, r2) -> {
int v1 = r1.priority;
int v2 = r2.priority;
//System.out.println("Comparing: q1=" + q1 + " q2=" + q2);
@@ -140,62 +141,8 @@
return 0;
};
- private static UserManagerService sUserManager;
- private static PackageManagerInternal sPackageManagerInternal;
-
- /**
- * Locking within package manager is going to get worse before it gets better. Currently,
- * we need to share the {@link PackageManagerService} lock to prevent deadlocks. This occurs
- * because in order to safely query the resolvers, we need to obtain this lock. However,
- * during resolution, we call into the {@link PackageManagerService}. This is _not_ to
- * operate on data controlled by the service proper, but, to check the state of package
- * settings [contained in a {@link Settings} object]. However, the {@link Settings} object
- * happens to be protected by the main {@link PackageManagerService} lock.
- * <p>
- * There are a couple potential solutions.
- * <ol>
- * <li>Split all of our locks into reader/writer locks. This would allow multiple,
- * simultaneous read operations and means we don't have to be as cautious about lock
- * layering. Only when we want to perform a write operation will we ever be in a
- * position to deadlock the system.</li>
- * <li>Use the same lock across all classes within the {@code com.android.server.pm}
- * package. By unifying the lock object, we remove any potential lock layering issues
- * within the package manager. However, we already have a sense that this lock is
- * heavily contended and merely adding more dependencies on it will have further
- * impact.</li>
- * <li>Implement proper lock ordering within the package manager. By defining the
- * relative layer of the component [eg. {@link PackageManagerService} is at the top.
- * Somewhere in the middle would be {@link ComponentResolver}. At the very bottom
- * would be {@link Settings}.] The ordering would allow higher layers to hold their
- * lock while calling down. Lower layers must relinquish their lock before calling up.
- * Since {@link Settings} would live at the lowest layer, the {@link ComponentResolver}
- * would be able to hold its lock while checking the package setting state.</li>
- * </ol>
- */
- private final PackageManagerTracedLock mLock;
-
- /** All available activities, for your resolving pleasure. */
- @GuardedBy("mLock")
- private final ActivityIntentResolver mActivities;
-
- /** All available providers, for your resolving pleasure. */
- @GuardedBy("mLock")
- private final ProviderIntentResolver mProviders;
-
- /** All available receivers, for your resolving pleasure. */
- @GuardedBy("mLock")
- private final ReceiverIntentResolver mReceivers;
-
- /** All available services, for your resolving pleasure. */
- @GuardedBy("mLock")
- private final ServiceIntentResolver mServices;
-
- /** Mapping from provider authority [first directory in content URI codePath) to provider. */
- @GuardedBy("mLock")
- private final ArrayMap<String, ParsedProvider> mProvidersByAuthority;
-
/** Whether or not processing protected filters should be deferred. */
- private boolean mDeferProtectedFilters = true;
+ boolean mDeferProtectedFilters = true;
/**
* Tracks high priority intent filters for protected actions. During boot, certain
@@ -210,348 +157,62 @@
* This is a pair of component package name to actual filter, because we don't store the
* name inside the filter. It's technically independent of the component it's contained in.
*/
- private List<Pair<ParsedMainComponent, ParsedIntentInfo>> mProtectedFilters;
+ List<Pair<ParsedMainComponent, ParsedIntentInfo>> mProtectedFilters;
- ComponentResolver(UserManagerService userManager,
- PackageManagerInternal packageManagerInternal,
- PackageManagerTracedLock lock) {
- sPackageManagerInternal = packageManagerInternal;
- sUserManager = userManager;
- mLock = lock;
-
- mActivities = new ActivityIntentResolver();
- mProviders = new ProviderIntentResolver();
- mReceivers = new ReceiverIntentResolver();
- mServices = new ServiceIntentResolver();
+ public ComponentResolver(@NonNull UserManagerService userManager,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache) {
+ super(userManager);
+ mActivities = new ActivityIntentResolver(userManager, userNeedsBadgingCache);
+ mProviders = new ProviderIntentResolver(userManager);
+ mReceivers = new ReceiverIntentResolver(userManager, userNeedsBadgingCache);
+ mServices = new ServiceIntentResolver(userManager);
mProvidersByAuthority = new ArrayMap<>();
mDeferProtectedFilters = true;
- mSnapshot = new SnapshotCache<ComponentResolver>(this, this) {
+ mSnapshot = new SnapshotCache<ComponentResolverApi>(this, this) {
@Override
- public ComponentResolver createSnapshot() {
- return new ComponentResolver(mSource);
+ public ComponentResolverApi createSnapshot() {
+ return new ComponentResolverSnapshot(ComponentResolver.this,
+ userNeedsBadgingCache);
}};
}
- // Copy constructor used in creating snapshots.
- private ComponentResolver(ComponentResolver orig) {
- // Do not set the static variables that are set in the default constructor. Do
- // create a new object for the lock. The snapshot is read-only, so a lock is not
- // strictly required. However, the current code is simpler if the lock exists,
- // but does not contend with any outside class.
- // TODO: make the snapshot lock-free
- mLock = new PackageManagerTracedLock();
-
- mActivities = new ActivityIntentResolver(orig.mActivities);
- mProviders = new ProviderIntentResolver(orig.mProviders);
- mReceivers = new ReceiverIntentResolver(orig.mReceivers);
- mServices = new ServiceIntentResolver(orig.mServices);
- mProvidersByAuthority = new ArrayMap<>(orig.mProvidersByAuthority);
- mDeferProtectedFilters = orig.mDeferProtectedFilters;
- mProtectedFilters = (mProtectedFilters == null)
- ? null
- : new ArrayList<>(orig.mProtectedFilters);
-
- mSnapshot = null;
- }
-
- final SnapshotCache<ComponentResolver> mSnapshot;
+ final SnapshotCache<ComponentResolverApi> mSnapshot;
/**
* Create a snapshot.
*/
- public ComponentResolver snapshot() {
+ public ComponentResolverApi snapshot() {
return mSnapshot.snapshot();
}
-
- /** Returns the given activity */
- @Nullable
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public ParsedActivity getActivity(@NonNull ComponentName component) {
- synchronized (mLock) {
- return mActivities.mActivities.get(component);
- }
- }
-
- /** Returns the given provider */
- @Nullable
- ParsedProvider getProvider(@NonNull ComponentName component) {
- synchronized (mLock) {
- return mProviders.mProviders.get(component);
- }
- }
-
- /** Returns the given receiver */
- @Nullable
- ParsedActivity getReceiver(@NonNull ComponentName component) {
- synchronized (mLock) {
- return mReceivers.mActivities.get(component);
- }
- }
-
- /** Returns the given service */
- @Nullable
- ParsedService getService(@NonNull ComponentName component) {
- synchronized (mLock) {
- return mServices.mServices.get(component);
- }
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public boolean componentExists(@NonNull ComponentName componentName) {
- synchronized (mLock) {
- ParsedMainComponent component = mActivities.mActivities.get(componentName);
- if (component != null) {
- return true;
- }
- component = mReceivers.mActivities.get(componentName);
- if (component != null) {
- return true;
- }
- component = mServices.mServices.get(componentName);
- if (component != null) {
- return true;
- }
- return mProviders.mProviders.get(componentName) != null;
- }
- }
-
- @Nullable
- List<ResolveInfo> queryActivities(Intent intent, String resolvedType, long flags,
- int userId) {
- synchronized (mLock) {
- return mActivities.queryIntent(intent, resolvedType, flags, userId);
- }
- }
-
- @Nullable
- List<ResolveInfo> queryActivities(Intent intent, String resolvedType, long flags,
- List<ParsedActivity> activities, int userId) {
- synchronized (mLock) {
- return mActivities.queryIntentForPackage(
- intent, resolvedType, flags, activities, userId);
- }
- }
-
- @Nullable
- List<ResolveInfo> queryProviders(Intent intent, String resolvedType, long flags, int userId) {
- synchronized (mLock) {
- return mProviders.queryIntent(intent, resolvedType, flags, userId);
- }
- }
-
- @Nullable
- List<ResolveInfo> queryProviders(Intent intent, String resolvedType, long flags,
- List<ParsedProvider> providers, int userId) {
- synchronized (mLock) {
- return mProviders.queryIntentForPackage(intent, resolvedType, flags, providers, userId);
- }
- }
-
- @Nullable
- List<ProviderInfo> queryProviders(String processName, String metaDataKey, int uid, long flags,
- int userId) {
- if (!sUserManager.exists(userId)) {
- return null;
- }
- List<ProviderInfo> providerList = null;
- CachedApplicationInfoGenerator appInfoGenerator = null;
- synchronized (mLock) {
- for (int i = mProviders.mProviders.size() - 1; i >= 0; --i) {
- final ParsedProvider p = mProviders.mProviders.valueAt(i);
- if (p.getAuthority() == null) {
- continue;
- }
-
- final PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(p.getPackageName());
- if (ps == null) {
- continue;
- }
-
- AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
- if (pkg == null) {
- continue;
- }
-
- if (processName != null && (!p.getProcessName().equals(processName)
- || !UserHandle.isSameApp(pkg.getUid(), uid))) {
- continue;
- }
- // See PM.queryContentProviders()'s javadoc for why we have the metaData parameter.
- if (metaDataKey != null && !p.getMetaData().containsKey(metaDataKey)) {
- continue;
- }
- if (appInfoGenerator == null) {
- appInfoGenerator = new CachedApplicationInfoGenerator();
- }
- final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
- final ApplicationInfo appInfo =
- appInfoGenerator.generate(pkg, flags, state, userId, ps);
- if (appInfo == null) {
- continue;
- }
-
- final ProviderInfo info = PackageInfoUtils.generateProviderInfo(
- pkg, p, flags, state, appInfo, userId, ps);
- if (info == null) {
- continue;
- }
- if (providerList == null) {
- providerList = new ArrayList<>(i + 1);
- }
- providerList.add(info);
- }
- }
- return providerList;
- }
-
- @Nullable
- ProviderInfo queryProvider(String authority, long flags, int userId) {
- synchronized (mLock) {
- final ParsedProvider p = mProvidersByAuthority.get(authority);
- if (p == null) {
- return null;
- }
- final PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(p.getPackageName());
- if (ps == null) {
- return null;
- }
- final AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
- if (pkg == null) {
- return null;
- }
- final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
- ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
- pkg, flags, state, userId, ps);
- if (appInfo == null) {
- return null;
- }
- return PackageInfoUtils.generateProviderInfo(pkg, p, flags, state, appInfo, userId, ps);
- }
- }
-
- void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo, boolean safeMode,
- int userId) {
- synchronized (mLock) {
- CachedApplicationInfoGenerator appInfoGenerator = null;
- for (int i = mProvidersByAuthority.size() - 1; i >= 0; --i) {
- final ParsedProvider p = mProvidersByAuthority.valueAt(i);
- if (!p.isSyncable()) {
- continue;
- }
-
- final PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(p.getPackageName());
- if (ps == null) {
- continue;
- }
-
- final AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
- if (pkg == null) {
- continue;
- }
-
- if (safeMode && !pkg.isSystem()) {
- continue;
- }
- if (appInfoGenerator == null) {
- appInfoGenerator = new CachedApplicationInfoGenerator();
- }
- final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
- final ApplicationInfo appInfo =
- appInfoGenerator.generate(pkg, 0, state, userId, ps);
- if (appInfo == null) {
- continue;
- }
-
- final ProviderInfo info = PackageInfoUtils.generateProviderInfo(
- pkg, p, 0, state, appInfo, userId, ps);
- if (info == null) {
- continue;
- }
- outNames.add(mProvidersByAuthority.keyAt(i));
- outInfo.add(info);
- }
- }
- }
-
- @Nullable
- List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, long flags, int userId) {
- synchronized (mLock) {
- return mReceivers.queryIntent(intent, resolvedType, flags, userId);
- }
- }
-
- @Nullable
- List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, long flags,
- List<ParsedActivity> receivers, int userId) {
- synchronized (mLock) {
- return mReceivers.queryIntentForPackage(intent, resolvedType, flags, receivers, userId);
- }
- }
-
- @Nullable
- List<ResolveInfo> queryServices(Intent intent, String resolvedType, long flags, int userId) {
- synchronized (mLock) {
- return mServices.queryIntent(intent, resolvedType, flags, userId);
- }
- }
-
- @Nullable
- List<ResolveInfo> queryServices(Intent intent, String resolvedType, long flags,
- List<ParsedService> services, int userId) {
- synchronized (mLock) {
- return mServices.queryIntentForPackage(intent, resolvedType, flags, services, userId);
- }
- }
-
- /** Returns {@code true} if the given activity is defined by some package */
- boolean isActivityDefined(ComponentName component) {
- synchronized (mLock) {
- return mActivities.mActivities.get(component) != null;
- }
- }
-
- /** Asserts none of the providers defined in the given package haven't already been defined. */
- void assertProvidersNotDefined(AndroidPackage pkg) throws PackageManagerException {
- synchronized (mLock) {
- assertProvidersNotDefinedLocked(pkg);
- }
- }
-
/** Add all components defined in the given package to the internal structures. */
- void addAllComponents(AndroidPackage pkg, boolean chatty) {
+ public void addAllComponents(AndroidPackage pkg, boolean chatty,
+ @Nullable String setupWizardPackage, @NonNull Computer computer) {
final ArrayList<Pair<ParsedActivity, ParsedIntentInfo>> newIntents = new ArrayList<>();
synchronized (mLock) {
- addActivitiesLocked(pkg, newIntents, chatty);
- addReceiversLocked(pkg, chatty);
- addProvidersLocked(pkg, chatty);
- addServicesLocked(pkg, chatty);
+ addActivitiesLocked(computer, pkg, newIntents, chatty);
+ addReceiversLocked(computer, pkg, chatty);
+ addProvidersLocked(computer, pkg, chatty);
+ addServicesLocked(computer, pkg, chatty);
onChanged();
}
- // expect single setupwizard package
- final String setupWizardPackage = ArrayUtils.firstOrNull(
- sPackageManagerInternal.getKnownPackageNames(
- PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
for (int i = newIntents.size() - 1; i >= 0; --i) {
final Pair<ParsedActivity, ParsedIntentInfo> pair = newIntents.get(i);
- final PackageSetting disabledPkgSetting = (PackageSetting) sPackageManagerInternal
+ final PackageStateInternal disabledPkgSetting = computer
.getDisabledSystemPackage(pair.first.getPackageName());
final AndroidPackage disabledPkg =
disabledPkgSetting == null ? null : disabledPkgSetting.getPkg();
final List<ParsedActivity> systemActivities =
disabledPkg != null ? disabledPkg.getActivities() : null;
- adjustPriority(systemActivities, pair.first, pair.second, setupWizardPackage);
+ adjustPriority(computer, systemActivities, pair.first, pair.second, setupWizardPackage);
onChanged();
}
}
/** Removes all components defined in the given package from the internal structures. */
- void removeAllComponents(AndroidPackage pkg, boolean chatty) {
+ public void removeAllComponents(AndroidPackage pkg, boolean chatty) {
synchronized (mLock) {
removeAllComponentsLocked(pkg, chatty);
onChanged();
@@ -562,7 +223,7 @@
* Reprocess any protected filters that have been deferred. At this point, we've scanned
* all of the filters defined on the /system partition and know the special components.
*/
- void fixProtectedFilterPriorities() {
+ public void fixProtectedFilterPriorities(@Nullable String setupWizardPackage) {
synchronized (mLock) {
if (!mDeferProtectedFilters) {
return;
@@ -576,11 +237,6 @@
mProtectedFilters;
mProtectedFilters = null;
- // expect single setupwizard package
- final String setupWizardPackage = ArrayUtils.firstOrNull(
- sPackageManagerInternal.getKnownPackageNames(
- PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
-
if (DEBUG_FILTERS && setupWizardPackage == null) {
Slog.i(TAG, "No setup wizard;"
+ " All protected intents capped to priority 0");
@@ -615,7 +271,7 @@
}
}
- void dumpActivityResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
+ public void dumpActivityResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
synchronized (mLock) {
if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
: "Activity Resolver Table:", " ", packageName,
@@ -625,7 +281,7 @@
}
}
- void dumpProviderResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
+ public void dumpProviderResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
synchronized (mLock) {
if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
: "Provider Resolver Table:", " ", packageName,
@@ -635,7 +291,7 @@
}
}
- void dumpReceiverResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
+ public void dumpReceiverResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
synchronized (mLock) {
if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
: "Receiver Resolver Table:", " ", packageName,
@@ -645,7 +301,7 @@
}
}
- void dumpServiceResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
+ public void dumpServiceResolvers(PrintWriter pw, DumpState dumpState, String packageName) {
synchronized (mLock) {
if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
: "Service Resolver Table:", " ", packageName,
@@ -655,7 +311,8 @@
}
}
- void dumpContentProviders(PrintWriter pw, DumpState dumpState, String packageName) {
+ public void dumpContentProviders(@NonNull Computer computer, PrintWriter pw,
+ DumpState dumpState, String packageName) {
synchronized (mLock) {
boolean printedSomething = false;
for (ParsedProvider p : mProviders.mProviders.values()) {
@@ -695,7 +352,7 @@
pw.print(" ");
pw.println(p.toString());
- AndroidPackage pkg = sPackageManagerInternal.getPackage(p.getPackageName());
+ AndroidPackage pkg = computer.getPackage(p.getPackageName());
if (pkg != null) {
pw.print(" applicationInfo=");
@@ -705,7 +362,7 @@
}
}
- void dumpServicePermissions(PrintWriter pw, DumpState dumpState) {
+ public void dumpServicePermissions(PrintWriter pw, DumpState dumpState) {
synchronized (mLock) {
if (dumpState.onTitlePrinted()) pw.println();
pw.println("Service permissions:");
@@ -728,13 +385,13 @@
}
@GuardedBy("mLock")
- private void addActivitiesLocked(AndroidPackage pkg,
+ private void addActivitiesLocked(@NonNull Computer computer, AndroidPackage pkg,
List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents, boolean chatty) {
final int activitiesSize = ArrayUtils.size(pkg.getActivities());
StringBuilder r = null;
for (int i = 0; i < activitiesSize; i++) {
ParsedActivity a = pkg.getActivities().get(i);
- mActivities.addActivity(a, "activity", newIntents);
+ mActivities.addActivity(computer, a, "activity", newIntents);
if (DEBUG_PACKAGE_SCANNING && chatty) {
if (r == null) {
r = new StringBuilder(256);
@@ -750,12 +407,12 @@
}
@GuardedBy("mLock")
- private void addProvidersLocked(AndroidPackage pkg, boolean chatty) {
+ private void addProvidersLocked(@NonNull Computer computer, AndroidPackage pkg, boolean chatty) {
final int providersSize = ArrayUtils.size(pkg.getProviders());
StringBuilder r = null;
for (int i = 0; i < providersSize; i++) {
ParsedProvider p = pkg.getProviders().get(i);
- mProviders.addProvider(p);
+ mProviders.addProvider(computer, p);
if (p.getAuthority() != null) {
String[] names = p.getAuthority().split(";");
@@ -814,12 +471,12 @@
}
@GuardedBy("mLock")
- private void addReceiversLocked(AndroidPackage pkg, boolean chatty) {
+ private void addReceiversLocked(@NonNull Computer computer, AndroidPackage pkg, boolean chatty) {
final int receiversSize = ArrayUtils.size(pkg.getReceivers());
StringBuilder r = null;
for (int i = 0; i < receiversSize; i++) {
ParsedActivity a = pkg.getReceivers().get(i);
- mReceivers.addActivity(a, "receiver", null);
+ mReceivers.addActivity(computer, a, "receiver", null);
if (DEBUG_PACKAGE_SCANNING && chatty) {
if (r == null) {
r = new StringBuilder(256);
@@ -835,12 +492,12 @@
}
@GuardedBy("mLock")
- private void addServicesLocked(AndroidPackage pkg, boolean chatty) {
+ private void addServicesLocked(@NonNull Computer computer, AndroidPackage pkg, boolean chatty) {
final int servicesSize = ArrayUtils.size(pkg.getServices());
StringBuilder r = null;
for (int i = 0; i < servicesSize; i++) {
ParsedService s = pkg.getServices().get(i);
- mServices.addService(s);
+ mServices.addService(computer, s);
if (DEBUG_PACKAGE_SCANNING && chatty) {
if (r == null) {
r = new StringBuilder(256);
@@ -945,8 +602,8 @@
* <em>NOTE:</em> There is one exception. For security reasons, the setup wizard is
* allowed to obtain any priority on any action.
*/
- private void adjustPriority(List<ParsedActivity> systemActivities, ParsedActivity activity,
- ParsedIntentInfo intentInfo, String setupWizardPackage) {
+ private void adjustPriority(@NonNull Computer computer, List<ParsedActivity> systemActivities,
+ ParsedActivity activity, ParsedIntentInfo intentInfo, String setupWizardPackage) {
// nothing to do; priority is fine as-is
IntentFilter intentFilter = intentInfo.getIntentFilter();
if (intentFilter.getPriority() <= 0) {
@@ -954,7 +611,7 @@
}
String packageName = activity.getPackageName();
- AndroidPackage pkg = sPackageManagerInternal.getPackage(packageName);
+ AndroidPackage pkg = computer.getPackage(packageName);
final boolean privilegedApp = pkg.isPrivileged();
String className = activity.getClassName();
@@ -1231,28 +888,30 @@
}
}
- @GuardedBy("mLock")
- private void assertProvidersNotDefinedLocked(AndroidPackage pkg)
+ /** Asserts none of the providers defined in the given package haven't already been defined. */
+ public void assertProvidersNotDefined(@NonNull AndroidPackage pkg)
throws PackageManagerException {
- final int providersSize = ArrayUtils.size(pkg.getProviders());
- int i;
- for (i = 0; i < providersSize; i++) {
- ParsedProvider p = pkg.getProviders().get(i);
- if (p.getAuthority() != null) {
- final String[] names = p.getAuthority().split(";");
- for (int j = 0; j < names.length; j++) {
- if (mProvidersByAuthority.containsKey(names[j])) {
- final ParsedProvider other = mProvidersByAuthority.get(names[j]);
- final String otherPackageName =
- (other != null && other.getComponentName() != null)
- ? other.getComponentName().getPackageName() : "?";
- // if we're installing over the same already-installed package, this is ok
- if (!otherPackageName.equals(pkg.getPackageName())) {
- throw new PackageManagerException(
- INSTALL_FAILED_CONFLICTING_PROVIDER,
- "Can't install because provider name " + names[j]
- + " (in package " + pkg.getPackageName()
- + ") is already used by " + otherPackageName);
+ synchronized (mLock) {
+ final int providersSize = ArrayUtils.size(pkg.getProviders());
+ int i;
+ for (i = 0; i < providersSize; i++) {
+ ParsedProvider p = pkg.getProviders().get(i);
+ if (p.getAuthority() != null) {
+ final String[] names = p.getAuthority().split(";");
+ for (int j = 0; j < names.length; j++) {
+ if (mProvidersByAuthority.containsKey(names[j])) {
+ final ParsedProvider other = mProvidersByAuthority.get(names[j]);
+ final String otherPackageName =
+ (other != null && other.getComponentName() != null)
+ ? other.getComponentName().getPackageName() : "?";
+ // if installing over the same already-installed package,this is ok
+ if (!otherPackageName.equals(pkg.getPackageName())) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_CONFLICTING_PROVIDER,
+ "Can't install because provider name " + names[j]
+ + " (in package " + pkg.getPackageName()
+ + ") is already used by " + otherPackageName);
+ }
}
}
}
@@ -1266,22 +925,31 @@
private final ArrayMap<String, F[]> mMimeGroupToFilter = new ArrayMap<>();
private boolean mIsUpdatingMimeGroup = false;
+ @NonNull
+ protected final UserManagerService mUserManager;
+
// Default constructor
- MimeGroupsAwareIntentResolver() {
+ MimeGroupsAwareIntentResolver(@NonNull UserManagerService userManager) {
+ mUserManager = userManager;
}
// Copy constructor used in creating snapshots
- MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F, R> orig) {
+ MimeGroupsAwareIntentResolver(MimeGroupsAwareIntentResolver<F, R> orig,
+ @NonNull UserManagerService userManager) {
+ mUserManager = userManager;
copyFrom(orig);
copyInto(mMimeGroupToFilter, orig.mMimeGroupToFilter);
mIsUpdatingMimeGroup = orig.mIsUpdatingMimeGroup;
}
@Override
- public void addFilter(F f) {
+ public void addFilter(@Nullable PackageDataSnapshot snapshot, F f) {
IntentFilter intentFilter = getIntentFilter(f);
- applyMimeGroups(f);
- super.addFilter(f);
+ // We assume Computer is available for this class and all subclasses. Because this class
+ // uses subclass method override to handle logic, the Computer parameter must be in the
+ // base, leading to this odd nullability.
+ applyMimeGroups((Computer) snapshot, f);
+ super.addFilter(snapshot, f);
if (!mIsUpdatingMimeGroup) {
register_intent_filter(f, intentFilter.mimeGroupsIterator(), mMimeGroupToFilter,
@@ -1309,7 +977,8 @@
* @param mimeGroup MIME group to update
* @return true, if any intent filters were changed due to this update
*/
- public boolean updateMimeGroup(String packageName, String mimeGroup) {
+ public boolean updateMimeGroup(@NonNull Computer computer, String packageName,
+ String mimeGroup) {
F[] filters = mMimeGroupToFilter.get(mimeGroup);
int n = filters != null ? filters.length : 0;
@@ -1318,18 +987,18 @@
F filter;
for (int i = 0; i < n && (filter = filters[i]) != null; i++) {
if (isPackageForFilter(packageName, filter)) {
- hasChanges |= updateFilter(filter);
+ hasChanges |= updateFilter(computer, filter);
}
}
mIsUpdatingMimeGroup = false;
return hasChanges;
}
- private boolean updateFilter(F f) {
+ private boolean updateFilter(@NonNull Computer computer, F f) {
IntentFilter filter = getIntentFilter(f);
List<String> oldTypes = filter.dataTypes();
removeFilter(f);
- addFilter(f);
+ addFilter(computer, f);
List<String> newTypes = filter.dataTypes();
return !equalLists(oldTypes, newTypes);
}
@@ -1350,16 +1019,18 @@
return first.equals(second);
}
- private void applyMimeGroups(F f) {
+ private void applyMimeGroups(@NonNull Computer computer, F f) {
IntentFilter filter = getIntentFilter(f);
for (int i = filter.countMimeGroups() - 1; i >= 0; i--) {
- List<String> mimeTypes = sPackageManagerInternal.getMimeGroup(
- f.first.getPackageName(), filter.getMimeGroup(i));
+ final PackageStateInternal packageState = computer.getPackageStateInternal(
+ f.first.getPackageName());
- for (int typeIndex = mimeTypes.size() - 1; typeIndex >= 0; typeIndex--) {
- String mimeType = mimeTypes.get(typeIndex);
+ Collection<String> mimeTypes = packageState == null
+ ? Collections.emptyList() : packageState.getMimeGroups()
+ .get(filter.getMimeGroup(i));
+ for (String mimeType : mimeTypes) {
try {
filter.addDynamicDataType(mimeType);
} catch (IntentFilter.MalformedMimeTypeException e) {
@@ -1370,50 +1041,74 @@
}
}
}
+
+ @Override
+ protected boolean isFilterStopped(@Nullable PackageStateInternal packageState,
+ @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) {
+ return true;
+ }
+
+ if (packageState == null || packageState.getPkg() == null) {
+ return false;
+ }
+
+ // System apps are never considered stopped for purposes of
+ // filtering, because there may be no way for the user to
+ // actually re-launch them.
+ return !packageState.isSystem()
+ && packageState.getUserStateOrDefault(userId).isStopped();
+ }
}
- private static class ActivityIntentResolver
+ public static class ActivityIntentResolver
extends MimeGroupsAwareIntentResolver<Pair<ParsedActivity, ParsedIntentInfo>, ResolveInfo> {
+ @NonNull
+ private UserNeedsBadgingCache mUserNeedsBadging;
+
// Default constructor
- ActivityIntentResolver() {
+ ActivityIntentResolver(@NonNull UserManagerService userManager,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache) {
+ super(userManager);
+ mUserNeedsBadging = userNeedsBadgingCache;
}
// Copy constructor used in creating snapshots
- ActivityIntentResolver(ActivityIntentResolver orig) {
- super(orig);
+ ActivityIntentResolver(@NonNull ActivityIntentResolver orig,
+ @NonNull UserManagerService userManager,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache) {
+ super(orig, userManager);
mActivities.putAll(orig.mActivities);
- mFlags = orig.mFlags;
+ mUserNeedsBadging = userNeedsBadgingCache;
}
@Override
- public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
- boolean defaultOnly, int userId) {
- if (!sUserManager.exists(userId)) return null;
- mFlags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
- return super.queryIntent(intent, resolvedType, defaultOnly, userId);
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ long flags = (defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0);
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
}
- List<ResolveInfo> queryIntent(Intent intent, String resolvedType, long flags,
- int userId) {
- if (!sUserManager.exists(userId)) {
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int userId) {
+ if (!mUserManager.exists(userId)) {
return null;
}
- mFlags = flags;
- return super.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId);
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
}
- List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
- long flags, List<ParsedActivity> packageActivities, int userId) {
- if (!sUserManager.exists(userId)) {
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<ParsedActivity> packageActivities,
+ int userId) {
+ if (!mUserManager.exists(userId)) {
return null;
}
if (packageActivities == null) {
return Collections.emptyList();
}
- mFlags = flags;
final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
final int activitiesSize = packageActivities.size();
ArrayList<Pair<ParsedActivity, ParsedIntentInfo>[]> listCut =
@@ -1431,10 +1126,11 @@
listCut.add(array);
}
}
- return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, userId, flags);
}
- protected void addActivity(ParsedActivity a, String type,
+ protected void addActivity(@NonNull Computer computer, ParsedActivity a, String type,
List<Pair<ParsedActivity, ParsedIntentInfo>> newIntents) {
mActivities.put(a.getComponentName(), a);
if (DEBUG_SHOW_INFO) {
@@ -1455,7 +1151,7 @@
if (!intentFilter.debugCheck()) {
Log.w(TAG, "==> For Activity " + a.getName());
}
- addFilter(Pair.create(a, intent));
+ addFilter(computer, Pair.create(a, intent));
}
}
@@ -1497,11 +1193,6 @@
}
@Override
- protected boolean isFilterStopped(Pair<ParsedActivity, ParsedIntentInfo> filter, int userId) {
- return ComponentResolver.isFilterStopped(filter, userId);
- }
-
- @Override
protected boolean isPackageForFilter(String packageName,
Pair<ParsedActivity, ParsedIntentInfo> info) {
return packageName.equals(info.first.getPackageName());
@@ -1517,43 +1208,35 @@
}
@Override
- protected ResolveInfo newResult(Pair<ParsedActivity, ParsedIntentInfo> pair,
- int match, int userId) {
+ protected ResolveInfo newResult(@NonNull Computer computer,
+ Pair<ParsedActivity, ParsedIntentInfo> pair, int match, int userId,
+ long customFlags) {
ParsedActivity activity = pair.first;
ParsedIntentInfo info = pair.second;
IntentFilter intentFilter = info.getIntentFilter();
- if (!sUserManager.exists(userId)) {
+ if (!mUserManager.exists(userId)) {
if (DEBUG) {
log("User doesn't exist", info, match, userId);
}
return null;
}
- AndroidPackage pkg = sPackageManagerInternal.getPackage(activity.getPackageName());
- if (pkg == null) {
- return null;
- }
-
- if (!sPackageManagerInternal.isEnabledAndMatches(activity, mFlags, userId)) {
+ final PackageStateInternal packageState =
+ computer.getPackageStateInternal(activity.getPackageName());
+ if (packageState == null || packageState.getPkg() == null
+ || !PackageStateUtils.isEnabledAndMatches(packageState, activity, customFlags,
+ userId)) {
if (DEBUG) {
- log("!PackageManagerInternal.isEnabledAndMatches; mFlags="
- + DebugUtils.flagsToString(PackageManager.class, "MATCH_", mFlags),
+ log("!PackageManagerInternal.isEnabledAndMatches; flags="
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", customFlags),
info, match, userId);
}
return null;
}
- PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(activity.getPackageName());
- if (ps == null) {
- if (DEBUG) {
- log("info.activity.owner.mExtras == null", info, match, userId);
- }
- return null;
- }
- final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
- ActivityInfo ai = PackageInfoUtils.generateActivityInfo(pkg, activity, mFlags,
- userState, userId, ps);
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+ ActivityInfo ai = PackageInfoUtils.generateActivityInfo(packageState.getPkg(), activity,
+ customFlags, userState, userId, packageState);
if (ai == null) {
if (DEBUG) {
log("Failed to create ActivityInfo based on " + activity, info, match,
@@ -1562,15 +1245,15 @@
return null;
}
final boolean matchExplicitlyVisibleOnly =
- (mFlags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
+ (customFlags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
final boolean matchVisibleToInstantApp =
- (mFlags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+ (customFlags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
final boolean componentVisible =
matchVisibleToInstantApp
&& intentFilter.isVisibleToInstantApp()
&& (!matchExplicitlyVisibleOnly
|| intentFilter.isExplicitlyVisibleToInstantApp());
- final boolean matchInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
+ final boolean matchInstantApp = (customFlags & PackageManager.MATCH_INSTANT) != 0;
// throw out filters that aren't visible to ephemeral apps
if (matchVisibleToInstantApp && !(componentVisible || userState.isInstantApp())) {
if (DEBUG) {
@@ -1595,7 +1278,7 @@
}
// throw out instant app filters if updates are available; will trigger
// instant app resolution
- if (userState.isInstantApp() && ps.isUpdateAvailable()) {
+ if (userState.isInstantApp() && packageState.isUpdateAvailable()) {
if (DEBUG) {
log("Instant app update is available", info, match, userId);
}
@@ -1604,7 +1287,7 @@
final ResolveInfo res =
new ResolveInfo(intentFilter.hasCategory(Intent.CATEGORY_BROWSABLE));
res.activityInfo = ai;
- if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
+ if ((customFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = intentFilter;
}
res.handleAllWebDataURI = intentFilter.handleAllWebDataURI();
@@ -1617,7 +1300,7 @@
res.isDefault = info.isHasDefault();
res.labelRes = info.getLabelRes();
res.nonLocalizedLabel = info.getNonLocalizedLabel();
- if (sPackageManagerInternal.userNeedsBadging(userId)) {
+ if (mUserNeedsBadging.get(userId)) {
res.noResourceId = true;
} else {
res.icon = info.getIcon();
@@ -1683,19 +1366,22 @@
// ActivityIntentResolver.
protected final ArrayMap<ComponentName, ParsedActivity> mActivities =
new ArrayMap<>();
- private long mFlags;
}
// Both receivers and activities share a class, but point to different get methods
- private static final class ReceiverIntentResolver extends ActivityIntentResolver {
+ public static final class ReceiverIntentResolver extends ActivityIntentResolver {
// Default constructor
- ReceiverIntentResolver() {
+ ReceiverIntentResolver(@NonNull UserManagerService userManager,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache) {
+ super(userManager, userNeedsBadgingCache);
}
// Copy constructor used in creating snapshots
- ReceiverIntentResolver(ReceiverIntentResolver orig) {
- super(orig);
+ ReceiverIntentResolver(@NonNull ReceiverIntentResolver orig,
+ @NonNull UserManagerService userManager,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache) {
+ super(orig, userManager, userNeedsBadgingCache);
}
@Override
@@ -1704,48 +1390,50 @@
}
}
- private static final class ProviderIntentResolver
+ public static final class ProviderIntentResolver
extends MimeGroupsAwareIntentResolver<Pair<ParsedProvider, ParsedIntentInfo>, ResolveInfo> {
// Default constructor
- ProviderIntentResolver() {
+ ProviderIntentResolver(@NonNull UserManagerService userManager) {
+ super(userManager);
}
// Copy constructor used in creating snapshots
- ProviderIntentResolver(ProviderIntentResolver orig) {
- super(orig);
+ ProviderIntentResolver(@NonNull ProviderIntentResolver orig,
+ @NonNull UserManagerService userManager) {
+ super(orig, userManager);
mProviders.putAll(orig.mProviders);
- mFlags = orig.mFlags;
}
@Override
- public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
- boolean defaultOnly, int userId) {
- mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
- return super.queryIntent(intent, resolvedType, defaultOnly, userId);
- }
-
- @Nullable
- List<ResolveInfo> queryIntent(Intent intent, String resolvedType, long flags,
- int userId) {
- if (!sUserManager.exists(userId)) {
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) {
return null;
}
- mFlags = flags;
- return super.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId);
+ long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
}
@Nullable
- List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
- long flags, List<ParsedProvider> packageProviders, int userId) {
- if (!sUserManager.exists(userId)) {
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
+ }
+
+ @Nullable
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<ParsedProvider> packageProviders,
+ int userId) {
+ if (!mUserManager.exists(userId)) {
return null;
}
if (packageProviders == null) {
return Collections.emptyList();
}
- mFlags = flags;
final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
final int providersSize = packageProviders.size();
ArrayList<Pair<ParsedProvider, ParsedIntentInfo>[]> listCut =
@@ -1763,10 +1451,11 @@
listCut.add(array);
}
}
- return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, userId, flags);
}
- void addProvider(ParsedProvider p) {
+ void addProvider(@NonNull Computer computer, ParsedProvider p) {
if (mProviders.containsKey(p.getComponentName())) {
Slog.w(TAG, "Provider " + p.getComponentName() + " already defined; ignoring");
return;
@@ -1789,7 +1478,7 @@
if (!intentFilter.debugCheck()) {
Log.w(TAG, "==> For Provider " + p.getName());
}
- addFilter(Pair.create(p, intent));
+ addFilter(computer, Pair.create(p, intent));
}
}
@@ -1832,21 +1521,16 @@
}
@Override
- protected boolean isFilterStopped(Pair<ParsedProvider, ParsedIntentInfo> filter,
- int userId) {
- return ComponentResolver.isFilterStopped(filter, userId);
- }
-
- @Override
protected boolean isPackageForFilter(String packageName,
Pair<ParsedProvider, ParsedIntentInfo> info) {
return packageName.equals(info.first.getPackageName());
}
@Override
- protected ResolveInfo newResult(Pair<ParsedProvider, ParsedIntentInfo> pair,
- int match, int userId) {
- if (!sUserManager.exists(userId)) {
+ protected ResolveInfo newResult(@NonNull Computer computer,
+ Pair<ParsedProvider, ParsedIntentInfo> pair, int match, int userId,
+ long customFlags) {
+ if (!mUserManager.exists(userId)) {
return null;
}
@@ -1854,24 +1538,18 @@
ParsedIntentInfo intentInfo = pair.second;
IntentFilter filter = intentInfo.getIntentFilter();
- AndroidPackage pkg = sPackageManagerInternal.getPackage(provider.getPackageName());
- if (pkg == null) {
+ PackageStateInternal packageState =
+ computer.getPackageStateInternal(provider.getPackageName());
+ if (packageState == null || packageState.getPkg() == null
+ || !PackageStateUtils.isEnabledAndMatches(packageState, provider, customFlags,
+ userId)) {
return null;
}
- if (!sPackageManagerInternal.isEnabledAndMatches(provider, mFlags, userId)) {
- return null;
- }
-
- PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(provider.getPackageName());
- if (ps == null) {
- return null;
- }
- final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
- final boolean matchVisibleToInstantApp = (mFlags
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+ final boolean matchVisibleToInstantApp = (customFlags
& PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
- final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
+ final boolean isInstantApp = (customFlags & PackageManager.MATCH_INSTANT) != 0;
// throw out filters that aren't visible to instant applications
if (matchVisibleToInstantApp
&& !(filter.isVisibleToInstantApp() || userState.isInstantApp())) {
@@ -1883,22 +1561,22 @@
}
// throw out instant application filters if updates are available; will trigger
// instant application resolution
- if (userState.isInstantApp() && ps.isUpdateAvailable()) {
+ if (userState.isInstantApp() && packageState.isUpdateAvailable()) {
return null;
}
final ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
- pkg, mFlags, userState, userId, ps);
+ packageState.getPkg(), customFlags, userState, userId, packageState);
if (appInfo == null) {
return null;
}
- ProviderInfo pi = PackageInfoUtils.generateProviderInfo(pkg, provider, mFlags,
- userState, appInfo, userId, ps);
+ ProviderInfo pi = PackageInfoUtils.generateProviderInfo(packageState.getPkg(), provider,
+ customFlags, userState, appInfo, userId, packageState);
if (pi == null) {
return null;
}
final ResolveInfo res = new ResolveInfo();
res.providerInfo = pi;
- if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
+ if ((customFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = filter;
}
res.priority = filter.getPriority();
@@ -1959,46 +1637,46 @@
return input.second.getIntentFilter();
}
- private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
- private long mFlags;
+ final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
}
- private static final class ServiceIntentResolver
+ public static final class ServiceIntentResolver
extends MimeGroupsAwareIntentResolver<Pair<ParsedService, ParsedIntentInfo>, ResolveInfo> {
// Default constructor
- ServiceIntentResolver() {
+ ServiceIntentResolver(@NonNull UserManagerService userManager) {
+ super(userManager);
}
// Copy constructor used in creating snapshots
- ServiceIntentResolver(ServiceIntentResolver orig) {
- copyFrom(orig);
+ ServiceIntentResolver(@NonNull ServiceIntentResolver orig,
+ @NonNull UserManagerService userManager) {
+ super(orig, userManager);
mServices.putAll(orig.mServices);
- mFlags = orig.mFlags;
}
@Override
- public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
- boolean defaultOnly, int userId) {
- mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
- return super.queryIntent(intent, resolvedType, defaultOnly, userId);
+ public List<ResolveInfo> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
+ String resolvedType, boolean defaultOnly, @UserIdInt int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ long flags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+ return super.queryIntent(snapshot, intent, resolvedType, defaultOnly, userId, flags);
}
- List<ResolveInfo> queryIntent(Intent intent, String resolvedType, long flags,
- int userId) {
- if (!sUserManager.exists(userId)) return null;
- mFlags = flags;
- return super.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId);
+ List<ResolveInfo> queryIntent(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, int userId) {
+ if (!mUserManager.exists(userId)) return null;
+ return super.queryIntent(computer, intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId, flags);
}
- List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
- long flags, List<ParsedService> packageServices, int userId) {
- if (!sUserManager.exists(userId)) return null;
+ List<ResolveInfo> queryIntentForPackage(@NonNull Computer computer, Intent intent,
+ String resolvedType, long flags, List<ParsedService> packageServices, int userId) {
+ if (!mUserManager.exists(userId)) return null;
if (packageServices == null) {
return Collections.emptyList();
}
- mFlags = flags;
final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
final int servicesSize = packageServices.size();
ArrayList<Pair<ParsedService, ParsedIntentInfo>[]> listCut =
@@ -2016,10 +1694,11 @@
listCut.add(array);
}
}
- return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
+ return super.queryIntentFromList(computer, intent, resolvedType,
+ defaultOnly, listCut, userId, flags);
}
- void addService(ParsedService s) {
+ void addService(@NonNull Computer computer, ParsedService s) {
mServices.put(s.getComponentName(), s);
if (DEBUG_SHOW_INFO) {
Log.v(TAG, " service:");
@@ -2037,7 +1716,7 @@
if (!intentFilter.debugCheck()) {
Log.w(TAG, "==> For Service " + s.getName());
}
- addFilter(Pair.create(s, intent));
+ addFilter(computer, Pair.create(s, intent));
}
}
@@ -2080,48 +1759,38 @@
}
@Override
- protected boolean isFilterStopped(Pair<ParsedService, ParsedIntentInfo> filter, int userId) {
- return ComponentResolver.isFilterStopped(filter, userId);
- }
-
- @Override
protected boolean isPackageForFilter(String packageName,
Pair<ParsedService, ParsedIntentInfo> info) {
return packageName.equals(info.first.getPackageName());
}
@Override
- protected ResolveInfo newResult(Pair<ParsedService, ParsedIntentInfo> pair, int match,
- int userId) {
- if (!sUserManager.exists(userId)) return null;
+ protected ResolveInfo newResult(@NonNull Computer computer,
+ Pair<ParsedService, ParsedIntentInfo> pair, int match, int userId,
+ long customFlags) {
+ if (!mUserManager.exists(userId)) return null;
ParsedService service = pair.first;
ParsedIntentInfo intentInfo = pair.second;
IntentFilter filter = intentInfo.getIntentFilter();
- AndroidPackage pkg = sPackageManagerInternal.getPackage(service.getPackageName());
- if (pkg == null) {
+ final PackageStateInternal packageState = computer.getPackageStateInternal(
+ service.getPackageName());
+ if (packageState == null || packageState.getPkg() == null
+ || !PackageStateUtils.isEnabledAndMatches(packageState, service, customFlags,
+ userId)) {
return null;
}
- if (!sPackageManagerInternal.isEnabledAndMatches(service, mFlags, userId)) {
- return null;
- }
-
- PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(service.getPackageName());
- if (ps == null) {
- return null;
- }
- final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
- ServiceInfo si = PackageInfoUtils.generateServiceInfo(pkg, service, mFlags,
- userState, userId, ps);
+ final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+ ServiceInfo si = PackageInfoUtils.generateServiceInfo(packageState.getPkg(), service,
+ customFlags, userState, userId, packageState);
if (si == null) {
return null;
}
final boolean matchVisibleToInstantApp =
- (mFlags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
- final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
+ (customFlags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
+ final boolean isInstantApp = (customFlags & PackageManager.MATCH_INSTANT) != 0;
// throw out filters that aren't visible to ephemeral apps
if (matchVisibleToInstantApp
&& !(filter.isVisibleToInstantApp() || userState.isInstantApp())) {
@@ -2133,12 +1802,12 @@
}
// throw out instant app filters if updates are available; will trigger
// instant app resolution
- if (userState.isInstantApp() && ps.isUpdateAvailable()) {
+ if (userState.isInstantApp() && packageState.isUpdateAvailable()) {
return null;
}
final ResolveInfo res = new ResolveInfo();
res.serviceInfo = si;
- if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
+ if ((customFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = filter;
}
res.priority = filter.getPriority();
@@ -2203,11 +1872,10 @@
}
// Keys are String (activity class name), values are Activity.
- private final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>();
- private long mFlags;
+ final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>();
}
- static final class InstantAppIntentResolver
+ public static final class InstantAppIntentResolver
extends IntentResolver<AuxiliaryResolveInfo.AuxiliaryFilter,
AuxiliaryResolveInfo.AuxiliaryFilter> {
/**
@@ -2224,6 +1892,13 @@
final ArrayMap<String, Pair<Integer, InstantAppResolveInfo>> mOrderResult =
new ArrayMap<>();
+ @NonNull
+ private final UserManagerService mUserManager;
+
+ public InstantAppIntentResolver(@NonNull UserManagerService userManager) {
+ mUserManager = userManager;
+ }
+
@Override
protected AuxiliaryResolveInfo.AuxiliaryFilter[] newArray(int size) {
return new AuxiliaryResolveInfo.AuxiliaryFilter[size];
@@ -2236,9 +1911,10 @@
}
@Override
- protected AuxiliaryResolveInfo.AuxiliaryFilter newResult(
- AuxiliaryResolveInfo.AuxiliaryFilter responseObj, int match, int userId) {
- if (!sUserManager.exists(userId)) {
+ protected AuxiliaryResolveInfo.AuxiliaryFilter newResult(@NonNull Computer computer,
+ AuxiliaryResolveInfo.AuxiliaryFilter responseObj, int match, int userId,
+ long customFlags) {
+ if (!mUserManager.exists(userId)) {
return null;
}
final String packageName = responseObj.resolveInfo.getPackageName();
@@ -2296,40 +1972,17 @@
}
}
- private static boolean isFilterStopped(Pair<? extends ParsedComponent, ParsedIntentInfo> pair,
- int userId) {
- if (!sUserManager.exists(userId)) {
- return true;
- }
-
- AndroidPackage pkg = sPackageManagerInternal.getPackage(pair.first.getPackageName());
- if (pkg == null) {
- return false;
- }
-
- PackageStateInternal ps =
- sPackageManagerInternal.getPackageStateInternal(pair.first.getPackageName());
- if (ps == null) {
- return false;
- }
-
- // System apps are never considered stopped for purposes of
- // filtering, because there may be no way for the user to
- // actually re-launch them.
- return !ps.isSystem() && ps.getUserStateOrDefault(userId).isStopped();
- }
-
/**
* Removes MIME type from the group, by delegating to IntentResolvers
* @return true if any intent filters were changed due to this update
*/
- boolean updateMimeGroup(String packageName, String group) {
+ public boolean updateMimeGroup(@NonNull Computer computer, String packageName, String group) {
boolean hasChanges = false;
synchronized (mLock) {
- hasChanges |= mActivities.updateMimeGroup(packageName, group);
- hasChanges |= mProviders.updateMimeGroup(packageName, group);
- hasChanges |= mReceivers.updateMimeGroup(packageName, group);
- hasChanges |= mServices.updateMimeGroup(packageName, group);
+ hasChanges |= mActivities.updateMimeGroup(computer, packageName, group);
+ hasChanges |= mProviders.updateMimeGroup(computer, packageName, group);
+ hasChanges |= mReceivers.updateMimeGroup(computer, packageName, group);
+ hasChanges |= mServices.updateMimeGroup(computer, packageName, group);
if (hasChanges) {
onChanged();
}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
new file mode 100644
index 0000000..b6f2b2a0
--- /dev/null
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.resolution;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.Computer;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+
+import java.util.List;
+
+public interface ComponentResolverApi {
+
+ boolean isActivityDefined(@NonNull ComponentName component);
+
+ @Nullable
+ ParsedActivity getActivity(@NonNull ComponentName component);
+
+ @Nullable
+ ParsedProvider getProvider(@NonNull ComponentName component);
+
+ @Nullable
+ ParsedActivity getReceiver(@NonNull ComponentName component);
+
+ @Nullable
+ ParsedService getService(@NonNull ComponentName component);
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ boolean componentExists(@NonNull ComponentName componentName);
+
+ @Nullable
+ List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
+ @UserIdInt int userId);
+
+ @Nullable
+ ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority, long flags,
+ @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
+ @UserIdInt int userId);
+
+ @Nullable
+ List<ProviderInfo> queryProviders(@NonNull Computer computer, @Nullable String processName,
+ @Nullable String metaDataKey, int uid, long flags, @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
+ @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId);
+
+ @Nullable
+ List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
+ @UserIdInt int userId);
+
+ void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames,
+ @NonNull List<ProviderInfo> outInfo, boolean safeMode, @UserIdInt int userId);
+}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
new file mode 100644
index 0000000..6b50fc6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.resolution;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import com.android.server.pm.Computer;
+import com.android.server.pm.UserManagerService;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+import com.android.server.utils.WatchableImpl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class ComponentResolverBase extends WatchableImpl implements ComponentResolverApi {
+
+ @NonNull
+ protected ComponentResolver.ActivityIntentResolver mActivities;
+
+ @NonNull
+ protected ComponentResolver.ProviderIntentResolver mProviders;
+
+ @NonNull
+ protected ComponentResolver.ReceiverIntentResolver mReceivers;
+
+ @NonNull
+ protected ComponentResolver.ServiceIntentResolver mServices;
+
+ /** Mapping from provider authority [first directory in content URI codePath) to provider. */
+ @NonNull
+ protected ArrayMap<String, ParsedProvider> mProvidersByAuthority;
+
+ @NonNull
+ protected UserManagerService mUserManager;
+
+ protected ComponentResolverBase(@NonNull UserManagerService userManager) {
+ mUserManager = userManager;
+ }
+
+ @Override
+ public boolean componentExists(@NonNull ComponentName componentName) {
+ ParsedMainComponent component = mActivities.mActivities.get(componentName);
+ if (component != null) {
+ return true;
+ }
+ component = mReceivers.mActivities.get(componentName);
+ if (component != null) {
+ return true;
+ }
+ component = mServices.mServices.get(componentName);
+ if (component != null) {
+ return true;
+ }
+ return mProviders.mProviders.get(componentName) != null;
+ }
+
+ @Nullable
+ @Override
+ public ParsedActivity getActivity(@NonNull ComponentName component) {
+ return mActivities.mActivities.get(component);
+ }
+
+ @Nullable
+ @Override
+ public ParsedProvider getProvider(@NonNull ComponentName component) {
+ return mProviders.mProviders.get(component);
+ }
+
+ @Nullable
+ @Override
+ public ParsedActivity getReceiver(@NonNull ComponentName component) {
+ return mReceivers.mActivities.get(component);
+ }
+
+ @Nullable
+ @Override
+ public ParsedService getService(@NonNull ComponentName component) {
+ return mServices.mServices.get(component);
+ }
+
+ /**
+ * Returns {@code true} if the given activity is defined by some package
+ */
+ @Override
+ public boolean isActivityDefined(@NonNull ComponentName component) {
+ return mActivities.mActivities.get(component) != null;
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, int userId) {
+ return mActivities.queryIntent(computer, intent, resolvedType, flags, userId);
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
+ int userId) {
+ return mActivities.queryIntentForPackage(computer, intent, resolvedType, flags, activities,
+ userId);
+ }
+
+ @Nullable
+ @Override
+ public ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority,
+ long flags, int userId) {
+ final ParsedProvider p = mProvidersByAuthority.get(authority);
+ if (p == null) {
+ return null;
+ }
+ PackageStateInternal packageState = computer.getPackageStateInternal(p.getPackageName());
+ if (packageState == null) {
+ return null;
+ }
+ final AndroidPackage pkg = packageState.getPkg();
+ if (pkg == null) {
+ return null;
+ }
+ final PackageUserStateInternal state = packageState.getUserStateOrDefault(userId);
+ ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
+ pkg, flags, state, userId, packageState);
+ if (appInfo == null) {
+ return null;
+ }
+ return PackageInfoUtils.generateProviderInfo(pkg, p, flags, state, appInfo, userId,
+ packageState);
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, int userId) {
+ return mProviders.queryIntent(computer, intent, resolvedType, flags, userId);
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
+ @UserIdInt int userId) {
+ return mProviders.queryIntentForPackage(computer, intent, resolvedType, flags, providers,
+ userId);
+ }
+
+ @Nullable
+ @Override
+ public List<ProviderInfo> queryProviders(@NonNull Computer computer,
+ @Nullable String processName, @Nullable String metaDataKey, int uid, long flags,
+ int userId) {
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ List<ProviderInfo> providerList = null;
+ PackageInfoUtils.CachedApplicationInfoGenerator appInfoGenerator = null;
+ for (int i = mProviders.mProviders.size() - 1; i >= 0; --i) {
+ final ParsedProvider p = mProviders.mProviders.valueAt(i);
+ if (p.getAuthority() == null) {
+ continue;
+ }
+
+ final PackageStateInternal ps = computer.getPackageStateInternal(p.getPackageName());
+ if (ps == null) {
+ continue;
+ }
+
+ AndroidPackage pkg = ps.getPkg();
+ if (pkg == null) {
+ continue;
+ }
+
+ if (processName != null && (!p.getProcessName().equals(processName)
+ || !UserHandle.isSameApp(pkg.getUid(), uid))) {
+ continue;
+ }
+ // See PM.queryContentProviders()'s javadoc for why we have the metaData parameter.
+ if (metaDataKey != null && !p.getMetaData().containsKey(metaDataKey)) {
+ continue;
+ }
+ if (appInfoGenerator == null) {
+ appInfoGenerator = new PackageInfoUtils.CachedApplicationInfoGenerator();
+ }
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
+ final ApplicationInfo appInfo =
+ appInfoGenerator.generate(pkg, flags, state, userId, ps);
+ if (appInfo == null) {
+ continue;
+ }
+
+ final ProviderInfo info = PackageInfoUtils.generateProviderInfo(
+ pkg, p, flags, state, appInfo, userId, ps);
+ if (info == null) {
+ continue;
+ }
+ if (providerList == null) {
+ providerList = new ArrayList<>(i + 1);
+ }
+ providerList.add(info);
+ }
+ return providerList;
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, int userId) {
+ return mReceivers.queryIntent(computer, intent, resolvedType, flags, userId);
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
+ @UserIdInt int userId) {
+ return mReceivers.queryIntentForPackage(computer, intent, resolvedType, flags, receivers,
+ userId);
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ return mServices.queryIntent(computer, intent, resolvedType, flags, userId);
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
+ @UserIdInt int userId) {
+ return mServices.queryIntentForPackage(computer, intent, resolvedType, flags, services,
+ userId);
+ }
+
+ @Override
+ public void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames,
+ @NonNull List<ProviderInfo> outInfo, boolean safeMode, int userId) {
+ PackageInfoUtils.CachedApplicationInfoGenerator appInfoGenerator = null;
+ for (int i = mProvidersByAuthority.size() - 1; i >= 0; --i) {
+ final ParsedProvider p = mProvidersByAuthority.valueAt(i);
+ if (!p.isSyncable()) {
+ continue;
+ }
+
+ final PackageStateInternal ps = computer.getPackageStateInternal(p.getPackageName());
+ if (ps == null) {
+ continue;
+ }
+
+ final AndroidPackage pkg = ps.getPkg();
+ if (pkg == null) {
+ continue;
+ }
+
+ if (safeMode && !pkg.isSystem()) {
+ continue;
+ }
+ if (appInfoGenerator == null) {
+ appInfoGenerator = new PackageInfoUtils.CachedApplicationInfoGenerator();
+ }
+ final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
+ final ApplicationInfo appInfo =
+ appInfoGenerator.generate(pkg, 0, state, userId, ps);
+ if (appInfo == null) {
+ continue;
+ }
+
+ final ProviderInfo info = PackageInfoUtils.generateProviderInfo(
+ pkg, p, 0, state, appInfo, userId, ps);
+ if (info == null) {
+ continue;
+ }
+ outNames.add(mProvidersByAuthority.keyAt(i));
+ outInfo.add(info);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
new file mode 100644
index 0000000..ecc53eb
--- /dev/null
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.resolution;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+
+import com.android.server.pm.Computer;
+import com.android.server.pm.PackageManagerTracedLock;
+import com.android.server.pm.UserManagerService;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+
+import java.util.List;
+
+public abstract class ComponentResolverLocked extends ComponentResolverBase {
+
+ protected final PackageManagerTracedLock mLock = new PackageManagerTracedLock();
+
+ protected ComponentResolverLocked(@NonNull UserManagerService userManager) {
+ super(userManager);
+ }
+
+ @Override
+ public boolean componentExists(@NonNull ComponentName componentName) {
+ synchronized (mLock) {
+ return super.componentExists(componentName);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ParsedActivity getActivity(@NonNull ComponentName component) {
+ synchronized (mLock) {
+ return super.getActivity(component);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ParsedProvider getProvider(@NonNull ComponentName component) {
+ synchronized (mLock) {
+ return super.getProvider(component);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ParsedActivity getReceiver(@NonNull ComponentName component) {
+ synchronized (mLock) {
+ return super.getReceiver(component);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ParsedService getService(@NonNull ComponentName component) {
+ synchronized (mLock) {
+ return super.getService(component);
+ }
+ }
+
+ @Override
+ public boolean isActivityDefined(@NonNull ComponentName component) {
+ synchronized (mLock) {
+ return super.isActivityDefined(component);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryActivities(computer, intent, resolvedType, flags, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryActivities(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> activities,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryActivities(computer, intent, resolvedType, flags, activities, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ProviderInfo queryProvider(@NonNull Computer computer, @NonNull String authority,
+ long flags, @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryProvider(computer, authority, flags, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryProviders(computer, intent, resolvedType, flags, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryProviders(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedProvider> providers,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryProviders(computer, intent, resolvedType, flags, providers, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ProviderInfo> queryProviders(@NonNull Computer computer,
+ @Nullable String processName, @Nullable String metaDataKey, int uid, long flags,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryProviders(computer, processName, metaDataKey, uid, flags, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryReceivers(computer, intent, resolvedType, flags, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryReceivers(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedActivity> receivers,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryReceivers(computer, intent, resolvedType, flags, receivers, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryServices(computer, intent, resolvedType, flags, userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<ResolveInfo> queryServices(@NonNull Computer computer, @NonNull Intent intent,
+ @Nullable String resolvedType, long flags, @NonNull List<ParsedService> services,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ return super.queryServices(computer, intent, resolvedType, flags, services, userId);
+ }
+ }
+
+ @Override
+ public void querySyncProviders(@NonNull Computer computer, @NonNull List<String> outNames,
+ @NonNull List<ProviderInfo> outInfo, boolean safeMode, @UserIdInt int userId) {
+ synchronized (mLock) {
+ super.querySyncProviders(computer, outNames, outInfo, safeMode, userId);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverSnapshot.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverSnapshot.java
new file mode 100644
index 0000000..3a96fe6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverSnapshot.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.resolution;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+
+import com.android.server.pm.UserManagerService;
+import com.android.server.pm.UserNeedsBadgingCache;
+
+public class ComponentResolverSnapshot extends ComponentResolverBase {
+
+ public ComponentResolverSnapshot(@NonNull ComponentResolver orig,
+ @NonNull UserNeedsBadgingCache userNeedsBadgingCache) {
+ super(UserManagerService.getInstance());
+ mActivities = new ComponentResolver.ActivityIntentResolver(orig.mActivities, mUserManager,
+ userNeedsBadgingCache);
+ mProviders = new ComponentResolver.ProviderIntentResolver(orig.mProviders, mUserManager);
+ mReceivers = new ComponentResolver.ReceiverIntentResolver(orig.mReceivers, mUserManager,
+ userNeedsBadgingCache);
+ mServices = new ComponentResolver.ServiceIntentResolver(orig.mServices, mUserManager);
+ mProvidersByAuthority = new ArrayMap<>(orig.mProvidersByAuthority);
+ }
+}
diff --git a/apex/media/aidl/private/android/media/Session2Command.aidl b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
similarity index 79%
copy from apex/media/aidl/private/android/media/Session2Command.aidl
copy to services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index 43a7b12..b091445 100644
--- a/apex/media/aidl/private/android/media/Session2Command.aidl
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.media;
+package com.android.server.pm.snapshot;
-parcelable Session2Command;
+public interface PackageDataSnapshot {
+}
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 18c45e4..4ad6ed1 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -33,6 +33,7 @@
import android.location.LocationManagerInternal;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
@@ -342,8 +343,11 @@
+ Intent.ACTION_ACTIVITY_RECOGNIZER + ", ignoring!");
return;
}
- final String tagsList = resolvedService.serviceInfo.metaData.getString(
- ACTIVITY_RECOGNITION_TAGS);
+ final Bundle metaData = resolvedService.serviceInfo.metaData;
+ if (metaData == null) {
+ return;
+ }
+ final String tagsList = metaData.getString(ACTIVITY_RECOGNITION_TAGS);
if (!TextUtils.isEmpty(tagsList)) {
PackageTagsList packageTagsList = new PackageTagsList.Builder(1).add(
resolvedService.serviceInfo.packageName,
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e523153..bd58472 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -128,6 +128,8 @@
import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -1175,7 +1177,10 @@
@Override
public void onBootPhase(int phase) {
synchronized (mLock) {
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ systemReady();
+
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
incrementBootCount();
} else if (phase == PHASE_BOOT_COMPLETED) {
@@ -1201,7 +1206,7 @@
}
}
- public void systemReady() {
+ private void systemReady() {
synchronized (mLock) {
mSystemReady = true;
mDreamManager = getLocalService(DreamManagerInternal.class);
@@ -4340,6 +4345,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
private void dumpInternal(PrintWriter pw) {
pw.println("POWER MANAGER (dumpsys power)\n");
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index ffcb2bd..b4613a7 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -276,7 +276,7 @@
}
/**
- * Called by {@link PowerManagerService#systemReady}, *with no lock held.*
+ * Called by {@link PowerManagerService#onBootPhase}, *with no lock held.*
*/
public void systemReady() {
ConcurrentUtils.wtfIfLockHeld(TAG, mLock);
diff --git a/services/core/java/com/android/server/sensorprivacy/PersistedState.java b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
index 06f5fc0..e79efdb8 100644
--- a/services/core/java/com/android/server/sensorprivacy/PersistedState.java
+++ b/services/core/java/com/android/server/sensorprivacy/PersistedState.java
@@ -296,7 +296,7 @@
SensorState sensorState = states.valueAt(i);
// Do not persist hardware toggle states. Will be restored on reboot
- if (userSensor.mType != SensorPrivacyManager.ToggleTypes.SOFTWARE) {
+ if (userSensor.mType != SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
continue;
}
@@ -478,7 +478,7 @@
for (int j = 0; j < numSensors; j++) {
int sensor = userIndividualEnabled.keyAt(j);
SensorState sensorState = userIndividualEnabled.valueAt(j);
- result.addState(SensorPrivacyManager.ToggleTypes.SOFTWARE,
+ result.addState(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
userId, sensor, sensorState.getState(), sensorState.getLastChange());
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 358f69e..a8e2d43 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -39,8 +39,8 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static android.os.UserHandle.USER_NULL;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
@@ -310,11 +310,12 @@
// Reset sensor privacy when restriction is added
if (!prevRestrictions.getBoolean(UserManager.DISALLOW_CAMERA_TOGGLE)
&& newRestrictions.getBoolean(UserManager.DISALLOW_CAMERA_TOGGLE)) {
- setToggleSensorPrivacyUnchecked(SOFTWARE, userId, OTHER, CAMERA, false);
+ setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, OTHER, CAMERA, false);
}
if (!prevRestrictions.getBoolean(UserManager.DISALLOW_MICROPHONE_TOGGLE)
&& newRestrictions.getBoolean(UserManager.DISALLOW_MICROPHONE_TOGGLE)) {
- setToggleSensorPrivacyUnchecked(SOFTWARE, userId, OTHER, MICROPHONE, false);
+ setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, OTHER, MICROPHONE,
+ false);
}
}
@@ -565,7 +566,7 @@
*/
private String getSensorUseActivityName(ArraySet<Integer> sensors) {
for (Integer sensor : sensors) {
- if (isToggleSensorPrivacyEnabled(HARDWARE, sensor)) {
+ if (isToggleSensorPrivacyEnabled(TOGGLE_TYPE_HARDWARE, sensor)) {
return mContext.getResources().getString(
R.string.config_sensorUseStartedActivity_hwToggle);
}
@@ -691,7 +692,7 @@
return;
}
- setToggleSensorPrivacyUnchecked(SOFTWARE, userId, source, sensor, enable);
+ setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable);
}
private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source,
@@ -866,8 +867,8 @@
@Override
public boolean isCombinedToggleSensorPrivacyEnabled(int sensor) {
- return isToggleSensorPrivacyEnabled(SOFTWARE, sensor) || isToggleSensorPrivacyEnabled(
- HARDWARE, sensor);
+ return isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, sensor)
+ || isToggleSensorPrivacyEnabled(TOGGLE_TYPE_HARDWARE, sensor);
}
private boolean isToggleSensorPrivacyEnabledInternal(int userId, int toggleType,
@@ -879,7 +880,7 @@
@Override
public boolean supportsSensorToggle(int toggleType, int sensor) {
- if (toggleType == SOFTWARE) {
+ if (toggleType == TOGGLE_TYPE_SOFTWARE) {
if (sensor == MICROPHONE) {
return mContext.getResources()
.getBoolean(R.bool.config_supportsMicToggle);
@@ -887,7 +888,7 @@
return mContext.getResources()
.getBoolean(R.bool.config_supportsCamToggle);
}
- } else if (toggleType == SensorPrivacyManager.ToggleTypes.HARDWARE) {
+ } else if (toggleType == TOGGLE_TYPE_HARDWARE) {
if (sensor == MICROPHONE) {
return mContext.getResources()
.getBoolean(R.bool.config_supportsHardwareMicToggle);
@@ -1003,37 +1004,41 @@
final int hwToggleIdx = 1;
// Get SW toggles state
mSensorPrivacyStateController.atomic(() -> {
- prevMicState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, SOFTWARE,
- MICROPHONE);
- prevCamState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, SOFTWARE,
- CAMERA);
- micState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, SOFTWARE,
- MICROPHONE);
- camState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, SOFTWARE, CAMERA);
+ prevMicState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_SOFTWARE, MICROPHONE);
+ prevCamState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_SOFTWARE, CAMERA);
+ micState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_SOFTWARE, MICROPHONE);
+ camState[swToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_SOFTWARE, CAMERA);
});
// Get HW toggles state
mSensorPrivacyStateController.atomic(() -> {
- prevMicState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, HARDWARE,
- MICROPHONE);
- prevCamState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from, HARDWARE,
- CAMERA);
- micState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, HARDWARE,
- MICROPHONE);
- camState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to, HARDWARE, CAMERA);
+ prevMicState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_HARDWARE, MICROPHONE);
+ prevCamState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(from,
+ TOGGLE_TYPE_HARDWARE, CAMERA);
+ micState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_HARDWARE, MICROPHONE);
+ camState[hwToggleIdx] = isToggleSensorPrivacyEnabledInternal(to,
+ TOGGLE_TYPE_HARDWARE, CAMERA);
});
if (from == USER_NULL || prevMicState[swToggleIdx] != micState[swToggleIdx]
|| prevMicState[hwToggleIdx] != micState[hwToggleIdx]) {
- mHandler.handleSensorPrivacyChanged(to, SOFTWARE, MICROPHONE,
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_SOFTWARE, MICROPHONE,
micState[swToggleIdx]);
- mHandler.handleSensorPrivacyChanged(to, HARDWARE, MICROPHONE,
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_HARDWARE, MICROPHONE,
micState[hwToggleIdx]);
setGlobalRestriction(MICROPHONE, micState[swToggleIdx] || micState[hwToggleIdx]);
}
if (from == USER_NULL || prevCamState[swToggleIdx] != camState[swToggleIdx]
|| prevCamState[hwToggleIdx] != camState[hwToggleIdx]) {
- mHandler.handleSensorPrivacyChanged(to, SOFTWARE, CAMERA, camState[swToggleIdx]);
- mHandler.handleSensorPrivacyChanged(to, HARDWARE, CAMERA, camState[hwToggleIdx]);
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_SOFTWARE, CAMERA,
+ camState[swToggleIdx]);
+ mHandler.handleSensorPrivacyChanged(to, TOGGLE_TYPE_HARDWARE, CAMERA,
+ camState[hwToggleIdx]);
setGlobalRestriction(CAMERA, camState[swToggleIdx] || camState[hwToggleIdx]);
}
}
@@ -1437,7 +1442,7 @@
public boolean isSensorPrivacyEnabled(int userId, int sensor) {
return SensorPrivacyService.this
.mSensorPrivacyServiceImpl.isToggleSensorPrivacyEnabledInternal(userId,
- SOFTWARE, sensor);
+ TOGGLE_TYPE_SOFTWARE, sensor);
}
@Override
@@ -1487,10 +1492,12 @@
userId = (userId == UserHandle.USER_CURRENT ? mCurrentUser : userId);
final int realUserId = (userId == UserHandle.USER_NULL ? mContext.getUserId() : userId);
- sps.setToggleSensorPrivacyUnchecked(HARDWARE, realUserId, OTHER, sensor, enable);
+ sps.setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_HARDWARE, realUserId, OTHER, sensor,
+ enable);
// Also disable the SW toggle when disabling the HW toggle
if (!enable) {
- sps.setToggleSensorPrivacyUnchecked(SOFTWARE, realUserId, OTHER, sensor, enable);
+ sps.setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, realUserId, OTHER, sensor,
+ enable);
}
}
}
@@ -1546,9 +1553,9 @@
if (!mIsInEmergencyCall) {
mIsInEmergencyCall = true;
if (mSensorPrivacyServiceImpl
- .isToggleSensorPrivacyEnabled(SOFTWARE, MICROPHONE)) {
+ .isToggleSensorPrivacyEnabled(TOGGLE_TYPE_SOFTWARE, MICROPHONE)) {
mSensorPrivacyServiceImpl.setToggleSensorPrivacyUnchecked(
- SOFTWARE, mCurrentUser, OTHER, MICROPHONE, false);
+ TOGGLE_TYPE_SOFTWARE, mCurrentUser, OTHER, MICROPHONE, false);
mMicUnmutedForEmergencyCall = true;
} else {
mMicUnmutedForEmergencyCall = false;
@@ -1574,7 +1581,7 @@
mIsInEmergencyCall = false;
if (mMicUnmutedForEmergencyCall) {
mSensorPrivacyServiceImpl.setToggleSensorPrivacyUnchecked(
- SOFTWARE, mCurrentUser, OTHER, MICROPHONE, true);
+ TOGGLE_TYPE_SOFTWARE, mCurrentUser, OTHER, MICROPHONE, true);
mMicUnmutedForEmergencyCall = false;
}
}
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 8ec0556..1b15351 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -197,6 +197,8 @@
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
+import com.android.server.PinnerService;
+import com.android.server.PinnerService.PinnedFileStats;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.MemoryStat;
@@ -727,6 +729,8 @@
return pullAccessibilityFloatingMenuStatsLocked(atomTag, data);
case FrameworkStatsLog.MEDIA_CAPABILITIES:
return pullMediaCapabilitiesStats(atomTag, data);
+ case FrameworkStatsLog.PINNED_FILE_SIZES_PER_PACKAGE:
+ return pullSystemServerPinnerStats(atomTag, data);
case FrameworkStatsLog.PENDING_INTENTS_PER_PACKAGE:
return pullPendingIntentsPerPackage(atomTag, data);
default:
@@ -926,6 +930,7 @@
registerAccessibilityFloatingMenuStats();
registerMediaCapabilitiesStats();
registerPendingIntentsPerPackagePuller();
+ registerPinnerServiceStats();
}
private void initAndRegisterNetworkStatsPullers() {
@@ -4607,6 +4612,26 @@
return StatsManager.PULL_SUCCESS;
}
+ private void registerPinnerServiceStats() {
+ int tagId = FrameworkStatsLog.PINNED_FILE_SIZES_PER_PACKAGE;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
+ int pullSystemServerPinnerStats(int atomTag, List<StatsEvent> pulledData) {
+ PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+ List<PinnedFileStats> pinnedFileStats = pinnerService.dumpDataForStatsd();
+ for (PinnedFileStats pfstats : pinnedFileStats) {
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ pfstats.uid, pfstats.filename, pfstats.sizeKb));
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
private byte[] toBytes(List<Integer> audioEncodings) {
ProtoOutputStream protoOutputStream = new ProtoOutputStream();
for (int audioEncoding : audioEncodings) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e02fabd..b95d372 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -102,6 +102,8 @@
import com.android.server.IoThread;
import com.android.server.SystemService;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -2639,6 +2641,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
@SuppressWarnings("resource")
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 8b80b4a..597f7f2 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -78,6 +78,7 @@
import android.os.Process;
import android.os.SystemClock;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Slog;
@@ -163,6 +164,14 @@
public class VcnGatewayConnection extends StateMachine {
private static final String TAG = VcnGatewayConnection.class.getSimpleName();
+ // Matches DataConnection.NETWORK_TYPE private constant, and magic string from
+ // ConnectivityManager#getNetworkTypeName()
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final String NETWORK_INFO_NETWORK_TYPE_STRING = "MOBILE";
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static final String NETWORK_INFO_EXTRA_INFO = "VCN";
+
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
@@ -1631,6 +1640,12 @@
final NetworkAgentConfig nac =
new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_MOBILE)
+ .setLegacyTypeName(NETWORK_INFO_NETWORK_TYPE_STRING)
+ .setLegacySubType(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+ .setLegacySubTypeName(
+ TelephonyManager.getNetworkTypeName(
+ TelephonyManager.NETWORK_TYPE_UNKNOWN))
+ .setLegacyExtraInfo(NETWORK_INFO_EXTRA_INFO)
.build();
final VcnNetworkAgent agent =
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index fdd9913..1f1f40b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -70,32 +70,42 @@
private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
- /** Callbacks for playing a {@link Vibration}. */
- interface VibrationCallbacks {
+ /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
+ interface VibratorManagerHooks {
/**
- * Callback triggered before starting a synchronized vibration step. This will be called
- * with {@code requiredCapabilities = 0} if no synchronization is required.
+ * Request the manager to prepare for triggering a synchronized vibration step.
*
* @param requiredCapabilities The required syncing capabilities for this preparation step.
- * Expects a combination of values from
+ * Expect CAP_SYNC and a combination of values from
* IVibratorManager.CAP_PREPARE_* and
* IVibratorManager.CAP_MIXED_TRIGGER_*.
* @param vibratorIds The id of the vibrators to be prepared.
*/
boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds);
- /** Callback triggered after synchronized vibrations were prepared. */
+ /**
+ * Request the manager to trigger a synchronized vibration. The vibration must already
+ * have been prepared with {@link #prepareSyncedVibration}.
+ */
boolean triggerSyncedVibration(long vibrationId);
- /** Callback triggered to cancel a prepared synced vibration. */
+ /** Tell the manager to cancel a synced vibration. */
void cancelSyncedVibration();
- /** Callback triggered when the vibration is complete. */
+ /**
+ * Tell the manager that the currently active vibration has completed its vibration, from
+ * the perspective of the Effect. However, the VibrationThread may still be continuing with
+ * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
+ * is called.
+ */
void onVibrationCompleted(long vibrationId, Vibration.Status status);
- /** Callback triggered when the vibrators are released after the thread is complete. */
- void onVibratorsReleased();
+ /**
+ * Tells the manager that the VibrationThread is finished with the previous vibration and
+ * all of its cleanup tasks, and the vibrators can now be used for another vibration.
+ */
+ void onVibrationThreadReleased();
}
private final Object mLock = new Object();
@@ -105,7 +115,7 @@
private final VibrationSettings mVibrationSettings;
private final DeviceVibrationEffectAdapter mDeviceEffectAdapter;
private final Vibration mVibration;
- private final VibrationCallbacks mCallbacks;
+ private final VibratorManagerHooks mVibratorManagerHooks;
private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
private final StepQueue mStepQueue = new StepQueue();
@@ -117,11 +127,11 @@
VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
DeviceVibrationEffectAdapter effectAdapter,
SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
- IBatteryStats batteryStatsService, VibrationCallbacks callbacks) {
+ IBatteryStats batteryStatsService, VibratorManagerHooks vibratorManagerHooks) {
mVibration = vib;
mVibrationSettings = vibrationSettings;
mDeviceEffectAdapter = effectAdapter;
- mCallbacks = callbacks;
+ mVibratorManagerHooks = vibratorManagerHooks;
mWorkSource = new WorkSource(mVibration.uid);
mWakeLock = wakeLock;
mBatteryStatsService = batteryStatsService;
@@ -163,7 +173,7 @@
clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
}
} finally {
- mCallbacks.onVibratorsReleased();
+ mVibratorManagerHooks.onVibrationThreadReleased();
}
}
@@ -263,7 +273,7 @@
private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
- mCallbacks.onVibrationCompleted(mVibration.id, completedStatus);
+ mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus);
}
}
@@ -272,25 +282,29 @@
try {
CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect());
final int sequentialEffectSize = sequentialEffect.getEffects().size();
- mStepQueue.offer(new StartVibrateStep(sequentialEffect));
+ mStepQueue.initializeForEffect(sequentialEffect);
- while (!mStepQueue.isEmpty()) {
- long waitTime;
+ while (!mStepQueue.isFinished()) {
+ long waitMillisBeforeNextStep;
synchronized (mLock) {
- waitTime = mStepQueue.calculateWaitTime();
- if (waitTime > 0) {
+ waitMillisBeforeNextStep = mStepQueue.getWaitMillisBeforeNextStep();
+ if (waitMillisBeforeNextStep > 0) {
try {
- mLock.wait(waitTime);
+ mLock.wait(waitMillisBeforeNextStep);
} catch (InterruptedException e) {
}
}
}
- // If we waited, the queue may have changed, so let the loop run again.
- if (waitTime <= 0) {
+ // Only run the next vibration step if we didn't have to wait in this loop.
+ // If we waited then the queue may have changed, so loop again to re-evaluate
+ // the scheduling of the queue top element.
+ if (waitMillisBeforeNextStep <= 0) {
if (DEBUG) {
Slog.d(TAG, "Play vibration consuming next step...");
}
- mStepQueue.consumeNext();
+ // Run the step without holding the main lock, to avoid HAL interactions from
+ // blocking the thread.
+ mStepQueue.runNextStep();
}
Vibration.Status status = mStop ? Vibration.Status.CANCELLED
: mStepQueue.calculateVibrationStatus(sequentialEffectSize);
@@ -350,7 +364,7 @@
}
if (segmentIndex < 0) {
// No more segments to play, last step is to complete the vibration on this vibrator.
- return new CompleteStep(startTime, /* cancelled= */ false, controller,
+ return new EffectCompleteStep(startTime, /* cancelled= */ false, controller,
vibratorOffTimeout);
}
@@ -385,7 +399,7 @@
@GuardedBy("mLock")
private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>();
@GuardedBy("mLock")
- private final Queue<Integer> mNotifiedVibrators = new LinkedList<>();
+ private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>();
@GuardedBy("mLock")
private int mPendingVibrateSteps;
@@ -394,18 +408,16 @@
@GuardedBy("mLock")
private int mSuccessfulVibratorOnSteps;
@GuardedBy("mLock")
- private boolean mWaitToProcessVibratorCallbacks;
+ private boolean mWaitToProcessVibratorCompleteCallbacks;
- public void offer(@NonNull Step step) {
+ public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) {
synchronized (mLock) {
- if (!step.isCleanUp()) {
- mPendingVibrateSteps++;
- }
- mNextSteps.offer(step);
+ mPendingVibrateSteps++;
+ mNextSteps.offer(new StartVibrateStep(vibration));
}
}
- public boolean isEmpty() {
+ public boolean isFinished() {
synchronized (mLock) {
return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty();
}
@@ -429,11 +441,11 @@
}
}
- /** Returns the time in millis to wait before calling {@link #consumeNext()}. */
- @GuardedBy("mLock")
- public long calculateWaitTime() {
+ /** Returns the time in millis to wait before calling {@link #runNextStep()}. */
+ @GuardedBy("VibrationThread.this.mLock")
+ public long getWaitMillisBeforeNextStep() {
if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
- // Steps anticipated by vibrator complete callback should be played right away.
+ // Steps resumed by vibrator complete callback should be played right away.
return 0;
}
Step nextStep = mNextSteps.peek();
@@ -444,7 +456,7 @@
* Play and remove the step at the top of this queue, and also adds the next steps generated
* to be played next.
*/
- public void consumeNext() {
+ public void runNextStep() {
// Vibrator callbacks should wait until the polled step is played and the next steps are
// added back to the queue, so they can handle the callback.
markWaitToProcessVibratorCallbacks();
@@ -472,7 +484,7 @@
}
} finally {
synchronized (mLock) {
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
}
@@ -485,10 +497,10 @@
*/
@GuardedBy("mLock")
public void notifyVibratorComplete(int vibratorId) {
- mNotifiedVibrators.offer(vibratorId);
- if (!mWaitToProcessVibratorCallbacks) {
+ mCompletionNotifiedVibrators.offer(vibratorId);
+ if (!mWaitToProcessVibratorCompleteCallbacks) {
// No step is being played or cancelled now, process the callback right away.
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
@@ -515,7 +527,7 @@
}
} finally {
synchronized (mLock) {
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
}
@@ -540,7 +552,7 @@
}
} finally {
synchronized (mLock) {
- processVibratorCallbacks();
+ processVibratorCompleteCallbacks();
}
}
}
@@ -548,7 +560,7 @@
@Nullable
private Step pollNext() {
synchronized (mLock) {
- // Prioritize the steps anticipated by a vibrator complete callback.
+ // Prioritize the steps resumed by a vibrator complete callback.
if (!mPendingOnVibratorCompleteSteps.isEmpty()) {
return mPendingOnVibratorCompleteSteps.poll();
}
@@ -558,29 +570,29 @@
private void markWaitToProcessVibratorCallbacks() {
synchronized (mLock) {
- mWaitToProcessVibratorCallbacks = true;
+ mWaitToProcessVibratorCompleteCallbacks = true;
}
}
/**
- * Notify the step in this queue that should be anticipated by the vibrator completion
- * callback and keep it separate to be consumed by {@link #consumeNext()}.
+ * Notify the step in this queue that should be resumed by the vibrator completion
+ * callback and keep it separate to be consumed by {@link #runNextStep()}.
*
* <p>This is a lightweight method that do not trigger any operation from {@link
* VibratorController}, so it can be called directly from a native callback.
*
* <p>This assumes only one of the next steps is waiting on this given vibrator, so the
- * first step found will be anticipated by this method, in no particular order.
+ * first step found will be resumed by this method, in no particular order.
*/
@GuardedBy("mLock")
- private void processVibratorCallbacks() {
- mWaitToProcessVibratorCallbacks = false;
- while (!mNotifiedVibrators.isEmpty()) {
- int vibratorId = mNotifiedVibrators.poll();
+ private void processVibratorCompleteCallbacks() {
+ mWaitToProcessVibratorCompleteCallbacks = false;
+ while (!mCompletionNotifiedVibrators.isEmpty()) {
+ int vibratorId = mCompletionNotifiedVibrators.poll();
Iterator<Step> it = mNextSteps.iterator();
while (it.hasNext()) {
Step step = it.next();
- if (step.shouldPlayWhenVibratorComplete(vibratorId)) {
+ if (step.acceptVibratorCompleteCallback(vibratorId)) {
it.remove();
mPendingOnVibratorCompleteSteps.offer(step);
break;
@@ -637,10 +649,10 @@
}
/**
- * Return true to play this step right after a vibrator has notified vibration completed,
- * used to anticipate steps waiting on vibrator callbacks with a timeout.
+ * Return true to run this step right after a vibrator has notified vibration completed,
+ * used to resume steps waiting on vibrator callbacks with a timeout.
*/
- public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ public boolean acceptVibratorCompleteCallback(int vibratorId) {
return false;
}
@@ -670,7 +682,7 @@
* add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
* all their individual steps.
*
- * <o>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
+ * <p>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
* sequential effect isn't finished yet.
*/
private final class StartVibrateStep extends Step {
@@ -805,7 +817,7 @@
boolean hasTriggered = false;
long maxDuration = 0;
try {
- hasPrepared = mCallbacks.prepareSyncedVibration(
+ hasPrepared = mVibratorManagerHooks.prepareSyncedVibration(
effectMapping.getRequiredSyncCapabilities(),
effectMapping.getVibratorIds());
@@ -821,13 +833,13 @@
// Check if sync was prepared and if any step was accepted by a vibrator,
// otherwise there is nothing to trigger here.
if (hasPrepared && maxDuration > 0) {
- hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id);
+ hasTriggered = mVibratorManagerHooks.triggerSyncedVibration(mVibration.id);
}
return maxDuration;
} finally {
if (hasPrepared && !hasTriggered) {
// Trigger has failed or all steps were ignored by the vibrators.
- mCallbacks.cancelSyncedVibration();
+ mVibratorManagerHooks.cancelSyncedVibration();
nextSteps.clear();
} else if (maxDuration < 0) {
// Some vibrator failed without being prepared so other vibrators might be
@@ -910,7 +922,7 @@
public final long vibratorOffTimeout;
long mVibratorOnResult;
- boolean mVibratorCallbackReceived;
+ boolean mVibratorCompleteCallbackReceived;
/**
* @param startTime The time to schedule this step in the {@link StepQueue}.
@@ -919,7 +931,7 @@
* @param index The index of the next segment to be played by this step
* @param vibratorOffTimeout The time the vibrator is expected to complete any previous
* vibration and turn off. This is used to allow this step to be
- * anticipated when the completion callback is triggered, and can
+ * triggered when the completion callback is received, and can
* be used play effects back-to-back.
*/
SingleVibratorStep(long startTime, VibratorController controller,
@@ -937,17 +949,17 @@
}
@Override
- public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ public boolean acceptVibratorCompleteCallback(int vibratorId) {
boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId;
- mVibratorCallbackReceived |= isSameVibrator;
- // Only anticipate this step if a timeout was set to wait for the vibration to complete,
+ mVibratorCompleteCallbackReceived |= isSameVibrator;
+ // Only activate this step if a timeout was set to wait for the vibration to complete,
// otherwise we are waiting for the correct time to play the next step.
return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis());
}
@Override
public List<Step> cancel() {
- return Arrays.asList(new CompleteStep(SystemClock.uptimeMillis(),
+ return Arrays.asList(new EffectCompleteStep(SystemClock.uptimeMillis(),
/* cancelled= */ true, controller, vibratorOffTimeout));
}
@@ -1205,10 +1217,10 @@
* <p>This runs right at the time the vibration is considered to end and will update the pending
* vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude.
*/
- private final class CompleteStep extends SingleVibratorStep {
+ private final class EffectCompleteStep extends SingleVibratorStep {
private final boolean mCancelled;
- CompleteStep(long startTime, boolean cancelled, VibratorController controller,
+ EffectCompleteStep(long startTime, boolean cancelled, VibratorController controller,
long vibratorOffTimeout) {
super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout);
mCancelled = cancelled;
@@ -1232,13 +1244,13 @@
@Override
public List<Step> play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteStep");
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "EffectCompleteStep");
try {
if (DEBUG) {
Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration"
+ " step on vibrator " + controller.getVibratorInfo().getId());
}
- if (mVibratorCallbackReceived) {
+ if (mVibratorCompleteCallbackReceived) {
// Vibration completion callback was received by this step, just turn if off
// and skip any clean-up.
stopVibrating();
@@ -1310,7 +1322,7 @@
Slog.d(TAG, "Ramp down the vibrator amplitude, step with "
+ latency + "ms latency.");
}
- if (mVibratorCallbackReceived) {
+ if (mVibratorCompleteCallbackReceived) {
// Vibration completion callback was received by this step, just turn if off
// and skip the rest of the steps to ramp down the vibrator amplitude.
stopVibrating();
@@ -1337,7 +1349,7 @@
* Represents a step to turn the vibrator off.
*
* <p>This runs after a timeout on the expected time the vibrator should have finished playing,
- * and can anticipated by vibrator complete callbacks.
+ * and can be brought forward by vibrator complete callbacks.
*/
private final class OffStep extends SingleVibratorStep {
@@ -1389,13 +1401,14 @@
}
@Override
- public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ public boolean acceptVibratorCompleteCallback(int vibratorId) {
if (controller.getVibratorInfo().getId() == vibratorId) {
- mVibratorCallbackReceived = true;
+ mVibratorCompleteCallbackReceived = true;
mNextOffTime = SystemClock.uptimeMillis();
}
- // Timings are tightly controlled here, so only anticipate if the vibrator was supposed
- // to be ON but has completed prematurely, to turn it back on as soon as possible.
+ // Timings are tightly controlled here, so only trigger this step if the vibrator was
+ // supposed to be ON but has completed prematurely, to turn it back on as soon as
+ // possible.
return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0;
}
@@ -1409,8 +1422,8 @@
Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
}
- if (mVibratorCallbackReceived && latency < 0) {
- // This step was anticipated because the vibrator turned off prematurely.
+ if (mVibratorCompleteCallbackReceived && latency < 0) {
+ // This step was run early because the vibrator turned off prematurely.
// Turn it back on and return this same step to run at the exact right time.
mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency);
return Arrays.asList(new AmplitudeStep(startTime, controller, effect,
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index b2e34da..63f3af3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -125,7 +125,8 @@
private final long mCapabilities;
private final int[] mVibratorIds;
private final SparseArray<VibratorController> mVibrators;
- private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
+ private final VibrationThreadCallbacks mVibrationThreadCallbacks =
+ new VibrationThreadCallbacks();
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
@@ -634,7 +635,7 @@
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService,
- mVibrationCallbacks);
+ mVibrationThreadCallbacks);
if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread);
@@ -1115,10 +1116,10 @@
}
/**
- * Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations
- * and reports them when finished.
+ * Implementation of {@link VibrationThread.VibratorManagerHooks} that controls synced
+ * vibrations and reports them when finished.
*/
- private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
+ private final class VibrationThreadCallbacks implements VibrationThread.VibratorManagerHooks {
@Override
public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) {
@@ -1153,7 +1154,7 @@
}
@Override
- public void onVibratorsReleased() {
+ public void onVibrationThreadReleased() {
if (DEBUG) {
Slog.d(TAG, "Vibrators released after finished vibration");
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 76e1c43..a4a200d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -756,6 +756,19 @@
}
}
+ @Override
+ public void setPreferDockBigOverlays(IBinder token, boolean preferDockBigOverlays) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ r.setPreferDockBigOverlays(preferDockBigOverlays);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
/**
* Splash screen view is attached to activity.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dc53cc6..b0efa5b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -348,6 +348,8 @@
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
+import dalvik.annotation.optimization.NeverCompile;
+
import com.google.android.collect.Sets;
import org.xmlpull.v1.XmlPullParserException;
@@ -530,6 +532,7 @@
// activity can enter picture in picture while pausing (only when switching to another task)
PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build();
// The PiP params used when deferring the entering of picture-in-picture.
+ boolean preferDockBigOverlays;
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last launch of this activity
ComponentName requestedVrComponent; // the requested component for handling VR mode.
@@ -625,6 +628,10 @@
// it references to gets removed. This should also be cleared when we move out of pip.
private Task mLastParentBeforePip;
+ // Only set if this instance is a launch-into-pip Activity, points to the
+ // host Activity the launch-into-pip Activity is originated from.
+ private ActivityRecord mLaunchIntoPipHostActivity;
+
boolean firstWindowDrawn;
/** Whether the visible window(s) of this activity is drawn. */
private boolean mReportedDrawn;
@@ -942,6 +949,7 @@
}
};
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
final long now = SystemClock.uptimeMillis();
@@ -1221,6 +1229,9 @@
if (mLastParentBeforePip != null) {
pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
}
+ if (mLaunchIntoPipHostActivity != null) {
+ pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);
+ }
mLetterboxUiController.dump(pw, prefix);
@@ -1555,10 +1566,16 @@
/**
* Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
* {@link #getTask()} is set before this is called.
+ *
+ * @param launchIntoPipHostActivity {@link ActivityRecord} as the host Activity for the
+ * launch-int-pip Activity see also {@link #mLaunchIntoPipHostActivity}.
*/
- void setLastParentBeforePip() {
- mLastParentBeforePip = getTask();
+ void setLastParentBeforePip(@Nullable ActivityRecord launchIntoPipHostActivity) {
+ mLastParentBeforePip = (launchIntoPipHostActivity == null)
+ ? getTask()
+ : launchIntoPipHostActivity.getTask();
mLastParentBeforePip.mChildPipActivity = this;
+ mLaunchIntoPipHostActivity = launchIntoPipHostActivity;
}
private void clearLastParentBeforePip() {
@@ -1566,12 +1583,17 @@
mLastParentBeforePip.mChildPipActivity = null;
mLastParentBeforePip = null;
}
+ mLaunchIntoPipHostActivity = null;
}
@Nullable Task getLastParentBeforePip() {
return mLastParentBeforePip;
}
+ @Nullable ActivityRecord getLaunchIntoPipHostActivity() {
+ return mLaunchIntoPipHostActivity;
+ }
+
private void updateColorTransform() {
if (mSurfaceControl != null && mLastAppSaturationInfo != null) {
getPendingTransaction().setColorTransform(mSurfaceControl,
@@ -1852,6 +1874,10 @@
mRotationAnimationHint = rotationAnimation;
}
+ if (options.getLaunchIntoPipParams() != null) {
+ pictureInPictureArgs = options.getLaunchIntoPipParams();
+ }
+
mOverrideTaskTransition = options.getOverrideTaskTransition();
}
@@ -1956,6 +1982,8 @@
mLetterboxUiController = new LetterboxUiController(mWmService, this);
mCameraCompatControlEnabled = mWmService.mContext.getResources()
.getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+ preferDockBigOverlays = mWmService.mContext.getResources()
+ .getBoolean(R.bool.config_dockBigOverlayWindows);
if (_createTime > 0) {
createTime = _createTime;
@@ -2509,7 +2537,6 @@
}
removeStartingWindowAnimation(true /* prepareAnimation */);
- // TODO(b/215316431): Add tests
final Task task = getTask();
if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
&& task != null) {
@@ -7689,7 +7716,6 @@
* once the starting window is removed in {@link #removeStartingWindow}).
* </ul>
*/
- // TODO(b/215316431): Add tests
boolean isEligibleForLetterboxEducation() {
return mWmService.mLetterboxConfiguration.getIsEducationEnabled()
&& mIsEligibleForFixedOrientationLetterbox
@@ -9430,6 +9456,11 @@
getTask().getRootTask().onPictureInPictureParamsChanged();
}
+ void setPreferDockBigOverlays(boolean preferDockBigOverlays) {
+ this.preferDockBigOverlays = preferDockBigOverlays;
+ getTask().getRootTask().onPreferDockBigOverlaysChanged();
+ }
+
@Override
boolean isSyncFinished() {
if (!super.isSyncFinished()) return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5ffe214..ef0ee12 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1896,6 +1896,14 @@
mSupervisor.handleNonResizableTaskIfNeeded(startedTask,
mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask);
+ // If Activity's launching into PiP, move the mStartActivity immediately to pinned mode.
+ // Note that mStartActivity and source should be in the same Task at this point.
+ if (mOptions != null && mOptions.isLaunchIntoPip()
+ && sourceRecord != null && sourceRecord.getTask() == mStartActivity.getTask()) {
+ mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,
+ sourceRecord, "launch-into-pip");
+ }
+
return START_SUCCESS;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4000595..0497477 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1468,7 +1468,7 @@
a.packageName = process.mInfo.packageName;
a.applicationInfo = process.mInfo;
- a.processName = process.mInfo.processName;
+ a.processName = process.mName;
a.uiOptions = process.mInfo.uiOptions;
a.taskAffinity = "android:" + a.packageName + "/dream";
@@ -3538,8 +3538,8 @@
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final float expandedAspectRatio = r.pictureInPictureArgs.getExpandedAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
- mRootWindowContainer.moveActivityToPinnedRootTask(
- r, "enterPictureInPictureMode");
+ mRootWindowContainer.moveActivityToPinnedRootTask(r,
+ null /* launchIntoPipHostActivity */, "enterPictureInPictureMode");
final Task task = r.getTask();
task.setPictureInPictureAspectRatio(aspectRatio, expandedAspectRatio);
task.setPictureInPictureActions(actions);
@@ -3922,11 +3922,11 @@
@Override
public void onPictureInPictureStateChanged(PictureInPictureUiState pipState) {
enforceTaskPermission("onPictureInPictureStateChanged");
- final Task rootPinnedStask = mRootWindowContainer.getDefaultTaskDisplayArea()
+ final Task rootPinnedTask = mRootWindowContainer.getDefaultTaskDisplayArea()
.getRootPinnedTask();
- if (rootPinnedStask != null && rootPinnedStask.getTopMostActivity() != null) {
+ if (rootPinnedTask != null && rootPinnedTask.getTopMostActivity() != null) {
mWindowManager.mAtmService.mActivityClientController.onPictureInPictureStateChanged(
- rootPinnedStask.getTopMostActivity(), pipState);
+ rootPinnedTask.getTopMostActivity(), pipState);
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 33b807b..9893f68 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -106,12 +106,6 @@
synchronized (task.mWmService.mGlobalLock) {
activityRecord = task.topRunningActivity();
- if(!activityRecord.info.applicationInfo.isOnBackInvokedCallbackEnabled()) {
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Activity %s: enableOnBackInvokedCallback=false."
- + " Returning null BackNavigationInfo.", activityRecord.getName());
- return null;
- }
-
removedWindowContainer = activityRecord;
taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration;
WindowState window = task.getWindow(WindowState::isFocused);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 132396b..0868111 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -652,7 +652,9 @@
void prepareSurfaces() {
mDimmer.resetDimStates();
super.prepareSurfaces();
+ // Bounds need to be relative, as the dim layer is a child.
getBounds(mTmpDimBoundsRect);
+ mTmpDimBoundsRect.offsetTo(0 /* newLeft */, 0 /* newTop */);
// If SystemUI is dragging for recents, we want to reset the dim state so any dim layer
// on the display level fades out.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e845034..ddfdddc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -457,8 +457,6 @@
*/
private boolean mLastWallpaperVisible = false;
- private Rect mBaseDisplayRect = new Rect();
-
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
@@ -488,9 +486,6 @@
private final Rect mTmpRect2 = new Rect();
private final Region mTmpRegion = new Region();
- /** Used for handing back size of display */
- private final Rect mTmpBounds = new Rect();
-
private final Configuration mTmpConfiguration = new Configuration();
/** Remove this display when animation on it has completed. */
@@ -1362,8 +1357,8 @@
return mDisplayRotation;
}
- void setInsetProvider(@InternalInsetsType int type, WindowState win,
- @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider){
+ void setInsetProvider(@InternalInsetsType int type, WindowContainer win,
+ @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider) {
setInsetProvider(type, win, frameProvider, null /* imeFrameProvider */);
}
@@ -1377,10 +1372,10 @@
* @param imeFrameProvider Function to compute the frame when dispatching insets to the IME, or
* {@code null} if the normal frame should be taken.
*/
- void setInsetProvider(@InternalInsetsType int type, WindowState win,
- @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,
- @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {
- mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider,
+ void setInsetProvider(@InternalInsetsType int type, WindowContainer win,
+ @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider,
+ @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider) {
+ mInsetsStateController.getSourceProvider(type).setWindowContainer(win, frameProvider,
imeFrameProvider);
}
@@ -2080,8 +2075,6 @@
mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
mDisplayInfo);
- mBaseDisplayRect.set(0, 0, dw, dh);
-
if (isDefaultDisplay) {
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
mCompatDisplayMetrics);
@@ -2213,14 +2206,14 @@
*/
void computeScreenConfiguration(Configuration config) {
final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
- calculateBounds(displayInfo, mTmpBounds);
- config.windowConfiguration.setBounds(mTmpBounds);
- config.windowConfiguration.setMaxBounds(mTmpBounds);
+ final int dw = displayInfo.logicalWidth;
+ final int dh = displayInfo.logicalHeight;
+ mTmpRect.set(0, 0, dw, dh);
+ config.windowConfiguration.setBounds(mTmpRect);
+ config.windowConfiguration.setMaxBounds(mTmpRect);
config.windowConfiguration.setWindowingMode(getWindowingMode());
config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode,
displayInfo.displayCutout);
@@ -2844,10 +2837,6 @@
/** Update base (override) display metrics. */
void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
- final int originalWidth = mBaseDisplayWidth;
- final int originalHeight = mBaseDisplayHeight;
- final int originalDensity = mBaseDisplayDensity;
-
mBaseDisplayWidth = baseWidth;
mBaseDisplayHeight = baseHeight;
mBaseDisplayDensity = baseDensity;
@@ -2867,12 +2856,6 @@
+ mBaseDisplayHeight + " on display:" + getDisplayId());
}
}
-
- if (mBaseDisplayWidth != originalWidth || mBaseDisplayHeight != originalHeight
- || mBaseDisplayDensity != originalDensity) {
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
- updateBounds();
- }
}
/**
@@ -3021,7 +3004,7 @@
if (focusedTask == null) {
mTouchExcludeRegion.setEmpty();
} else {
- mTouchExcludeRegion.set(mBaseDisplayRect);
+ mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
mTmpRect.setEmpty();
mTmpRect2.setEmpty();
@@ -3822,7 +3805,7 @@
final int imePid = mInputMethodWindow.mSession.mPid;
mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer);
}
- mInsetsStateController.getSourceProvider(ITYPE_IME).setWindow(win,
+ mInsetsStateController.getSourceProvider(ITYPE_IME).setWindowContainer(win,
mDisplayPolicy.getImeSourceFrameProvider(), null /* imeFrameProvider */);
computeImeTarget(true /* updateImeTarget */);
updateImeControlTarget();
@@ -4587,25 +4570,6 @@
}
}
- private void updateBounds() {
- calculateBounds(mDisplayInfo, mTmpBounds);
- setBounds(mTmpBounds);
- }
-
- // Determines the current display bounds based on the current state
- private void calculateBounds(DisplayInfo displayInfo, Rect out) {
- // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
- final int rotation = displayInfo.rotation;
- boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
- final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
- int width = displayInfo.logicalWidth;
- int left = (physWidth - width) / 2;
- int height = displayInfo.logicalHeight;
- int top = (physHeight - height) / 2;
- out.set(left, top, left + width, top + height);
- }
-
private void getBounds(Rect out, @Rotation int rotation) {
getBounds(out);
@@ -5561,8 +5525,6 @@
}
void onDisplayChanged() {
- mDisplay.getRealSize(mTmpDisplaySize);
- setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
final int lastDisplayState = mDisplayInfo.state;
updateDisplayInfo();
@@ -6441,6 +6403,8 @@
@Override
public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ // It is only needed when freezing display in legacy transition.
+ if (mTransitionController.isShellTransitionsEnabled()) return;
continueUpdateOrientationForDiffOrienLaunchingApp();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 30fffd3..4148d8b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1106,8 +1106,8 @@
break;
case TYPE_STATUS_BAR:
mStatusBar = win;
- final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider =
- (displayFrames, windowState, rect) -> {
+ final TriConsumer<DisplayFrames, WindowContainer, Rect> gestureFrameProvider =
+ (displayFrames, windowContainer, rect) -> {
rect.bottom = rect.top + getStatusBarHeight(displayFrames);
final DisplayCutout cutout =
displayFrames.mInsetsState.getDisplayCutout();
@@ -1128,24 +1128,25 @@
case TYPE_NAVIGATION_BAR:
mNavigationBar = win;
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
- (displayFrames, windowState, inOutFrame) -> {
+ (displayFrames, windowContainer, inOutFrame) -> {
if (!mNavButtonForcedVisible) {
- inOutFrame.inset(windowState.getLayoutingAttrs(
+ inOutFrame.inset(win.getLayoutingAttrs(
displayFrames.mRotation).providedInternalInsets);
inOutFrame.inset(win.mGivenContentInsets);
}
},
// For IME we use regular frame.
- (displayFrames, windowState, inOutFrame) ->
- inOutFrame.set(windowState.getFrame()));
+ (displayFrames, windowContainer, inOutFrame) -> {
+ inOutFrame.set(win.getFrame());
+ });
mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win,
- (displayFrames, windowState, inOutFrame) -> {
+ (displayFrames, windowContainer, inOutFrame) -> {
inOutFrame.top -= mBottomGestureAdditionalInset;
});
mDisplayContent.setInsetProvider(ITYPE_LEFT_GESTURES, win,
- (displayFrames, windowState, inOutFrame) -> {
+ (displayFrames, windowContainer, inOutFrame) -> {
final int leftSafeInset =
Math.max(displayFrames.mDisplayCutoutSafe.left, 0);
inOutFrame.left = 0;
@@ -1154,7 +1155,7 @@
inOutFrame.right = leftSafeInset + mLeftGestureInset;
});
mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
- (displayFrames, windowState, inOutFrame) -> {
+ (displayFrames, windowContainer, inOutFrame) -> {
final int rightSafeInset =
Math.min(displayFrames.mDisplayCutoutSafe.right,
displayFrames.mUnrestricted.right);
@@ -1164,8 +1165,8 @@
inOutFrame.right = displayFrames.mDisplayWidth;
});
mDisplayContent.setInsetProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT, win,
- (displayFrames, windowState, inOutFrame) -> {
- if ((windowState.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
+ (displayFrames, windowContainer, inOutFrame) -> {
+ if ((win.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
|| mNavigationBarLetsThroughTaps) {
inOutFrame.setEmpty();
}
@@ -1176,11 +1177,13 @@
default:
if (attrs.providesInsetsTypes != null) {
for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
- final TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider =
+ final TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider =
!attrs.providedInternalImeInsets.equals(Insets.NONE)
- ? (displayFrames, windowState, inOutFrame) ->
- inOutFrame.inset(windowState.getLayoutingAttrs(
- displayFrames.mRotation).providedInternalImeInsets)
+ ? (displayFrames, windowContainer, inOutFrame) -> {
+ inOutFrame.inset(win.getLayoutingAttrs(
+ displayFrames.mRotation)
+ .providedInternalImeInsets);
+ }
: null;
switch (insetsType) {
case ITYPE_STATUS_BAR:
@@ -1201,10 +1204,9 @@
break;
}
mDisplayContent.setInsetProvider(insetsType, win, (displayFrames,
- windowState, inOutFrame) -> {
- inOutFrame.inset(
- windowState.getLayoutingAttrs(displayFrames.mRotation)
- .providedInternalInsets);
+ windowContainer, inOutFrame) -> {
+ inOutFrame.inset(win.getLayoutingAttrs(
+ displayFrames.mRotation).providedInternalInsets);
inOutFrame.inset(win.mGivenContentInsets);
}, imeFrameProvider);
mInsetsSourceWindowsExceptIme.add(win);
@@ -1230,8 +1232,13 @@
}
}
- TriConsumer<DisplayFrames, WindowState, Rect> getImeSourceFrameProvider() {
- return (displayFrames, windowState, inOutFrame) -> {
+ TriConsumer<DisplayFrames, WindowContainer, Rect> getImeSourceFrameProvider() {
+ return (displayFrames, windowContainer, inOutFrame) -> {
+ WindowState windowState = windowContainer.asWindowState();
+ if (windowState == null) {
+ throw new IllegalArgumentException("IME insets must be provided by a window.");
+ }
+
if (mNavigationBar != null && navigationBarPosition(displayFrames.mRotation)
== NAV_BAR_BOTTOM) {
// In gesture navigation, nav bar frame is larger than frame to calculate insets.
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index cbefe7f..8f97220 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -113,8 +113,8 @@
private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
if (caller.getWindow().getTask().isOrganized()) {
- mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask(
- caller.getWindow().getTask());
+ mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
+ .reportImeDrawnOnTask(caller.getWindow().getTask());
}
}
}
@@ -173,12 +173,18 @@
}
void checkShowImePostLayout() {
+ if (mWindowContainer == null) {
+ return;
+ }
+ WindowState windowState = mWindowContainer.asWindowState();
+ if (windowState == null) {
+ throw new IllegalArgumentException("IME insets must be provided by a window.");
+ }
// check if IME is drawn
if (mIsImeLayoutDrawn
|| (isReadyToShowIme()
- && mWin != null
- && mWin.isDrawn()
- && !mWin.mGivenInsetsPending)) {
+ && windowState.isDrawn()
+ && !windowState.mGivenInsetsPending)) {
mIsImeLayoutDrawn = true;
// show IME if InputMethodService requested it to be shown.
if (mShowImeRunner != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d28dfd5..433f1071 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -19,24 +19,36 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
+import android.app.WindowConfiguration;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.util.ArrayMap;
import android.util.IntArray;
import android.util.SparseArray;
import android.view.InsetsAnimationControlCallbacks;
@@ -164,8 +176,9 @@
}
boolean isHidden(@InternalInsetsType int type) {
- final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
- return provider != null && provider.hasWindow() && !provider.getSource().isVisible();
+ final InsetsSourceProvider provider = mStateController.peekSourceProvider(type);
+ return provider != null && provider.hasWindowContainer()
+ && !provider.getSource().isVisible();
}
void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) {
@@ -235,10 +248,11 @@
}
/**
- * @see InsetsStateController#getInsetsForWindow
+ * Adjusts the sources in {@code originalState} to account for things like transient bars, IME
+ * & rounded corners.
*/
- InsetsState getInsetsForWindow(WindowState target, boolean includesTransient) {
- final InsetsState originalState = mStateController.getInsetsForWindow(target);
+ InsetsState adjustInsetsForWindow(WindowState target, InsetsState originalState,
+ boolean includesTransient) {
InsetsState state;
if (!includesTransient) {
state = adjustVisibilityForTransientTypes(originalState);
@@ -249,17 +263,131 @@
return adjustInsetsForRoundedCorners(target, state, state == originalState);
}
- InsetsState getInsetsForWindow(WindowState target) {
- return getInsetsForWindow(target, false);
+ InsetsState adjustInsetsForWindow(WindowState target, InsetsState originalState) {
+ return adjustInsetsForWindow(target, originalState, false);
+ }
+
+ /**
+ * @see WindowState#getInsetsState()
+ */
+ InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
+ final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
+ final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
+ if (token != null) {
+ final InsetsState rotatedState = token.getFixedRotationTransformInsetsState();
+ if (rotatedState != null) {
+ return rotatedState;
+ }
+ }
+ final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
+ // Always use windowing mode fullscreen when get insets for window metrics to make sure it
+ // contains all insets types.
+ final InsetsState originalState = mDisplayContent.getInsetsPolicy()
+ .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
+ mStateController.getRawInsetsState());
+ return adjustVisibilityForTransientTypes(originalState);
+ }
+
+ private static @InternalInsetsType int getInsetsTypeForLayoutParams(
+ WindowManager.LayoutParams attrs) {
+ @WindowManager.LayoutParams.WindowType int type = attrs.type;
+ switch (type) {
+ case TYPE_STATUS_BAR:
+ return ITYPE_STATUS_BAR;
+ case TYPE_NAVIGATION_BAR:
+ return ITYPE_NAVIGATION_BAR;
+ case TYPE_INPUT_METHOD:
+ return ITYPE_IME;
+ }
+
+ // If not one of the types above, check whether an internal inset mapping is specified.
+ if (attrs.providesInsetsTypes != null) {
+ for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+ switch (insetsType) {
+ case ITYPE_STATUS_BAR:
+ case ITYPE_NAVIGATION_BAR:
+ case ITYPE_CLIMATE_BAR:
+ case ITYPE_EXTRA_NAVIGATION_BAR:
+ return insetsType;
+ }
+ }
+ }
+
+ return ITYPE_INVALID;
}
/**
- * @see InsetsStateController#getInsetsForWindowMetrics
+ * Modifies the given {@code state} according to the {@code type} (Inset type) provided by
+ * the target.
+ * When performing layout of the target or dispatching insets to the target, we need to exclude
+ * sources which should not be visible to the target. e.g., the source which represents the
+ * target window itself, and the IME source when the target is above IME. We also need to
+ * exclude certain types of insets source for client within specific windowing modes.
+ *
+ * @param type the inset type provided by the target
+ * @param windowingMode the windowing mode of the target
+ * @param isAlwaysOnTop is the target always on top
+ * @param state the input inset state containing all the sources
+ * @return The state stripped of the necessary information.
*/
- InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final InsetsState originalState = mStateController.getInsetsForWindowMetrics(attrs);
- return adjustVisibilityForTransientTypes(originalState);
+ InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
+ @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
+ InsetsState state) {
+ boolean stateCopied = false;
+
+ if (type != ITYPE_INVALID) {
+ state = new InsetsState(state);
+ stateCopied = true;
+ state.removeSource(type);
+
+ // Navigation bar doesn't get influenced by anything else
+ if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
+ state.removeSource(ITYPE_IME);
+ state.removeSource(ITYPE_STATUS_BAR);
+ state.removeSource(ITYPE_CLIMATE_BAR);
+ state.removeSource(ITYPE_CAPTION_BAR);
+ state.removeSource(ITYPE_NAVIGATION_BAR);
+ state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ }
+
+ // Status bar doesn't get influenced by caption bar
+ if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
+ state.removeSource(ITYPE_CAPTION_BAR);
+ }
+
+ // IME needs different frames for certain cases (e.g. navigation bar in gesture nav).
+ if (type == ITYPE_IME) {
+ ArrayMap<Integer, InsetsSourceProvider> providers = mStateController
+ .getSourceProviders();
+ for (int i = providers.size() - 1; i >= 0; i--) {
+ InsetsSourceProvider otherProvider = providers.valueAt(i);
+ if (otherProvider.overridesImeFrame()) {
+ InsetsSource override =
+ new InsetsSource(
+ state.getSource(otherProvider.getSource().getType()));
+ override.setFrame(otherProvider.getImeOverrideFrame());
+ state.addSource(override);
+ }
+ }
+ }
+ }
+
+ if (WindowConfiguration.isFloating(windowingMode)
+ || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
+ if (!stateCopied) {
+ state = new InsetsState(state);
+ stateCopied = true;
+ }
+ state.removeSource(ITYPE_STATUS_BAR);
+ state.removeSource(ITYPE_NAVIGATION_BAR);
+ state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ state.removeSource(ITYPE_IME);
+ }
+ }
+
+ return state;
}
private InsetsState adjustVisibilityForTransientTypes(InsetsState originalState) {
@@ -644,7 +772,7 @@
}
mAnimatingShown = show;
- final InsetsState state = getInsetsForWindow(mFocusedWin);
+ final InsetsState state = mFocusedWin.getInsetsState();
// We are about to playing the default animation. Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 21eea94..4c7a297 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -69,7 +69,7 @@
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
- protected WindowState mWin;
+ protected WindowContainer mWindowContainer;
private final Rect mTmpRect = new Rect();
private final InsetsStateController mStateController;
@@ -80,8 +80,8 @@
private @Nullable InsetsControlTarget mFakeControlTarget;
private @Nullable ControlAdapter mAdapter;
- private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider;
- private TriConsumer<DisplayFrames, WindowState, Rect> mImeFrameProvider;
+ private TriConsumer<DisplayFrames, WindowContainer, Rect> mFrameProvider;
+ private TriConsumer<DisplayFrames, WindowContainer, Rect> mImeFrameProvider;
private final Rect mImeOverrideFrame = new Rect();
private boolean mIsLeashReadyForDispatching;
private final Rect mLastSourceFrame = new Rect();
@@ -100,7 +100,8 @@
private boolean mClientVisible;
/**
- * Whether the window is available and considered visible as in {@link WindowState#isVisible}.
+ * Whether the window container is available and considered visible as in
+ * {@link WindowContainer#isVisible}.
*/
private boolean mServerVisible;
@@ -109,8 +110,8 @@
private final boolean mControllable;
/**
- * Whether to forced the dimensions of the source window to the inset frame and crop out any
- * overflow.
+ * Whether to forced the dimensions of the source window container to the inset frame and crop
+ * out any overflow.
* Used to crop the taskbar inset source when a task animation is occurring to hide the taskbar
* rounded corners overlays.
*
@@ -152,42 +153,42 @@
}
/**
- * Updates the window that currently backs this source.
+ * Updates the window container that currently backs this source.
*
- * @param win The window that links to this source.
+ * @param windowContainer The window container that links to this source.
* @param frameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to clients.
* @param imeFrameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to IME.
*/
- void setWindow(@Nullable WindowState win,
- @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,
- @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {
- if (mWin != null) {
+ void setWindowContainer(@Nullable WindowContainer windowContainer,
+ @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider,
+ @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider) {
+ if (mWindowContainer != null) {
if (mControllable) {
- mWin.setControllableInsetProvider(null);
+ mWindowContainer.setControllableInsetProvider(null);
}
- // The window may be animating such that we can hand out the leash to the control
- // target. Revoke the leash by cancelling the animation to correct the state.
+ // The window container may be animating such that we can hand out the leash to the
+ // control target. Revoke the leash by cancelling the animation to correct the state.
// TODO: Ideally, we should wait for the animation to finish so previous window can
// animate-out as new one animates-in.
- mWin.cancelAnimation();
- mWin.mProvidedInsetsSources.remove(mSource.getType());
+ mWindowContainer.cancelAnimation();
+ mWindowContainer.getProvidedInsetsSources().remove(mSource.getType());
mSeamlessRotating = false;
}
- ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", win,
- InsetsState.typeToString(mSource.getType()));
- mWin = win;
+ ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
+ windowContainer, InsetsState.typeToString(mSource.getType()));
+ mWindowContainer = windowContainer;
mFrameProvider = frameProvider;
mImeFrameProvider = imeFrameProvider;
- if (win == null) {
+ if (windowContainer == null) {
setServerVisible(false);
mSource.setFrame(new Rect());
mSource.setVisibleFrame(null);
} else {
- mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
+ mWindowContainer.getProvidedInsetsSources().put(mSource.getType(), mSource);
if (mControllable) {
- mWin.setControllableInsetProvider(this);
+ mWindowContainer.setControllableInsetProvider(this);
if (mPendingControlTarget != null) {
updateControlForTarget(mPendingControlTarget, true /* force */);
mPendingControlTarget = null;
@@ -197,18 +198,39 @@
}
/**
- * @return Whether there is a window which backs this source.
+ * @return Whether there is a window container which backs this source.
*/
- boolean hasWindow() {
- return mWin != null;
+ boolean hasWindowContainer() {
+ return mWindowContainer != null;
}
/**
* The source frame can affect the layout of other windows, so this should be called once the
- * window gets laid out.
+ * window container gets laid out.
*/
void updateSourceFrame() {
- if (mWin == null || mWin.mGivenInsetsPending) {
+ if (mWindowContainer == null) {
+ return;
+ }
+ WindowState win = mWindowContainer.asWindowState();
+
+ if (win == null) {
+ // For all the non window WindowContainers.
+ if (mServerVisible) {
+ mTmpRect.set(mWindowContainer.getBounds());
+ if (mFrameProvider != null) {
+ mFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames,
+ mWindowContainer, mTmpRect);
+ }
+ } else {
+ mTmpRect.setEmpty();
+ }
+ mSource.setFrame(mTmpRect);
+ mSource.setVisibleFrame(null);
+ return;
+ }
+
+ if (win.mGivenInsetsPending) {
// If the given insets are pending, they are not reliable for now. The source frame
// should be updated after the new given insets are sent to window manager.
return;
@@ -218,11 +240,12 @@
// frame may not yet determined that server side doesn't think the window is ready to
// visible. (i.e. No surface, pending insets that were given during layout, etc..)
if (mServerVisible) {
- mTmpRect.set(mWin.getFrame());
+ mTmpRect.set(win.getFrame());
if (mFrameProvider != null) {
- mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect);
+ mFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames,
+ mWindowContainer, mTmpRect);
} else {
- mTmpRect.inset(mWin.mGivenContentInsets);
+ mTmpRect.inset(win.mGivenContentInsets);
}
} else {
mTmpRect.setEmpty();
@@ -230,15 +253,17 @@
mSource.setFrame(mTmpRect);
if (mImeFrameProvider != null) {
- mImeOverrideFrame.set(mWin.getFrame());
- mImeFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin,
+ mImeOverrideFrame.set(win.getFrame());
+ mImeFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames,
+ mWindowContainer,
mImeOverrideFrame);
}
- if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
- || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
- mTmpRect.set(mWin.getFrame());
- mTmpRect.inset(mWin.mGivenVisibleInsets);
+ if (win.mGivenVisibleInsets.left != 0 || win.mGivenVisibleInsets.top != 0
+ || win.mGivenVisibleInsets.right != 0
+ || win.mGivenVisibleInsets.bottom != 0) {
+ mTmpRect.set(win.getFrame());
+ mTmpRect.inset(win.mGivenVisibleInsets);
mSource.setVisibleFrame(mTmpRect);
} else {
mSource.setVisibleFrame(null);
@@ -253,7 +278,7 @@
source.setVisible(mSource.isVisible());
mTmpRect.set(winFrame);
if (mFrameProvider != null) {
- mFrameProvider.accept(displayFrames, mWin, mTmpRect);
+ mFrameProvider.accept(displayFrames, mWindowContainer, mTmpRect);
}
source.setFrame(mTmpRect);
return source;
@@ -263,27 +288,30 @@
* Called when a layout pass has occurred.
*/
void onPostLayout() {
- if (mWin == null) {
+ if (mWindowContainer == null) {
return;
}
-
- setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy());
+ WindowState windowState = mWindowContainer.asWindowState();
+ boolean isServerVisible = windowState != null
+ ? windowState.wouldBeVisibleIfPolicyIgnored() && windowState.isVisibleByPolicy()
+ : mWindowContainer.isVisibleRequested();
+ setServerVisible(isServerVisible);
updateSourceFrame();
if (mControl != null) {
boolean changed = false;
final Point position = getWindowFrameSurfacePosition();
if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
changed = true;
- if (mWin.getWindowFrames().didFrameSizeChange() && mWin.mWinAnimator.getShown()
- && mWin.okToDisplay()) {
- mWin.applyWithNextDraw(mSetLeashPositionConsumer);
+ if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
+ && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
+ windowState.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
- mSetLeashPositionConsumer.accept(mWin.getSyncTransaction());
+ mSetLeashPositionConsumer.accept(mWindowContainer.getSyncTransaction());
}
}
if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
final Insets insetsHint = mSource.calculateInsets(
- mWin.getBounds(), true /* ignoreVisibility */);
+ mWindowContainer.getBounds(), true /* ignoreVisibility */);
if (!insetsHint.equals(mControl.getInsetsHint())) {
changed = true;
mControl.setInsetsHint(insetsHint);
@@ -297,17 +325,19 @@
}
private Point getWindowFrameSurfacePosition() {
+ WindowState win = mWindowContainer.asWindowState();
if (mControl != null) {
final AsyncRotationController controller =
- mWin.mDisplayContent.getAsyncRotationController();
- if (controller != null && controller.shouldFreezeInsetsPosition(mWin)) {
+ win.mDisplayContent.getAsyncRotationController();
+ if (controller != null && controller.shouldFreezeInsetsPosition(win)) {
// Use previous position because the fade-out animation runs in old rotation.
return mControl.getSurfacePosition();
}
}
- final Rect frame = mWin.getFrame();
+ final Rect frame = mWindowContainer.asWindowState() != null
+ ? mWindowContainer.asWindowState().getFrame() : mWindowContainer.getBounds();
final Point position = new Point();
- mWin.transformFrameToSurfacePosition(frame.left, frame.top, position);
+ mWindowContainer.transformFrameToSurfacePosition(frame.left, frame.top, position);
return position;
}
@@ -322,8 +352,8 @@
}
/**
- * Ensures that the inset source window is cropped so that anything that doesn't fit within the
- * inset frame is cropped out until removeCropToProvidingInsetsBounds is called.
+ * Ensures that the inset source window container is cropped so that anything that doesn't fit
+ * within the inset frame is cropped out until removeCropToProvidingInsetsBounds is called.
*
* The inset source surface will get cropped to the be of the size of the insets it's providing.
*
@@ -342,9 +372,10 @@
void setCropToProvidingInsetsBounds(Transaction t) {
mCropToProvidingInsets = true;
- if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) {
+ if (mWindowContainer != null && mWindowContainer.mSurfaceAnimator.hasLeash()) {
// apply to existing leash
- t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, getProvidingInsetsBoundsCropRect());
+ t.setWindowCrop(mWindowContainer.mSurfaceAnimator.mLeash,
+ getProvidingInsetsBoundsCropRect());
}
}
@@ -359,13 +390,15 @@
mCropToProvidingInsets = false;
// apply to existing leash
- if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) {
- t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, null);
+ if (mWindowContainer != null && mWindowContainer.mSurfaceAnimator.hasLeash()) {
+ t.setWindowCrop(mWindowContainer.mSurfaceAnimator.mLeash, null);
}
}
private Rect getProvidingInsetsBoundsCropRect() {
- Rect sourceWindowFrame = mWin.getFrame();
+ Rect sourceWindowFrame = mWindowContainer.asWindowState() != null
+ ? mWindowContainer.asWindowState().getFrame()
+ : mWindowContainer.getBounds();
Rect insetFrame = getSource().getFrame();
// The rectangle in buffer space we want to crop to
@@ -384,11 +417,11 @@
return;
}
- if (mWin != null && mWin.getSurfaceControl() == null) {
+ if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) {
// if window doesn't have a surface, set it null and return.
- setWindow(null, null, null);
+ setWindowContainer(null, null, null);
}
- if (mWin == null) {
+ if (mWindowContainer == null) {
mPendingControlTarget = target;
return;
}
@@ -397,7 +430,7 @@
}
if (target == null) {
// Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
- mWin.cancelAnimation();
+ mWindowContainer.cancelAnimation();
setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
return;
}
@@ -407,7 +440,7 @@
setClientVisible(target.getRequestedVisibility(mSource.getType()));
}
final Transaction t = mDisplayContent.getSyncTransaction();
- mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
+ mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
ANIMATION_TYPE_INSETS_CONTROL);
// The leash was just created. We cannot dispatch it until its surface transaction is
@@ -418,16 +451,16 @@
mControlTarget = target;
updateVisibility();
mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition,
- mSource.calculateInsets(mWin.getBounds(), true /* ignoreVisibility */));
+ mSource.calculateInsets(mWindowContainer.getBounds(), true /* ignoreVisibility */));
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
}
void startSeamlessRotation() {
- if (!mSeamlessRotating) {
- mSeamlessRotating = true;
- mWin.cancelAnimation();
- }
+ if (!mSeamlessRotating) {
+ mSeamlessRotating = true;
+ mWindowContainer.cancelAnimation();
+ }
}
void finishSeamlessRotation() {
@@ -475,10 +508,13 @@
}
private boolean isMirroredSource() {
- if (mWin == null) {
+ if (mWindowContainer == null) {
return false;
}
- final int[] provides = mWin.mAttrs.providesInsetsTypes;
+ if (mWindowContainer.asWindowState() == null) {
+ return false;
+ }
+ final int[] provides = ((WindowState) mWindowContainer).mAttrs.providesInsetsTypes;
if (provides == null) {
return false;
}
@@ -542,9 +578,9 @@
pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
pw.print(" mImeOverrideFrame="); pw.print(mImeOverrideFrame.toShortString());
pw.println();
- if (mWin != null) {
- pw.print(prefix + "mWin=");
- pw.println(mWin);
+ if (mWindowContainer != null) {
+ pw.print(prefix + "mWindowContainer=");
+ pw.println(mWindowContainer);
}
if (mAdapter != null) {
pw.print(prefix + "mAdapter=");
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index a1468cc..32e70d9 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -16,30 +16,20 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.systemGestures;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.WindowConfiguration;
-import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Rect;
import android.os.Trace;
import android.util.ArrayMap;
@@ -49,8 +39,6 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams.WindowType;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -103,134 +91,6 @@
mDisplayContent = displayContent;
}
- /**
- * Gets the insets state from the perspective of the target. When performing layout of the
- * target or dispatching insets to the target, we need to exclude sources which should not be
- * visible to the target. e.g., the source which represents the target window itself, and the
- * IME source when the target is above IME. We also need to exclude certain types of insets
- * source for client within specific windowing modes.
- * This is to get the insets for a window layout on the screen. If the window is not there, use
- * the {@link #getInsetsForWindowMetrics} to get insets instead.
- *
- * @param target The window associate with the perspective.
- * @return The state stripped of the necessary information.
- */
- InsetsState getInsetsForWindow(@NonNull WindowState target) {
- final InsetsState rotatedState = target.mToken.getFixedRotationTransformInsetsState();
- if (rotatedState != null) {
- return rotatedState;
- }
- final InsetsSourceProvider provider = target.getControllableInsetProvider();
- final @InternalInsetsType int type = provider != null
- ? provider.getSource().getType() : ITYPE_INVALID;
- return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
- target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() :
- (target.mAttrs.receiveInsetsIgnoringZOrder ? mState :
- target.mAboveInsetsState));
- }
-
- InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
- final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
- if (token != null) {
- final InsetsState rotatedState = token.getFixedRotationTransformInsetsState();
- if (rotatedState != null) {
- return rotatedState;
- }
- }
- final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
- // Always use windowing mode fullscreen when get insets for window metrics to make sure it
- // contains all insets types.
- return getInsetsForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop, mState);
- }
-
- private static @InternalInsetsType
- int getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs) {
- @WindowType int type = attrs.type;
- switch (type) {
- case TYPE_STATUS_BAR:
- return ITYPE_STATUS_BAR;
- case TYPE_NAVIGATION_BAR:
- return ITYPE_NAVIGATION_BAR;
- case TYPE_INPUT_METHOD:
- return ITYPE_IME;
- }
-
- // If not one of the types above, check whether an internal inset mapping is specified.
- if (attrs.providesInsetsTypes != null) {
- for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
- switch (insetsType) {
- case ITYPE_STATUS_BAR:
- case ITYPE_NAVIGATION_BAR:
- case ITYPE_CLIMATE_BAR:
- case ITYPE_EXTRA_NAVIGATION_BAR:
- return insetsType;
- }
- }
- }
-
- return ITYPE_INVALID;
- }
-
- /**
- * @see #getInsetsForWindow
- * @see #getInsetsForWindowMetrics
- */
- private InsetsState getInsetsForTarget(@InternalInsetsType int type,
- @WindowingMode int windowingMode, boolean isAlwaysOnTop, InsetsState state) {
- boolean stateCopied = false;
-
- if (type != ITYPE_INVALID) {
- state = new InsetsState(state);
- stateCopied = true;
- state.removeSource(type);
-
- // Navigation bar doesn't get influenced by anything else
- if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
- state.removeSource(ITYPE_STATUS_BAR);
- state.removeSource(ITYPE_CLIMATE_BAR);
- state.removeSource(ITYPE_CAPTION_BAR);
- state.removeSource(ITYPE_NAVIGATION_BAR);
- state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
- }
-
- // Status bar doesn't get influenced by caption bar
- if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
- state.removeSource(ITYPE_CAPTION_BAR);
- }
-
- // IME needs different frames for certain cases (e.g. navigation bar in gesture nav).
- if (type == ITYPE_IME) {
- for (int i = mProviders.size() - 1; i >= 0; i--) {
- InsetsSourceProvider otherProvider = mProviders.valueAt(i);
- if (otherProvider.overridesImeFrame()) {
- InsetsSource override =
- new InsetsSource(
- state.getSource(otherProvider.getSource().getType()));
- override.setFrame(otherProvider.getImeOverrideFrame());
- state.addSource(override);
- }
- }
- }
- }
-
- if (WindowConfiguration.isFloating(windowingMode)
- || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
- if (!stateCopied) {
- state = new InsetsState(state);
- stateCopied = true;
- }
- state.removeSource(ITYPE_STATUS_BAR);
- state.removeSource(ITYPE_NAVIGATION_BAR);
- state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
- if (windowingMode == WINDOWING_MODE_PINNED) {
- state.removeSource(ITYPE_IME);
- }
- }
-
- return state;
- }
-
InsetsState getRawInsetsState() {
return mState;
}
@@ -248,6 +108,10 @@
return result;
}
+ ArrayMap<Integer, InsetsSourceProvider> getSourceProviders() {
+ return mProviders;
+ }
+
/**
* @return The provider of a specific type.
*/
@@ -303,7 +167,7 @@
final InsetsState aboveInsetsState = new InsetsState();
aboveInsetsState.set(mState,
displayCutout() | systemGestures() | mandatorySystemGestures());
- final SparseArray<InsetsSource> winProvidedSources = win.mProvidedInsetsSources;
+ final SparseArray<InsetsSource> winProvidedSources = win.getProvidedInsetsSources();
final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>();
mDisplayContent.forAllWindows(w -> {
if (aboveWin[0]) {
@@ -315,7 +179,7 @@
}
return winProvidedSources.size() == 0;
} else {
- final SparseArray<InsetsSource> providedSources = w.mProvidedInsetsSources;
+ final SparseArray<InsetsSource> providedSources = w.getProvidedInsetsSources();
for (int i = providedSources.size() - 1; i >= 0; i--) {
aboveInsetsState.addSource(providedSources.valueAt(i));
}
@@ -382,7 +246,7 @@
final InsetsState state = displayFrames.mInsetsState;
for (int i = mProviders.size() - 1; i >= 0; i--) {
final InsetsSourceProvider provider = mProviders.valueAt(i);
- if (provider.mWin == win) {
+ if (provider.mWindowContainer == win) {
state.addSource(provider.createSimulatedSource(displayFrames, winFrame));
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d535018..8ab2ee0 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1979,7 +1979,8 @@
onTop);
}
- void moveActivityToPinnedRootTask(ActivityRecord r, String reason) {
+ void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
+ @Nullable ActivityRecord launchIntoPipHostActivity, String reason) {
mService.deferWindowLayout();
final TaskDisplayArea taskDisplayArea = r.getDisplayArea();
@@ -2030,7 +2031,7 @@
.setHasBeenVisible(true)
.build();
// Establish bi-directional link between the original and pinned task.
- r.setLastParentBeforePip();
+ r.setLastParentBeforePip(launchIntoPipHostActivity);
// It's possible the task entering PIP is in freeform, so save the last
// non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
// to its previous freeform bounds.
@@ -2091,6 +2092,10 @@
r.setWindowingMode(intermediateWindowingMode);
r.mWaitForEnteringPinnedMode = true;
rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
+ // Set the launch bounds for launch-into-pip Activity on the root task.
+ if (r.getOptions() != null && r.getOptions().isLaunchIntoPip()) {
+ rootTask.setBounds(r.getOptions().getLaunchBounds());
+ }
rootTask.setDeferTaskAppear(false);
// Reset the state that indicates it can enter PiP while pausing after we've moved it
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d1460f4..6bb63d9 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -108,15 +108,12 @@
*/
private SurfaceControl mBackColorSurface;
private BlackFrame mEnteringBlackFrame;
- private int mWidth, mHeight;
private final int mOriginalRotation;
private final int mOriginalWidth;
private final int mOriginalHeight;
private int mCurRotation;
- private Rect mOriginalDisplayRect = new Rect();
- private Rect mCurrentDisplayRect = new Rect();
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
// rotations.
@@ -127,7 +124,6 @@
private boolean mAnimRunning;
private boolean mFinishAnimReady;
private long mFinishAnimStartTime;
- private boolean mForceDefaultOrientation;
private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
/** Intensity of light/whiteness of the layout before rotation occurs. */
private float mStartLuma;
@@ -138,25 +134,13 @@
mService = displayContent.mWmService;
mContext = mService.mContext;
mDisplayContent = displayContent;
- displayContent.getBounds(mOriginalDisplayRect);
+ final Rect currentBounds = displayContent.getBounds();
+ final int width = currentBounds.width();
+ final int height = currentBounds.height();
// Screenshot does NOT include rotation!
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int realOriginalRotation = displayInfo.rotation;
- final int originalWidth;
- final int originalHeight;
- if (displayContent.getDisplayRotation().isFixedToUserRotation()) {
- // Emulated orientation.
- mForceDefaultOrientation = true;
- originalWidth = displayContent.mBaseDisplayWidth;
- originalHeight = displayContent.mBaseDisplayHeight;
- } else {
- // Normal situation
- originalWidth = displayInfo.logicalWidth;
- originalHeight = displayInfo.logicalHeight;
- }
- mWidth = originalWidth;
- mHeight = originalHeight;
mOriginalRotation = originalRotation;
// If the delta is not zero, the rotation of display may not change, but we still want to
@@ -165,8 +149,8 @@
// when restoring the rotated app to the same rotation as current display.
final int delta = deltaRotation(originalRotation, realOriginalRotation);
final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
- mOriginalWidth = flipped ? originalHeight : originalWidth;
- mOriginalHeight = flipped ? originalWidth : originalHeight;
+ mOriginalWidth = flipped ? height : width;
+ mOriginalHeight = flipped ? width : height;
mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
// Check whether the current screen contains any secure content.
@@ -179,7 +163,7 @@
new SurfaceControl.LayerCaptureArgs.Builder(displayContent.getSurfaceControl())
.setCaptureSecureLayers(true)
.setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, mWidth, mHeight))
+ .setSourceCrop(new Rect(0, 0, width, height))
.build();
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
SurfaceControl.captureLayers(args);
@@ -244,6 +228,19 @@
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
+ // If display size is changed with the same orientation, map the bounds of screenshot to
+ // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction
+ // are merged to global transaction, so it can be synced with display change when calling
+ // DisplayManagerInternal#performTraversal(transaction).
+ final int logicalWidth = displayInfo.logicalWidth;
+ final int logicalHeight = displayInfo.logicalHeight;
+ if (logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
+ && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight)) {
+ displayContent.getPendingTransaction().setGeometry(mScreenshotLayer,
+ new Rect(0, 0, mOriginalWidth, mOriginalHeight),
+ new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0);
+ }
+
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" FREEZE %s: CREATE", mScreenshotLayer);
if (originalRotation == realOriginalRotation) {
@@ -277,11 +274,6 @@
matrix.getValues(mTmpFloats);
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
- if (mForceDefaultOrientation) {
- mDisplayContent.getBounds(mCurrentDisplayRect);
- x -= mCurrentDisplayRect.left;
- y -= mCurrentDisplayRect.top;
- }
t.setPosition(mScreenshotLayer, x, y);
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
@@ -293,8 +285,6 @@
public void printTo(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
- pw.print(" mWidth="); pw.print(mWidth);
- pw.print(" mHeight="); pw.println(mHeight);
pw.print(prefix);
pw.print("mEnteringBlackFrame=");
pw.println(mEnteringBlackFrame);
@@ -315,11 +305,6 @@
pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
mSnapshotInitialMatrix.dump(pw); pw.println();
- pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
- if (mForceDefaultOrientation) {
- pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString());
- pw.print(" mCurrentDisplayRect="); pw.println(mCurrentDisplayRect.toShortString());
- }
}
public void setRotation(SurfaceControl.Transaction t, int rotation) {
@@ -329,7 +314,8 @@
// to the snapshot to make it stay in the same original position
// with the current screen rotation.
int delta = deltaRotation(rotation, mOriginalRotation);
- RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+ RotationAnimationUtils.createRotationMatrix(delta, mOriginalWidth, mOriginalHeight,
+ mSnapshotInitialMatrix);
setRotationTransform(t, mSnapshotInitialMatrix);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f7c5b26..358a615 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3397,6 +3397,13 @@
info.positionInParent = getRelativePosition();
info.pictureInPictureParams = getPictureInPictureParams(top);
+ info.preferDockBigOverlays = getPreferDockBigOverlays();
+ if (info.pictureInPictureParams != null
+ && info.pictureInPictureParams.isLaunchIntoPip()
+ && top.getTopMostActivity().getLastParentBeforePip() != null) {
+ info.launchIntoPipHostTaskId =
+ top.getTopMostActivity().getLastParentBeforePip().mTaskId;
+ }
info.displayCutoutInsets = top != null ? top.getDisplayCutoutInsets() : null;
info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info
@@ -3443,6 +3450,11 @@
? null : new PictureInPictureParams(topMostActivity.pictureInPictureArgs);
}
+ private boolean getPreferDockBigOverlays() {
+ final ActivityRecord topMostActivity = getTopMostActivity();
+ return topMostActivity != null && topMostActivity.preferDockBigOverlays;
+ }
+
Rect getDisplayCutoutInsets() {
if (mDisplayContent == null || getDisplayInfo().displayCutout == null) return null;
final WindowState w = getTopVisibleAppMainWindow();
@@ -4336,6 +4348,10 @@
}
}
+ void onPreferDockBigOverlaysChanged() {
+ dispatchTaskInfoChangedIfNeeded(true /* force */);
+ }
+
/** Called when the top activity in the Root Task enters or exits size compat mode. */
void onSizeCompatActivityChanged() {
// Trigger TaskInfoChanged to update the size compat restart button.
@@ -6566,8 +6582,7 @@
return;
}
if (mOverlayHost != null) {
- final InsetsState s = getDisplayContent().getInsetsPolicy()
- .getInsetsForWindow(originalChange, true);
+ final InsetsState s = originalChange.getInsetsState(true);
getBounds(mTmpRect);
mOverlayHost.dispatchInsetsChanged(s, mTmpRect);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 1bba103..f0cca18 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -18,7 +18,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -111,9 +110,6 @@
private Task mRootHomeTask;
private Task mRootPinnedTask;
- // TODO(b/159029784): Remove when getStack() behavior is cleaned-up
- private Task mRootRecentsTask;
-
private final ArrayList<WindowContainer> mTmpAlwaysOnTopChildren = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>();
@@ -222,8 +218,6 @@
Task getRootTask(int windowingMode, int activityType) {
if (activityType == ACTIVITY_TYPE_HOME) {
return mRootHomeTask;
- } else if (activityType == ACTIVITY_TYPE_RECENTS) {
- return mRootRecentsTask;
}
if (windowingMode == WINDOWING_MODE_PINNED) {
return mRootPinnedTask;
@@ -249,11 +243,6 @@
return mRootHomeTask;
}
- @Nullable
- Task getRootRecentsTask() {
- return mRootRecentsTask;
- }
-
Task getRootPinnedTask() {
return mRootPinnedTask;
}
@@ -288,16 +277,6 @@
} else {
mRootHomeTask = rootTask;
}
- } else if (rootTask.isActivityTypeRecents()) {
- if (mRootRecentsTask != null) {
- if (!rootTask.isDescendantOf(mRootRecentsTask)) {
- throw new IllegalArgumentException("addRootTaskReferenceIfNeeded: root recents"
- + " task=" + mRootRecentsTask + " already exist on display=" + this
- + " rootTask=" + rootTask);
- }
- } else {
- mRootRecentsTask = rootTask;
- }
}
if (!rootTask.isRootTask()) {
@@ -317,8 +296,6 @@
void removeRootTaskReferenceIfNeeded(Task rootTask) {
if (rootTask == mRootHomeTask) {
mRootHomeTask = null;
- } else if (rootTask == mRootRecentsTask) {
- mRootRecentsTask = null;
} else if (rootTask == mRootPinnedTask) {
mRootPinnedTask = null;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index dd1f29e..7fab94c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -31,6 +31,7 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -82,6 +83,7 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -530,6 +532,11 @@
* certificate.</li>
*/
private boolean isAllowedToEmbedActivityInTrustedMode(@NonNull ActivityRecord a) {
+ if (UserHandle.getAppId(mTaskFragmentOrganizerUid) == SYSTEM_UID) {
+ // The system is trusted to embed other apps securely and for all users.
+ return true;
+ }
+
if (mTaskFragmentOrganizerUid == a.getUid()) {
// Activities from the same UID can be embedded freely by the host.
return true;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 9f2188b..0b965c3 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -550,10 +550,10 @@
throw new IllegalStateException("Too late to abort.");
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
- mController.dispatchLegacyAppTransitionCancelled();
mState = STATE_ABORT;
// Syncengine abort will call through to onTransactionReady()
mSyncEngine.abort(mSyncId);
+ mController.dispatchLegacyAppTransitionCancelled();
}
void setRemoteTransition(RemoteTransition remoteTransition) {
@@ -1251,6 +1251,17 @@
final ActivityRecord topMostActivity = task.getTopMostActivity();
change.setAllowEnterPip(topMostActivity != null
&& topMostActivity.checkEnterPictureInPictureAppOpsState());
+ final ActivityRecord topRunningActivity = task.topRunningActivity();
+ if (topRunningActivity != null && task.mDisplayContent != null) {
+ // If Activity is in fixed rotation, its will be applied with the next rotation,
+ // when the Task is still in the previous rotation.
+ final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
+ final int activityRotation = topRunningActivity.getWindowConfiguration()
+ .getDisplayRotation();
+ if (taskRotation != activityRotation) {
+ change.setEndFixedRotation(activityRotation);
+ }
+ }
} else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e1746cc..45fdc04 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -81,8 +81,10 @@
import android.util.Pools;
import android.util.RotationUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
+import android.view.InsetsSource;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
@@ -127,7 +129,8 @@
* changes are made to this class.
*/
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
- implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable {
+ implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
+ InsetsControlTarget {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
@@ -145,6 +148,13 @@
// onParentChanged() notification.
boolean mReparenting;
+ protected @Nullable InsetsSourceProvider mControllableInsetProvider;
+
+ /**
+ * The insets sources provided by this windowContainer.
+ */
+ private SparseArray<InsetsSource> mProvidedInsetsSources = null;
+
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final WindowList<E> mChildren = new WindowList<E>();
@@ -329,6 +339,25 @@
mSurfaceFreezer = new SurfaceFreezer(this, wms);
}
+ /**
+ * Set's an {@link InsetsSourceProvider} to be associated with this window, but only if the
+ * provider itself is controllable, as one window can be the provider of more than one inset
+ * type (i.e. gesture insets). If this window is controllable, all its animations must be
+ * controlled by its control target, and the visibility of this window should be taken account
+ * into the state of the control target.
+ *
+ * @param insetProvider the provider which should not be visible to the client.
+ * @see #getInsetsState()
+ */
+ void setControllableInsetProvider(InsetsSourceProvider insetProvider) {
+ mControllableInsetProvider = insetProvider;
+ }
+
+ InsetsSourceProvider getControllableInsetProvider() {
+ return mControllableInsetProvider;
+ }
+
+
@Override
final protected WindowContainer getParent() {
return mParent;
@@ -858,6 +887,13 @@
}
}
+ public SparseArray<InsetsSource> getProvidedInsetsSources() {
+ if (mProvidedInsetsSources == null) {
+ mProvidedInsetsSources = new SparseArray<>();
+ }
+ return mProvidedInsetsSources;
+ }
+
DisplayContent getDisplayContent() {
return mDisplayContent;
}
@@ -2962,6 +2998,16 @@
scheduleAnimation();
}
+ void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
+ outPoint.set(left, top);
+ final WindowContainer parentWindowContainer = getParent();
+ if (parentWindowContainer == null) {
+ return;
+ }
+ final Rect parentBounds = parentWindowContainer.getBounds();
+ outPoint.offset(-parentBounds.left, -parentBounds.top);
+ }
+
void reassignLayer(Transaction t) {
final WindowContainer parent = getParent();
if (parent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0d4eae0..4ff2b6a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,8 @@
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
@@ -419,7 +421,7 @@
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
@@ -6617,6 +6619,7 @@
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
+ @NeverCompile // Avoid size overhead of debugging code.
private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
boolean dumpAll = false;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e84a747..1ab8cbf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -30,6 +30,7 @@
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsState.ITYPE_INVALID;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
@@ -216,7 +217,6 @@
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -257,6 +257,8 @@
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.SurfaceAnimator.AnimationType;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -678,11 +680,6 @@
final InsetsState mAboveInsetsState = new InsetsState();
/**
- * The insets sources provided by this window.
- */
- final SparseArray<InsetsSource> mProvidedInsetsSources = new SparseArray<>();
-
- /**
* Surface insets from the previous call to relayout(), used to track
* if we are changing the Surface insets.
*/
@@ -736,7 +733,6 @@
*/
private boolean mIsDimming = false;
- private @Nullable InsetsSourceProvider mControllableInsetProvider;
private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
/**
@@ -1645,11 +1641,39 @@
}
/**
- * Returns the insets state for the window. Its sources may be the copies with visibility
- * modification according to the state of transient bars.
+ * See {@link WindowState#getInsetsState(boolean)}
*/
InsetsState getInsetsState() {
- return getDisplayContent().getInsetsPolicy().getInsetsForWindow(this);
+ return getInsetsState(false);
+ }
+
+ /**
+ * Returns the insets state for the window. Its sources may be the copies with visibility
+ * modification according to the state of transient bars.
+ * This is to get the insets for a window layout on the screen. If the window is not there, use
+ * the {@link InsetsPolicy#getInsetsForWindowMetrics} to get insets instead.
+ * @param includeTransient whether or not the transient types should be included in the
+ * insets state.
+ */
+ InsetsState getInsetsState(boolean includeTransient) {
+ final InsetsState rotatedState = mToken.getFixedRotationTransformInsetsState();
+ final InsetsPolicy insetsPolicy = getDisplayContent().getInsetsPolicy();
+ if (rotatedState != null) {
+ return insetsPolicy.adjustInsetsForWindow(this, rotatedState);
+ }
+ final InsetsSourceProvider provider = getControllableInsetProvider();
+ final InsetsStateController insetsStateController = getDisplayContent()
+ .getInsetsStateController();
+ final @InternalInsetsType int insetTypeProvidedByWindow = provider != null
+ ? provider.getSource().getType() : ITYPE_INVALID;
+ final InsetsState rawInsetsState = getFrozenInsetsState() != null
+ ? getFrozenInsetsState() : (mAttrs.receiveInsetsIgnoringZOrder
+ ? insetsStateController.getRawInsetsState() : mAboveInsetsState);
+ final InsetsState insetsStateForWindow = insetsPolicy
+ .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
+ getWindowingMode(), isAlwaysOnTop(), rawInsetsState);
+ return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
+ includeTransient);
}
/**
@@ -4178,6 +4202,7 @@
proto.end(token);
}
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
pw.print(prefix + "mDisplayId=" + getDisplayId());
@@ -5655,24 +5680,6 @@
mWindowFrames.setContentChanged(false);
}
- /**
- * Set's an {@link InsetsSourceProvider} to be associated with this window, but only if the
- * provider itself is controllable, as one window can be the provider of more than one inset
- * type (i.e. gesture insets). If this window is controllable, all its animations must be
- * controlled by its control target, and the visibility of this window should be taken account
- * into the state of the control target.
- *
- * @param insetProvider the provider which should not be visible to the client.
- * @see InsetsStateController#getInsetsForWindow(WindowState)
- */
- void setControllableInsetProvider(InsetsSourceProvider insetProvider) {
- mControllableInsetProvider = insetProvider;
- }
-
- InsetsSourceProvider getControllableInsetProvider() {
- return mControllableInsetProvider;
- }
-
private final class MoveAnimationSpec implements AnimationSpec {
private final long mDuration;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index f398034..ccaa03a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -615,6 +616,12 @@
getResolvedOverrideConfiguration().updateFrom(
mFixedRotationTransformState.mRotatedOverrideConfiguration);
}
+ if (getTaskDisplayArea() == null) {
+ // We only defined behaviors of system windows in fullscreen mode, i.e. windows not
+ // contained in a task display area.
+ getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN);
+ }
}
@Override
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 94bc22a..636ca41 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -57,6 +57,19 @@
#define ASYNC_RECEIVED_WHILE_FROZEN (2)
#define TXNS_PENDING_WHILE_FROZEN (4)
+#define MAX_RW_COUNT (INT_MAX & PAGE_MASK)
+
+// Defines the maximum amount of VMAs we can send per process_madvise syscall.
+// Currently this is set to UIO_MAXIOV which is the maximum segments allowed by
+// iovec implementation used by process_madvise syscall
+#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV
+
+// Maximum bytes that we can send per process_madvise syscall once this limit
+// is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT
+// limit is imposed by iovec implementation. However, if you want to use a smaller
+// limit, it has to be a page aligned value, otherwise, compaction would fail.
+#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT
+
namespace android {
static bool cancelRunningCompaction;
@@ -70,12 +83,12 @@
}
// Compacts a set of VMAs for pid using an madviseType accepted by process_madvise syscall
-// On success returns the total bytes that where compacted. On failure it returns
-// a negative error code from the standard linux error codes.
+// Returns the total bytes that where madvised.
+//
+// If any VMA fails compaction due to -EINVAL it will be skipped and continue.
+// However, if it fails for any other reason, it will bail out and forward the error
static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
- // UIO_MAXIOV is currently a small value and we might have more addresses
- // we do multiple syscalls if we exceed its maximum
- static struct iovec vmasToKernel[UIO_MAXIOV];
+ static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION];
if (vmas.empty()) {
return 0;
@@ -89,33 +102,72 @@
compactionInProgress = true;
cancelRunningCompaction = false;
- int64_t totalBytesCompacted = 0;
- for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
- if (CC_UNLIKELY(cancelRunningCompaction)) {
- // There could be a significant delay betweenwhen a compaction
- // is requested and when it is handled during this time
- // our OOM adjust could have improved.
+ int64_t totalBytesProcessed = 0;
+
+ int64_t vmaOffset = 0;
+ for (int iVma = 0; iVma < vmas.size();) {
+ uint64_t bytesSentToCompact = 0;
+ int iVec = 0;
+ while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) {
+ if (CC_UNLIKELY(cancelRunningCompaction)) {
+ // There could be a significant delay between when a compaction
+ // is requested and when it is handled during this time our
+ // OOM adjust could have improved.
+ LOG(DEBUG) << "Cancelled running compaction for " << pid;
+ break;
+ }
+
+ uint64_t vmaStart = vmas[iVma].start + vmaOffset;
+ uint64_t vmaSize = vmas[iVma].end - vmaStart;
+ if (vmaSize == 0) {
+ goto next_vma;
+ }
+ vmasToKernel[iVec].iov_base = (void*)vmaStart;
+ if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) {
+ // Exceeded the max bytes that could be sent, so clamp
+ // the end to avoid exceeding limit and issue compaction
+ vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact;
+ }
+
+ vmasToKernel[iVec].iov_len = vmaSize;
+ bytesSentToCompact += vmaSize;
+ ++iVec;
+ if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) {
+ // Ran out of bytes within iovec, dispatch compaction.
+ vmaOffset += vmaSize;
+ break;
+ }
+
+ next_vma:
+ // Finished current VMA, and have more bytes remaining
+ vmaOffset = 0;
+ ++iVma;
+ }
+
+ if (cancelRunningCompaction) {
cancelRunningCompaction = false;
break;
}
- int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
- for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
- vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
- vmasToKernel[iVec].iov_len = vmas[iVma].end - vmas[iVma].start;
+
+ auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0);
+
+ if (CC_UNLIKELY(bytesProcessed == -1)) {
+ if (errno == EINVAL) {
+ // This error is somewhat common due to an unevictable VMA if this is
+ // the case silently skip the bad VMA and continue compacting the rest.
+ continue;
+ } else {
+ // Forward irrecoverable errors and bail out compaction
+ compactionInProgress = false;
+ return -errno;
+ }
}
- auto bytesCompacted =
- process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
- if (CC_UNLIKELY(bytesCompacted == -1)) {
- compactionInProgress = false;
- return -errno;
- }
-
- totalBytesCompacted += bytesCompacted;
+ totalBytesProcessed += bytesProcessed;
}
compactionInProgress = false;
- return totalBytesCompacted;
+ return totalBytesProcessed;
}
static int getFilePageAdvice(const Vma& vma) {
@@ -138,7 +190,12 @@
}
// Perform a full process compaction using process_madvise syscall
-// reading all filtering VMAs and filtering pages as specified by pageFilter
+// using the madvise behavior defined by vmaToAdviseFunc per VMA.
+//
+// Currently supported behaviors are MADV_COLD and MADV_PAGEOUT.
+//
+// Returns the total number of bytes compacted or forwards an
+// process_madvise error.
static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
ProcMemInfo meminfo(pid);
std::vector<Vma> pageoutVmas, coldVmas;
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index be0ddc1..5b4febd 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -170,14 +170,6 @@
</xs:restriction>
</xs:simpleType>
- <!-- Maps to DisplayDeviceConfig.INTERPOLATION_* values. -->
- <xs:simpleType name="interpolation">
- <xs:restriction base="xs:string">
- <xs:enumeration value="default"/>
- <xs:enumeration value="linear"/>
- </xs:restriction>
- </xs:simpleType>
-
<xs:complexType name="thermalThrottling">
<xs:complexType>
<xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap">
@@ -216,7 +208,8 @@
<xs:annotation name="final"/>
</xs:element>
</xs:sequence>
- <xs:attribute name="interpolation" type="interpolation" use="optional"/>
+ <!-- valid value of interpolation if specified: linear -->
+ <xs:attribute name="interpolation" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="point">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2890d68..ba83c9f 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -108,17 +108,11 @@
method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal);
}
- public enum Interpolation {
- method public String getRawName();
- enum_constant public static final com.android.server.display.config.Interpolation _default;
- enum_constant public static final com.android.server.display.config.Interpolation linear;
- }
-
public class NitsMap {
ctor public NitsMap();
- method public com.android.server.display.config.Interpolation getInterpolation();
+ method public String getInterpolation();
method @NonNull public final java.util.List<com.android.server.display.config.Point> getPoint();
- method public void setInterpolation(com.android.server.display.config.Interpolation);
+ method public void setInterpolation(String);
}
public class Point {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2e801ab..e895d37 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2707,15 +2707,6 @@
systemTheme.rebase();
}
- t.traceBegin("MakePowerManagerServiceReady");
- try {
- // TODO: use boot phase
- mPowerManagerService.systemReady();
- } catch (Throwable e) {
- reportWtf("making Power Manager Service ready", e);
- }
- t.traceEnd();
-
// Permission policy service
t.traceBegin("StartPermissionPolicyService");
mSystemServiceManager.startService(PermissionPolicyService.class);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index f72b23c..2d082e1 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -63,6 +63,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -128,6 +129,19 @@
private final PackageManager mPackageManager;
+ private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
+ private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
+
+ // Used to lock mUsbMidiLegacyDeviceOpenCount and mUsbMidiUniversalDeviceInUse.
+ private final Object mUsbMidiLock = new Object();
+
+ // Number of times a USB MIDI 1.0 device has opened, based on the device name.
+ private final HashMap<String, Integer> mUsbMidiLegacyDeviceOpenCount =
+ new HashMap<String, Integer>();
+
+ // Whether a USB MIDI device has opened, based on the device name.
+ private final HashSet<String> mUsbMidiUniversalDeviceInUse = new HashSet<String>();
+
// UID of BluetoothMidiService
private int mBluetoothServiceUid;
@@ -271,6 +285,12 @@
}
for (DeviceConnection connection : mDeviceConnections.values()) {
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
connection.getDevice().removeDeviceConnection(connection);
}
}
@@ -748,6 +768,7 @@
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
if (device.isUidAllowed(uid)) {
+ deviceInfos.add(device.getDeviceInfo());
// UMP devices have protocols that are not PROTOCOL_UNKNOWN
if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
if (device.getDeviceInfo().getDefaultProtocol()
@@ -784,6 +805,15 @@
}
}
+ if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ if (isUsbMidiDeviceInUseLocked(deviceInfo)) {
+ throw new IllegalArgumentException("device already in use: " + deviceInfo);
+ }
+ addUsbMidiDeviceLocked(deviceInfo);
+ }
+ }
+
// clear calling identity so bindService does not fail
final long identity = Binder.clearCallingIdentity();
try {
@@ -1216,4 +1246,82 @@
}
pw.decreaseIndent();
}
+
+ // hold mUsbMidiLock before calling this
+ private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return false;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ // Only one MIDI 2.0 device can be used at once.
+ // Multiple MIDI 1.0 devices can be used at once.
+ if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
+ || ((tagName).equals(MIDI_UNIVERSAL_STRING)
+ && (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)))) {
+ return true;
+ }
+ return false;
+ }
+
+ // hold mUsbMidiLock before calling this
+ void addUsbMidiDeviceLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
+ mUsbMidiUniversalDeviceInUse.add(deviceName);
+ } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
+ int count = mUsbMidiLegacyDeviceOpenCount.getOrDefault(deviceName, 0) + 1;
+ mUsbMidiLegacyDeviceOpenCount.put(deviceName, count);
+ }
+ }
+
+ // hold mUsbMidiLock before calling this
+ void removeUsbMidiDeviceLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
+ mUsbMidiUniversalDeviceInUse.remove(deviceName);
+ } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
+ if (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)) {
+ int count = mUsbMidiLegacyDeviceOpenCount.get(deviceName);
+ if (count > 1) {
+ mUsbMidiLegacyDeviceOpenCount.put(deviceName, count - 1);
+ } else {
+ mUsbMidiLegacyDeviceOpenCount.remove(deviceName);
+ }
+ }
+ }
+ }
+
+ // The USB property name is in the form "manufacturer product#Id MIDI 1.0".
+ // This is defined in UsbDirectMidiDevice.java.
+ // This function extracts out the "manufacturer product#Id " part.
+ // Two devices would have the same device name if they had the following property name:
+ // "manufacturer product#Id MIDI 1.0"
+ // "manufacturer product#Id MIDI 2.0"
+ // Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
+ String extractUsbDeviceName(String propertyName) {
+ return propertyName.substring(0, propertyName.length() - MIDI_LEGACY_STRING.length());
+ }
+
+ // The USB property name is in the form "manufacturer product#Id MIDI 1.0".
+ // This is defined in UsbDirectMidiDevice.java.
+ // This function extracts the "MIDI 1.0" part.
+ // Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
+ String extractUsbDeviceTag(String propertyName) {
+ return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
+ }
}
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index c5709fc..43b2e1e 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -29,6 +29,7 @@
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.parsing.pkg.ParsedPackage
+import com.android.server.pm.resolution.ComponentResolver
import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
@@ -46,6 +47,8 @@
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.intThat
import org.mockito.Mockito.never
import org.mockito.Mockito.same
@@ -337,8 +340,8 @@
val mockSettings = Settings(mockedPkgSettings)
val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked {
params.componentName?.let {
- whenever(this.componentExists(same(it))) { mockActivity != null }
- whenever(this.getActivity(same(it))) { mockActivity }
+ doReturn(mockActivity != null).`when`(this).componentExists(same(it))
+ doReturn(mockActivity).`when`(this).getActivity(same(it))
}
whenever(this.snapshot()) { this@mockThrowOnUnmocked }
whenever(registerObserver(any())).thenCallRealMethod()
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 936940f..e6bb0ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -2648,6 +2648,11 @@
AppPermissionTracker getAppPermissionTracker() {
return mAppPermissionTracker;
}
+
+ @Override
+ void scheduleInitTrackers(Handler handler, Runnable initializers) {
+ initializers.run();
+ }
}
private class TestBaseTrackerInjector<T extends BaseAppStatePolicy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 4ec1641..ca5bf20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -68,6 +68,7 @@
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.parsing.ParsingPackage
import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import com.android.server.pm.resolution.ComponentResolver
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.supplementalprocess.SupplementalProcessManagerLocal
import com.android.server.testutils.TestHandler
@@ -632,7 +633,7 @@
}
private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) {
- whenever(mocks.componentResolver.queryActivities(
+ whenever(mocks.componentResolver.queryActivities(any(),
argThat { intent: Intent? -> intent != null && (action == intent.action) },
nullable(), anyLong(), anyInt())) {
ArrayList(activities.asList().map { info: ActivityInfo? ->
@@ -642,7 +643,7 @@
}
private fun mockQueryServices(action: String, vararg services: ServiceInfo) {
- whenever(mocks.componentResolver.queryServices(
+ whenever(mocks.componentResolver.queryServices(any(),
argThat { intent: Intent? -> intent != null && (action == intent.action) },
nullable(), anyLong(), anyInt())) {
ArrayList(services.asList().map { info ->
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
index 0411b94..9cf6c03 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
@@ -263,7 +263,7 @@
@Test
public void testUserActivityOnDeviceStateChange() {
createService();
- mService.systemReady();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
final DisplayInfo info = new DisplayInfo();
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index 64e8613..8b7cc74 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -18,8 +18,8 @@
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.HARDWARE;
-import static android.hardware.SensorPrivacyManager.ToggleTypes.SOFTWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
+import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -102,106 +102,106 @@
public void testMigration1() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE1);
- assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration2() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE2);
- assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertTrue(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertTrue(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 11, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 11, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 11, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 11, CAMERA));
- assertTrue(ps.getState(SOFTWARE, 12, MICROPHONE).isEnabled());
- assertNull(ps.getState(SOFTWARE, 12, CAMERA));
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 12, MICROPHONE).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 12, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 11, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 11, CAMERA));
- assertNull(ps.getState(HARDWARE, 12, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 12, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 11, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 11, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 12, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 12, CAMERA));
}
@Test
public void testMigration3() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE3);
- assertFalse(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration4() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE4);
- assertTrue(ps.getState(SOFTWARE, 0, MICROPHONE).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertTrue(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).isEnabled());
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertFalse(ps.getState(SOFTWARE, 10, MICROPHONE).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration5() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE5);
- assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
- assertFalse(ps.getState(SOFTWARE, 0, CAMERA).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).isEnabled());
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertFalse(ps.getState(SOFTWARE, 10, CAMERA).isEnabled());
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertFalse(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA).isEnabled());
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
@Test
public void testMigration6() throws IOException {
PersistedState ps = migrateFromFile(PERSISTENCE_FILE6);
- assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA));
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
private PersistedState migrateFromFile(String fileName) throws IOException {
@@ -233,32 +233,32 @@
public void testPersistence1Version2() throws IOException {
PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE7);
- assertEquals(1, ps.getState(SOFTWARE, 0, MICROPHONE).getState());
- assertEquals(123L, ps.getState(SOFTWARE, 0, MICROPHONE).getLastChange());
- assertEquals(2, ps.getState(SOFTWARE, 0, CAMERA).getState());
- assertEquals(123L, ps.getState(SOFTWARE, 0, CAMERA).getLastChange());
+ assertEquals(1, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).getState());
+ assertEquals(123L, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).getState());
+ assertEquals(123L, ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA).getLastChange());
- assertNull(ps.getState(HARDWARE, 0, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 0, CAMERA));
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
}
@Test
public void testPersistence2Version2() throws IOException {
PersistedState ps = getPersistedStateV2(PERSISTENCE_FILE8);
- assertEquals(1, ps.getState(HARDWARE, 0, MICROPHONE).getState());
- assertEquals(1234L, ps.getState(HARDWARE, 0, MICROPHONE).getLastChange());
- assertEquals(2, ps.getState(HARDWARE, 0, CAMERA).getState());
- assertEquals(1234L, ps.getState(HARDWARE, 0, CAMERA).getLastChange());
+ assertEquals(1, ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE).getState());
+ assertEquals(1234L, ps.getState(TOGGLE_TYPE_HARDWARE, 0, MICROPHONE).getLastChange());
+ assertEquals(2, ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA).getState());
+ assertEquals(1234L, ps.getState(TOGGLE_TYPE_HARDWARE, 0, CAMERA).getLastChange());
- assertNull(ps.getState(SOFTWARE, 0, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 0, CAMERA));
- assertNull(ps.getState(SOFTWARE, 10, MICROPHONE));
- assertNull(ps.getState(SOFTWARE, 10, CAMERA));
- assertNull(ps.getState(HARDWARE, 10, MICROPHONE));
- assertNull(ps.getState(HARDWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_SOFTWARE, 10, CAMERA));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, MICROPHONE));
+ assertNull(ps.getState(TOGGLE_TYPE_HARDWARE, 10, CAMERA));
}
private PersistedState getPersistedStateV2(String version2FilePath) throws IOException {
@@ -296,13 +296,15 @@
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
- SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
- SensorState camState = sensorPrivacyStateController.getState(SOFTWARE, 0, CAMERA);
+ SensorState micState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
+ MICROPHONE);
+ SensorState camState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
+ CAMERA);
assertEquals(SensorPrivacyManager.StateTypes.DISABLED, micState.getState());
assertEquals(SensorPrivacyManager.StateTypes.DISABLED, camState.getState());
- verify(persistedState, times(1)).getState(SOFTWARE, 0, MICROPHONE);
- verify(persistedState, times(1)).getState(SOFTWARE, 0, CAMERA);
+ verify(persistedState, times(1)).getState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE);
+ verify(persistedState, times(1)).getState(TOGGLE_TYPE_SOFTWARE, 0, CAMERA);
} finally {
mockitoSession.finishMocking();
}
@@ -319,14 +321,16 @@
PersistedState persistedState = mock(PersistedState.class);
SensorState sensorState = mock(SensorState.class);
doReturn(persistedState).when(() -> PersistedState.fromFile(any()));
- doReturn(sensorState).when(persistedState).getState(SOFTWARE, 0, MICROPHONE);
+ doReturn(sensorState).when(persistedState).getState(TOGGLE_TYPE_SOFTWARE, 0,
+ MICROPHONE);
doReturn(SensorPrivacyManager.StateTypes.ENABLED).when(sensorState).getState();
doReturn(0L).when(sensorState).getLastChange();
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
- SensorState micState = sensorPrivacyStateController.getState(SOFTWARE, 0, MICROPHONE);
+ SensorState micState = sensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, 0,
+ MICROPHONE);
assertEquals(SensorPrivacyManager.StateTypes.ENABLED, micState.getState());
assertEquals(0L, micState.getLastChange());
@@ -349,13 +353,13 @@
SensorPrivacyStateController sensorPrivacyStateController =
getSensorPrivacyStateControllerImpl();
- sensorPrivacyStateController.setState(SOFTWARE, 0, MICROPHONE, true,
+ sensorPrivacyStateController.setState(TOGGLE_TYPE_SOFTWARE, 0, MICROPHONE, true,
mock(Handler.class), changed -> {});
ArgumentCaptor<SensorState> captor = ArgumentCaptor.forClass(SensorState.class);
- verify(persistedState, times(1)).setState(eq(SOFTWARE), eq(0), eq(MICROPHONE),
- captor.capture());
+ verify(persistedState, times(1)).setState(eq(TOGGLE_TYPE_SOFTWARE), eq(0),
+ eq(MICROPHONE), captor.capture());
assertEquals(SensorPrivacyManager.StateTypes.ENABLED, captor.getValue().getState());
} finally {
mockitoSession.finishMocking();
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index 68994e6..177d72b 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -1,5 +1,6 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppOp* = file:/core/java/android/permission/OWNERS
+per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Bluetooth* = file:platform/packages/modules/Bluetooth:master:/framework/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 3890d4d..897b91e 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -37,7 +37,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
-import android.attention.AttentionManagerInternal.ProximityCallbackInternal;
+import android.attention.AttentionManagerInternal.ProximityUpdateCallbackInternal;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
@@ -48,7 +48,7 @@
import android.provider.DeviceConfig;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
-import android.service.attention.IProximityCallback;
+import android.service.attention.IProximityUpdateCallback;
import androidx.test.filters.SmallTest;
@@ -85,7 +85,7 @@
@Mock
Context mContext;
@Mock
- private ProximityCallbackInternal mMockProximityCallbackInternal;
+ private ProximityUpdateCallbackInternal mMockProximityUpdateCallbackInternal;
@Before
public void setUp() throws RemoteException {
@@ -119,7 +119,8 @@
public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() {
mSpyAttentionManager.mIsServiceEnabled = false;
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isFalse();
}
@@ -128,7 +129,8 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(false).when(mSpyAttentionManager).isServiceAvailable();
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isFalse();
}
@@ -139,7 +141,8 @@
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(false).when(mMockIPowerManager).isInteractive();
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isFalse();
}
@@ -149,9 +152,10 @@
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isTrue();
- verify(mMockProximityCallbackInternal, times(1))
+ verify(mMockProximityUpdateCallbackInternal, times(1))
.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
}
@@ -161,21 +165,23 @@
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isTrue();
ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate;
- assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal))
+ assertThat(mSpyAttentionManager.onStartProximityUpdates(
+ mMockProximityUpdateCallbackInternal))
.isTrue();
assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate);
- verify(mMockProximityCallbackInternal, times(1))
+ verify(mMockProximityUpdateCallbackInternal, times(1))
.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
}
@Test
public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() {
- mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
- verifyZeroInteractions(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal);
+ verifyZeroInteractions(mMockProximityUpdateCallbackInternal);
}
@Test
@@ -184,11 +190,11 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
- verify(mMockProximityCallbackInternal, times(1))
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal);
+ verify(mMockProximityUpdateCallbackInternal, times(1))
.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
- ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() {
+ ProximityUpdateCallbackInternal mismatchedCallback = new ProximityUpdateCallbackInternal() {
@Override
public void onProximityUpdate(double distance) {
fail("Callback shouldn't have responded.");
@@ -196,7 +202,7 @@
};
mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback);
- verifyNoMoreInteractions(mMockProximityCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal);
}
@Test
@@ -205,8 +211,8 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
- mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal);
assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull();
}
@@ -217,14 +223,14 @@
mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
- mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal);
- verify(mMockProximityCallbackInternal, times(1))
+ mSpyAttentionManager.onStartProximityUpdates(mMockProximityUpdateCallbackInternal);
+ verify(mMockProximityUpdateCallbackInternal, times(1))
.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
// Attention Service unregisters the proximity update twice in a row.
- mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
- mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal);
- verifyNoMoreInteractions(mMockProximityCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal);
+ mSpyAttentionManager.onStopProximityUpdates(mMockProximityUpdateCallbackInternal);
+ verifyNoMoreInteractions(mMockProximityUpdateCallbackInternal);
}
@Test
@@ -337,7 +343,8 @@
public void cancelAttentionCheck(IAttentionCallback callback) {
}
- public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException {
+ public void onStartProximityUpdates(IProximityUpdateCallback callback)
+ throws RemoteException {
callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 2ae2854..9aac81c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -361,6 +361,16 @@
}
@Test
+ public void close_cleanVirtualAudioController() {
+ mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID);
+ mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback);
+
+ mDeviceImpl.close();
+
+ assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull();
+ }
+
+ @Test
public void sendKeyEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index bdf94f3..356600d 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -33,6 +33,7 @@
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
+import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.display.AmbientBrightnessDayStats;
@@ -42,6 +43,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.input.InputSensorInfo;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.HandlerThread;
@@ -63,6 +65,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -84,8 +88,11 @@
private static final String DEFAULT_DISPLAY_ID = "123";
private static final float FLOAT_DELTA = 0.01f;
+ @Mock private InputSensorInfo mInputSensorInfoMock;
+
private BrightnessTracker mTracker;
private TestInjector mInjector;
+ private Sensor mLightSensorFake;
private static Object sHandlerLock = new Object();
private static Handler sHandler;
@@ -108,9 +115,12 @@
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mInjector = new TestInjector(ensureHandler());
+ mLightSensorFake = new Sensor(mInputSensorInfoMock);
mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector);
+ mTracker.setLightSensor(mLightSensorFake);
mDefaultNightModeColorTemperature =
InstrumentationRegistry.getContext().getResources().getInteger(
R.integer.config_nightDisplayColorTemperatureDefault);
@@ -834,6 +844,47 @@
mTracker.stop();
}
+ @Test
+ public void testLightSensorChange() {
+ // verify the tracker started correctly and a listener registered
+ startTracker(mTracker);
+ assertNotNull(mInjector.mSensorListener);
+ assertEquals(mInjector.mLightSensor, mLightSensorFake);
+
+ // Setting the sensor to null should stop the registered listener.
+ mTracker.setLightSensor(null);
+ mInjector.waitForHandler();
+ assertNull(mInjector.mSensorListener);
+ assertNull(mInjector.mLightSensor);
+
+ // Resetting sensor should start listener again
+ mTracker.setLightSensor(mLightSensorFake);
+ mInjector.waitForHandler();
+ assertNotNull(mInjector.mSensorListener);
+ assertEquals(mInjector.mLightSensor, mLightSensorFake);
+
+ Sensor secondSensor = new Sensor(mInputSensorInfoMock);
+ // Setting a different listener should keep things working
+ mTracker.setLightSensor(secondSensor);
+ mInjector.waitForHandler();
+ assertNotNull(mInjector.mSensorListener);
+ assertEquals(mInjector.mLightSensor, secondSensor);
+ }
+
+ @Test
+ public void testSetLightSensorDoesntStartListener() {
+ mTracker.setLightSensor(mLightSensorFake);
+ assertNull(mInjector.mSensorListener);
+ }
+
+ @Test
+ public void testNullLightSensorWontRegister() {
+ mTracker.setLightSensor(null);
+ startTracker(mTracker);
+ assertNull(mInjector.mSensorListener);
+ assertNull(mInjector.mLightSensor);
+ }
+
private InputStream getInputStream(String data) {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
@@ -924,6 +975,7 @@
private class TestInjector extends BrightnessTracker.Injector {
SensorEventListener mSensorListener;
+ Sensor mLightSensor;
BroadcastReceiver mBroadcastReceiver;
DisplayManager.DisplayListener mDisplayListener;
Map<String, Integer> mSecureIntSettings = new HashMap<>();
@@ -974,14 +1026,16 @@
@Override
public void registerSensorListener(Context context,
- SensorEventListener sensorListener, Handler handler) {
+ SensorEventListener sensorListener, Sensor lightSensor, Handler handler) {
mSensorListener = sensorListener;
+ mLightSensor = lightSensor;
}
@Override
public void unregisterSensorListener(Context context,
SensorEventListener sensorListener) {
mSensorListener = null;
+ mLightSensor = null;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 2f5993d1..5d3da43 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -132,7 +132,12 @@
@Test
public void testGetApexSystemServices() throws RemoteException {
- when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false));
+ when(mApexService.getAllPackages()).thenReturn(new ApexInfo[] {
+ createApexInfoForTestPkg(false, true, 1),
+ // only active apex reports apex-system-service
+ createApexInfoForTestPkg(true, false, 2),
+ });
+
mApexManager.scanApexPackagesTraced(mPackageParser2,
ParallelPackageParser.makeExecutorService());
@@ -484,17 +489,20 @@
assertThat(e).hasMessageThat().contains("Failed to collect certificates from ");
}
- private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) {
+ private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) {
File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
ApexInfo apexInfo = new ApexInfo();
apexInfo.isActive = isActive;
apexInfo.isFactory = isFactory;
apexInfo.moduleName = TEST_APEX_PKG;
apexInfo.modulePath = apexFile.getPath();
- apexInfo.versionCode = 191000070;
+ apexInfo.versionCode = version;
apexInfo.preinstalledModulePath = apexFile.getPath();
+ return apexInfo;
+ }
- return new ApexInfo[]{apexInfo};
+ private ApexInfo[] createApexInfoForTestPkg(boolean isActive, boolean isFactory) {
+ return new ApexInfo[]{createApexInfoForTestPkg(isActive, isFactory, 191000070)};
}
private ApexInfo createApexInfo(String moduleName, int versionCode, boolean isActive,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 8873f42..6789af4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -225,7 +225,11 @@
assertThat(packageUserState1.isSuspended(), is(true));
assertThat(packageUserState1.getSuspendParams().size(), is(1));
assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
- assertThat(packageUserState1.getSuspendParams().valueAt(0), is(nullValue()));
+ assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
+ assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
+ is(nullValue()));
+ assertThat(packageUserState1.getSuspendParams().valueAt(0).getLauncherExtras(),
+ is(nullValue()));
// Verify that the snapshot returns the same answers
ps1 = snapshot.mPackages.get(PACKAGE_NAME_1);
@@ -233,7 +237,11 @@
assertThat(packageUserState1.isSuspended(), is(true));
assertThat(packageUserState1.getSuspendParams().size(), is(1));
assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
- assertThat(packageUserState1.getSuspendParams().valueAt(0), is(nullValue()));
+ assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
+ assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
+ is(nullValue()));
+ assertThat(packageUserState1.getSuspendParams().valueAt(0).getLauncherExtras(),
+ is(nullValue()));
PackageSetting ps2 = settingsUnderTest.mPackages.get(PACKAGE_NAME_2);
PackageUserStateInternal packageUserState2 = ps2.readUserState(0);
@@ -315,14 +323,14 @@
.build();
ps1.modifyUserState(0).putSuspendParams( "suspendingPackage1",
- SuspendParams.getInstanceOrNull(dialogInfo1, appExtras1, launcherExtras1));
+ new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
ps1.modifyUserState(0).putSuspendParams( "suspendingPackage2",
- SuspendParams.getInstanceOrNull(dialogInfo2, appExtras2, launcherExtras2));
+ new SuspendParams(dialogInfo2, appExtras2, launcherExtras2));
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
watcher.verifyChangeReported("put package 1");
ps2.modifyUserState(0).putSuspendParams( "suspendingPackage3",
- SuspendParams.getInstanceOrNull(null, appExtras1, null));
+ new SuspendParams(null, appExtras1, null));
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
watcher.verifyChangeReported("put package 2");
@@ -686,7 +694,7 @@
.setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
.build();
origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1",
- SuspendParams.getInstanceOrNull(dialogInfo1, appExtras1, launcherExtras1));
+ new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
final PackageSetting testPkgSetting01 = new PackageSetting(
PACKAGE_NAME /*pkgName*/,
REAL_PACKAGE_NAME /*realPkgName*/,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 4dc9612..9ad503c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -85,7 +85,7 @@
oldUserState = new PackageUserStateImpl();
oldUserState.putSuspendParams("suspendingPackage",
- SuspendParams.getInstanceOrNull(null, new PersistableBundle(), null));
+ new SuspendParams(null, new PersistableBundle(), null));
assertThat(testUserState.equals(oldUserState), is(false));
oldUserState = new PackageUserStateImpl();
@@ -185,7 +185,7 @@
private static SuspendParams createSuspendParams(SuspendDialogInfo dialogInfo,
PersistableBundle appExtras, PersistableBundle launcherExtras) {
- return SuspendParams.getInstanceOrNull(dialogInfo, appExtras, launcherExtras);
+ return new SuspendParams(dialogInfo, appExtras, launcherExtras);
}
private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
diff --git a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java
index 95af1e1..6e03569 100644
--- a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java
@@ -145,7 +145,7 @@
IntentFilter i = new IntentFilter("TEST_ACTION");
PreferredActivity a1 = new PreferredActivity(i, 1, components, component, true);
- r.addFilter(a1);
+ r.addFilter(null, a1);
watcher.verifyChangeReported("addFilter");
i.setPriority(i.getPriority() + 1);
watcher.verifyNoChangeReported("indepenent intent");
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index c94168c..1b92017 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -321,7 +321,7 @@
}
private void startSystem() {
- mService.systemReady();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
// Grab the BatteryReceiver
ArgumentCaptor<BatteryReceiver> batCaptor = ArgumentCaptor.forClass(BatteryReceiver.class);
@@ -439,7 +439,7 @@
@Test
public void testGetDesiredScreenPolicy_WithVR() {
createService();
- mService.systemReady();
+ startSystem();
// Brighten up the screen
mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_AWAKE, 0, 0, 0, 0,
null, null);
@@ -627,8 +627,7 @@
public void testWasDeviceIdleFor_true() {
int interval = 1000;
createService();
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
mService.onUserActivity();
advanceTime(interval + 1 /* just a little more */);
assertThat(mService.wasDeviceIdleForInternal(interval)).isTrue();
@@ -638,8 +637,7 @@
public void testWasDeviceIdleFor_false() {
int interval = 1000;
createService();
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
mService.onUserActivity();
assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
}
@@ -647,8 +645,7 @@
@Test
public void testForceSuspend_putsDeviceToSleep() {
createService();
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
// Verify that we start awake
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -693,8 +690,7 @@
//
// TEST STARTS HERE
//
- mService.systemReady();
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ startSystem();
// Verify that we start awake
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -792,7 +788,7 @@
createService();
assertTrue(isAcquired[0]);
- mService.systemReady();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
assertTrue(isAcquired[0]);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -1231,7 +1227,7 @@
public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
createService();
- mService.systemReady();
+ startSystem();
mService.getBinderServiceInstance().wakeUp(mClock.now(),
PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
@@ -1444,7 +1440,7 @@
@Test
public void testSetPowerBoost_redirectsCallToNativeWrapper() {
createService();
- mService.systemReady();
+ startSystem();
mService.getBinderServiceInstance().setPowerBoost(Boost.INTERACTION, 1234);
@@ -1454,7 +1450,7 @@
@Test
public void testSetPowerMode_redirectsCallToNativeWrapper() {
createService();
- mService.systemReady();
+ startSystem();
// Enabled launch boost in BatterySaverController to allow setting launch mode.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(false);
@@ -1470,7 +1466,7 @@
@Test
public void testSetPowerMode_withLaunchBoostDisabledAndModeLaunch_ignoresCallToEnable() {
createService();
- mService.systemReady();
+ startSystem();
// Disables launch boost in BatterySaverController.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
@@ -1486,7 +1482,7 @@
@Test
public void testSetPowerModeChecked_returnsNativeCallResult() {
createService();
- mService.systemReady();
+ startSystem();
// Disables launch boost in BatterySaverController.
when(mBatterySaverControllerMock.isLaunchBoostDisabled()).thenReturn(true);
@@ -1649,7 +1645,7 @@
@Test
public void testGetFullPowerSavePolicy_returnsStateMachineResult() {
createService();
- mService.systemReady();
+ startSystem();
BatterySaverPolicyConfig mockReturnConfig = new BatterySaverPolicyConfig.Builder().build();
when(mBatterySaverStateMachineMock.getFullBatterySaverPolicy())
.thenReturn(mockReturnConfig);
@@ -1664,7 +1660,7 @@
@Test
public void testSetFullPowerSavePolicy_callsStateMachine() {
createService();
- mService.systemReady();
+ startSystem();
BatterySaverPolicyConfig mockSetPolicyConfig =
new BatterySaverPolicyConfig.Builder().build();
when(mBatterySaverStateMachineMock.setFullBatterySaverPolicy(any())).thenReturn(true);
@@ -1678,7 +1674,7 @@
@Test
public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
mService.setDeviceIdleModeInternal(true);
@@ -1689,7 +1685,7 @@
@Test
public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
mService.setDeviceIdleModeInternal(true);
@@ -1701,7 +1697,7 @@
@Test
public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE);
mService.setDeviceIdleModeInternal(true);
@@ -1714,7 +1710,7 @@
@Test
public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() {
createService();
- mService.systemReady();
+ startSystem();
WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK);
mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP);
mService.setDeviceIdleModeInternal(true);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 020d9f8..e1a4989 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -100,7 +100,7 @@
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
- private VibrationThread.VibrationCallbacks mThreadCallbacks;
+ private VibrationThread.VibratorManagerHooks mManagerHooks;
@Mock
private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
@Mock
@@ -648,9 +648,9 @@
VibrationEffect.createOneShot(10, 100)));
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
- verify(mThreadCallbacks, never()).prepareSyncedVibration(anyLong(), any());
- verify(mThreadCallbacks, never()).triggerSyncedVibration(anyLong());
- verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any());
+ verify(mManagerHooks, never()).triggerSyncedVibration(anyLong());
+ verify(mManagerHooks, never()).cancelSyncedVibration();
}
@Test
@@ -807,8 +807,8 @@
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- when(mThreadCallbacks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
- when(mThreadCallbacks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+ when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
+ when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
@@ -823,9 +823,9 @@
waitForCompletion(thread);
long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE;
- verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
- verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks, never()).cancelSyncedVibration();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
VibrationEffectSegment expected = expectedPrimitive(
@@ -840,8 +840,8 @@
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
- when(mThreadCallbacks.triggerSyncedVibration(anyLong())).thenReturn(true);
+ when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
+ when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
long vibrationId = 1;
VibrationEffect composed = VibrationEffect.startComposition()
@@ -863,9 +863,9 @@
| IVibratorManager.CAP_MIXED_TRIGGER_ON
| IVibratorManager.CAP_MIXED_TRIGGER_PERFORM
| IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE;
- verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
- verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks, never()).cancelSyncedVibration();
verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
}
@@ -875,7 +875,7 @@
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
+ when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
@@ -886,9 +886,9 @@
waitForCompletion(thread);
long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
- verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mThreadCallbacks, never()).triggerSyncedVibration(eq(vibrationId));
- verify(mThreadCallbacks, never()).cancelSyncedVibration();
+ verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks, never()).cancelSyncedVibration();
assertEquals(Arrays.asList(expectedOneShot(10)),
mVibratorProviders.get(1).getEffectSegments());
@@ -903,8 +903,8 @@
int[] vibratorIds = new int[]{1, 2};
mockVibrators(vibratorIds);
mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
- when(mThreadCallbacks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
- when(mThreadCallbacks.triggerSyncedVibration(anyLong())).thenReturn(false);
+ when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
+ when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
@@ -919,9 +919,9 @@
| IVibratorManager.CAP_PREPARE_PERFORM
| IVibratorManager.CAP_MIXED_TRIGGER_ON
| IVibratorManager.CAP_MIXED_TRIGGER_PERFORM;
- verify(mThreadCallbacks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
- verify(mThreadCallbacks).triggerSyncedVibration(eq(vibrationId));
- verify(mThreadCallbacks).cancelSyncedVibration();
+ verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds));
+ verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId));
+ verify(mManagerHooks).cancelSyncedVibration();
assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty());
}
@@ -1161,9 +1161,9 @@
VibrationThread thread = startThreadAndDispatcher(vibrationId, effect);
// Vibration completed but vibrator not yet released.
- verify(mThreadCallbacks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
+ verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
eq(Vibration.Status.FINISHED));
- verify(mThreadCallbacks, never()).onVibratorsReleased();
+ verify(mManagerHooks, never()).onVibrationThreadReleased();
// Thread still running ramp down.
assertTrue(thread.isAlive());
@@ -1177,9 +1177,9 @@
waitForCompletion(thread);
// Does not cancel already finished vibration, but releases vibrator.
- verify(mThreadCallbacks, never()).onVibrationCompleted(eq(vibrationId),
+ verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
eq(Vibration.Status.CANCELLED));
- verify(mThreadCallbacks).onVibratorsReleased();
+ verify(mManagerHooks).onVibrationThreadReleased();
}
@Test
@@ -1300,7 +1300,7 @@
private VibrationThread startThreadAndDispatcher(Vibration vib) {
VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter,
- createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mThreadCallbacks);
+ createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks);
doAnswer(answer -> {
thread.vibratorComplete(answer.getArgument(0));
return null;
@@ -1380,8 +1380,8 @@
}
private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
- verify(mThreadCallbacks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
- verify(mThreadCallbacks).onVibratorsReleased();
+ verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
+ verify(mManagerHooks).onVibrationThreadReleased();
}
private final class TestLooperAutoDispatcher extends Thread {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 184ea52..fe1ea0d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -33,6 +33,7 @@
import android.app.ActivityOptions;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
+import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +41,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.util.Rational;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
@@ -91,6 +93,20 @@
}
@Test
+ public void testMakeLaunchIntoPip() {
+ // Construct some params with set values
+ PictureInPictureParams params = new PictureInPictureParams.Builder()
+ .setAspectRatio(new Rational(1, 1))
+ .build();
+ // Construct ActivityOptions via makeLaunchIntoPip
+ ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params);
+
+ // Verify the params in ActivityOptions has the right flag being turned on
+ assertNotNull(opts.getLaunchIntoPipParams());
+ assertTrue(opts.isLaunchIntoPip());
+ }
+
+ @Test
public void testTransferLaunchCookie() {
final Binder cookie = new Binder();
final ActivityOptions options = ActivityOptions.makeBasic();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 043bc07..ffc10d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -109,6 +109,7 @@
import android.app.ActivityOptions;
import android.app.ICompatCameraControlCallback;
+import android.app.PictureInPictureParams;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.DestroyActivityItem;
@@ -2200,6 +2201,20 @@
assertFalse(activity.supportsPictureInPicture());
}
+ @Test
+ public void testLaunchIntoPip() {
+ final PictureInPictureParams params = new PictureInPictureParams.Builder()
+ .build();
+ final ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setLaunchIntoPipActivityOptions(opts)
+ .build();
+
+ // Verify the pictureInPictureArgs is set on the new Activity
+ assertNotNull(activity.pictureInPictureArgs);
+ assertTrue(activity.pictureInPictureArgs.isLaunchIntoPip());
+ }
+
private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
boolean shouldUpdate, boolean activityChange) {
reset(activity.app);
@@ -3044,7 +3059,7 @@
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.updateImeInputAndControlTarget(app);
- InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+ InsetsState state = app.getInsetsState();
assertFalse(state.getSource(ITYPE_IME).isVisible());
assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty());
@@ -3064,7 +3079,7 @@
// Verify when IME is visible and the app can receive the right IME insets from policy.
makeWindowVisibleAndDrawn(app, mImeWindow);
- state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+ state = app.getInsetsState();
assertTrue(state.getSource(ITYPE_IME).isVisible());
assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
}
@@ -3076,7 +3091,7 @@
final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
mImeWindow, null, null);
mImeWindow.getControllableInsetProvider().setServerVisible(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index c8e48a4..87abc53 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -39,6 +39,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
+import static android.os.Process.SYSTEM_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -68,6 +69,7 @@
import android.app.ActivityOptions;
import android.app.IApplicationThread;
+import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -1141,6 +1143,34 @@
}
@Test
+ public void testStartActivityInner_inTaskFragment_allowedForSystemUid() {
+ final ActivityStarter starter = prepareStarter(0, false);
+ final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+ true /* createdByOrganizer */);
+ sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+ taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), SYSTEM_UID,
+ "system_uid");
+
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* inTaskFragment */ taskFragment,
+ /* restrictedBgActivity */false,
+ /* intentGrants */null);
+
+ assertTrue(taskFragment.hasChild());
+ }
+
+ @Test
public void testStartActivityInner_inTaskFragment_allowedForSameUid() {
final ActivityStarter starter = prepareStarter(0, false);
final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
@@ -1264,4 +1294,36 @@
// Verify the cookie is updated
assertTrue(mRootWindowContainer.topRunningActivity().mLaunchCookie == newCookie);
}
+
+ @Test
+ public void testStartLaunchIntoPipActivity() {
+ final ActivityStarter starter = prepareStarter(0, false);
+
+ // Create an activity from ActivityOptions#makeLaunchIntoPip
+ final PictureInPictureParams params = new PictureInPictureParams.Builder()
+ .build();
+ final ActivityOptions opts = ActivityOptions.makeLaunchIntoPip(params);
+ ActivityRecord targetRecord = new ActivityBuilder(mAtm)
+ .setLaunchIntoPipActivityOptions(opts)
+ .build();
+
+ // Start the target launch-into-pip activity from a source
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ starter.startActivityInner(
+ /* r */ targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */ null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */ true,
+ /* options */ opts,
+ /* inTask */ null,
+ /* inTaskFragment */ null,
+ /* restrictedBgActivity */ false,
+ /* intentGrants */ null);
+
+ // Verify the ActivityRecord#getLaunchIntoPipHostActivity points to sourceRecord.
+ assertThat(targetRecord.getLaunchIntoPipHostActivity()).isNotNull();
+ assertEquals(targetRecord.getLaunchIntoPipHostActivity(), sourceRecord);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 497ae1d..db22757 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -258,11 +258,9 @@
.rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
mWindow.mAboveInsetsState.set(
mDisplayContent.getInsetsStateController().getRawInsetsState());
- final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
- .getSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
- final Rect rotatedFrame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
- .getSource(ITYPE_STATUS_BAR).getFrame();
+ final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
assertEquals(DISPLAY_WIDTH, frame.width());
assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index 5f96267..ca8481a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -71,7 +71,7 @@
public void testIsImeShowing() {
WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
- mImeProvider.setWindow(ime, null, null);
+ mImeProvider.setWindowContainer(ime, null, null);
WindowState target = createWindow(null, TYPE_APPLICATION, "app");
mDisplayContent.setImeLayeringTarget(target);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 2987f94..c61b88b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,7 +63,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
statusBar.getFrame().set(0, 0, 500, 100);
statusBar.mHasSurface = true;
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
mProvider.onPostLayout();
assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame());
assertEquals(Insets.of(0, 100, 0, 0),
@@ -80,7 +80,7 @@
ime.mGivenContentInsets.set(0, 0, 0, 60);
ime.mGivenVisibleInsets.set(0, 0, 0, 75);
ime.mHasSurface = true;
- mProvider.setWindow(ime, null, null);
+ mProvider.setWindowContainer(ime, null, null);
mProvider.onPostLayout();
assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
@@ -95,10 +95,10 @@
public void testPostLayout_invisible() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
statusBar.getFrame().set(0, 0, 500, 100);
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
mProvider.onPostLayout();
assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
- false /* ignoreVisibility */));
+ false /* ignoreVisibility */));
}
@Test
@@ -106,7 +106,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
statusBar.getFrame().set(0, 0, 500, 100);
statusBar.mHasSurface = true;
- mProvider.setWindow(statusBar,
+ mProvider.setWindowContainer(statusBar,
(displayFrames, windowState, rect) -> {
rect.set(10, 10, 20, 20);
}, null);
@@ -126,7 +126,7 @@
assertNull(mProvider.getControlTarget());
// We can have the control or the control target after we have the insets source window.
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateControlForTarget(target, false /* force */);
assertNotNull(mProvider.getControl(target));
assertNotNull(mProvider.getControlTarget());
@@ -164,7 +164,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateControlForFakeTarget(target);
assertNotNull(mProvider.getControl(target));
assertNull(mProvider.getControl(target).getLeash());
@@ -178,7 +178,7 @@
inputMethod.getFrame().set(new Rect(0, 400, 500, 500));
- mImeProvider.setWindow(inputMethod, null, null);
+ mImeProvider.setWindowContainer(inputMethod, null, null);
mImeProvider.setServerVisible(false);
mImeSource.setVisible(true);
mImeProvider.updateSourceFrame();
@@ -201,7 +201,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateControlForTarget(target, false /* force */);
final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
@@ -215,7 +215,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
target.setRequestedVisibilities(requestedVisibilities);
@@ -228,7 +228,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
statusBar.getFrame().set(0, 0, 500, 100);
statusBar.mHasSurface = true;
- mProvider.setWindow(statusBar, null, null);
+ mProvider.setWindowContainer(statusBar, null, null);
mProvider.onPostLayout();
assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame());
// Still apply top insets if window overlaps even if it's top doesn't exactly match
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2eece4c..c7a1b07 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -68,11 +68,14 @@
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
- getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
- assertNull(getController().getInsetsForWindow(navBar).peekSource(ITYPE_IME));
- assertNull(getController().getInsetsForWindow(navBar).peekSource(ITYPE_STATUS_BAR));
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+
+ assertNull(navBar.getInsetsState().peekSource(ITYPE_IME));
+ assertNull(navBar.getInsetsState().peekSource(ITYPE_STATUS_BAR));
}
@Test
@@ -81,13 +84,15 @@
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
app.setWindowingMode(WINDOWING_MODE_PINNED);
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_IME));
+ assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_IME));
}
@Test
@@ -96,12 +101,14 @@
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
app.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
@Test
@@ -110,19 +117,21 @@
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
app.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
app.setAlwaysOnTop(true);
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_independentSources() {
- getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
@@ -130,34 +139,34 @@
app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME));
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME)
+ assertFalse(app2.getInsetsState().getSource(ITYPE_IME)
.isVisible());
- assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME)
+ assertTrue(app1.getInsetsState().getSource(ITYPE_IME)
.isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_belowIme() {
- getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_aboveIme() {
- getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+ assertFalse(app.getInsetsState().getSource(ITYPE_IME)
.isVisible());
}
@@ -172,7 +181,7 @@
// Make IME and stay visible during the test.
mImeWindow.setHasSurface(true);
- getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(mDisplayContent.getImeTarget(IME_TARGET_INPUT));
final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
requestedVisibilities.setVisibility(ITYPE_IME, true);
@@ -195,7 +204,7 @@
// app won't get visible IME insets while above IME even when IME is visible.
assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
- assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME)
+ assertFalse(app.getInsetsState().getSource(ITYPE_IME)
.isVisible());
// Reset invocation counter.
@@ -212,13 +221,13 @@
verify(app, atLeastOnce()).notifyInsetsChanged();
// app will get visible IME insets while below IME.
- assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
+ assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_altFocusable() {
- getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
@@ -231,15 +240,15 @@
mDisplayContent.applySurfaceChangesTransaction();
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
- assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+ assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertFalse(child.getInsetsState().getSource(ITYPE_IME)
.isVisible());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_splitScreen() {
- getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
@@ -252,8 +261,8 @@
mDisplayContent.applySurfaceChangesTransaction();
getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
- assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
- assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME)
+ assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+ assertFalse(child.getInsetsState().getSource(ITYPE_IME)
.isVisible());
}
@@ -267,14 +276,14 @@
InsetsSourceProvider statusBarProvider =
getController().getSourceProvider(ITYPE_STATUS_BAR);
- statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) ->
- rect.set(0, 1, 2, 3)));
- getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
+ statusBarProvider.setWindowContainer(statusBar, null, ((displayFrames, windowState, rect) ->
+ rect.set(0, 1, 2, 3)));
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
statusBar.setControllableInsetProvider(statusBarProvider);
statusBarProvider.onPostLayout();
- final InsetsState state = getController().getInsetsForWindow(ime);
+ final InsetsState state = ime.getInsetsState();
assertEquals(new Rect(0, 1, 2, 3), state.getSource(ITYPE_STATUS_BAR).getFrame());
}
@@ -285,10 +294,14 @@
final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar");
final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
- getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindow(climateBar, null, null);
- getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindow(extraNavBar, null,
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindowContainer(climateBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindowContainer(
+ extraNavBar, null,
null);
getController().onBarControlTargetChanged(app, null, app, null);
InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
@@ -299,7 +312,8 @@
public void testControlRevoked() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
getController().onBarControlTargetChanged(app, null, null, null);
assertNotNull(getController().getControlsForDispatch(app));
getController().onBarControlTargetChanged(null, null, null, null);
@@ -310,7 +324,8 @@
public void testControlRevoked_animation() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
getController().onBarControlTargetChanged(app, null, null, null);
assertNotNull(getController().getControlsForDispatch(app));
statusBar.cancelAnimation();
@@ -322,7 +337,7 @@
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final InsetsSourceProvider provider = getController().getSourceProvider(ITYPE_STATUS_BAR);
- provider.setWindow(statusBar, null, null);
+ provider.setWindowContainer(statusBar, null, null);
final InsetsState rotatedState = new InsetsState(app.getInsetsState(),
true /* copySources */);
@@ -344,7 +359,8 @@
final WindowState statusBar = createTestWindow("statusBar");
final WindowState navBar = createTestWindow("navBar");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
@@ -365,8 +381,10 @@
final WindowState statusBar = createTestWindow("statusBar");
final WindowState navBar = createTestWindow("navBar");
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
assertNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
@@ -386,10 +404,12 @@
final WindowState statusBar = createTestWindow("statusBar");
final WindowState navBar = createTestWindow("navBar");
- getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
+ getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
getController().getSourceProvider(ITYPE_IME).setClientVisible(true);
- getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
+ null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
getController().updateAboveInsetsState(ime, false /* notifyInsetsChange */);
getController().updateAboveInsetsState(statusBar, false /* notifyInsetsChange */);
getController().updateAboveInsetsState(navBar, false /* notifyInsetsChange */);
@@ -420,11 +440,12 @@
@Test
public void testDispatchGlobalInsets() {
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
- getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
+ null);
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
+ assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
app.mAttrs.receiveInsetsIgnoringZOrder = true;
- assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
+ assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
private WindowState createTestWindow(String name) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index ee17f52..ba65104 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -251,7 +251,8 @@
ensureTaskPlacement(fullscreenTask, firstActivity, secondActivity);
// Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity, "initialMove");
+ mRootWindowContainer.moveActivityToPinnedRootTask(firstActivity,
+ null /* launchIntoPipHostActivity */, "initialMove");
final TaskDisplayArea taskDisplayArea = fullscreenTask.getDisplayArea();
Task pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -260,7 +261,8 @@
ensureTaskPlacement(fullscreenTask, secondActivity);
// Move second activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "secondMove");
+ mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity,
+ null /* launchIntoPipHostActivity */, "secondMove");
// Need to get root tasks again as a new instance might have been created.
pinnedRootTask = taskDisplayArea.getRootPinnedTask();
@@ -291,7 +293,8 @@
// Move first activity to pinned root task.
- mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity, "initialMove");
+ mRootWindowContainer.moveActivityToPinnedRootTask(secondActivity,
+ null /* launchIntoPipHostActivity */, "initialMove");
assertTrue(firstActivity.mRequestForceTransition);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index b815c38..7b38a95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -31,6 +31,7 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -2128,6 +2129,136 @@
APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
}
+ @Test
+ public void testIsEligibleForLetterboxEducation_educationNotEnabled_returnsFalse() {
+ setUpDisplaySizeWithApp(2500, 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(false);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_notEligibleForFixedOrientation_returnsFalse() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_windowingModeMultiWindow_returnsFalse() {
+ // Support non resizable in multi window
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ setUpDisplaySizeWithApp(1000, 1200);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true);
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Non-resizable landscape activity
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ final Rect originalBounds = new Rect(mActivity.getBounds());
+
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP,
+ false /*moveParents*/, "test");
+ organizer.mPrimary.setBounds(0, 0, 1000, 600);
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_fixedOrientationLandscape_returnsFalse() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_hasStartingWindow_returnsFalseUntilRemoved() {
+ setUpDisplaySizeWithApp(2500, 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.mStartingData = mock(StartingData.class);
+ mActivity.attachStartingWindow(
+ createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
+ mActivity));
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+
+ // Verify that after removing the starting window isEligibleForLetterboxEducation returns
+ // true and mTask.dispatchTaskInfoChangedIfNeeded is called.
+ spyOn(mTask);
+ mActivity.removeStartingWindow();
+
+ assertTrue(mActivity.isEligibleForLetterboxEducation());
+ verify(mTask).dispatchTaskInfoChangedIfNeeded(true);
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_hasStartingWindowAndEducationNotEnabled() {
+ setUpDisplaySizeWithApp(2500, 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(false);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.mStartingData = mock(StartingData.class);
+ mActivity.attachStartingWindow(
+ createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
+ mActivity));
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+
+ // Verify that after removing the starting window isEligibleForLetterboxEducation still
+ // returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called.
+ spyOn(mTask);
+ mActivity.removeStartingWindow();
+
+ assertFalse(mActivity.isEligibleForLetterboxEducation());
+ verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true);
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_letterboxedForFixedOrientation_returnsTrue() {
+ setUpDisplaySizeWithApp(2500, 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
+ public void testIsEligibleForLetterboxEducation_sizeCompatAndEligibleForFixedOrientation() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setIsEducationEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(mActivity.inSizeCompatMode());
+ }
+
/**
* Tests that all three paths in which aspect ratio logic can be applied yield the same
* result, which is that aspect ratio is respected on app bounds. The three paths are
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 80192f7..459e3a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -105,7 +105,7 @@
* Tests for the {@link WindowState} class.
*
* Build/Install/Run:
- * atest WmTests:WindowStateTests
+ * atest WmTests:WindowStateTests
*/
@SmallTest
@Presubmit
@@ -411,7 +411,7 @@
assertFalse(app.canAffectSystemUiFlags());
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_STATUS_BAR })
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
@Test
public void testVisibleWithInsetsProvider() {
final WindowState statusBar = mStatusBarWindow;
@@ -419,7 +419,8 @@
statusBar.mHasSurface = true;
assertTrue(statusBar.isVisible());
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
- .setWindow(statusBar, null /* frameProvider */, null /* imeFrameProvider */);
+ .setWindowContainer(statusBar, null /* frameProvider */,
+ null /* imeFrameProvider */);
mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
@@ -623,7 +624,7 @@
assertEquals(w.getWindowConfiguration().getBounds(), unscaledClientBounds);
}
- @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
+ @UseTestDisplay(addWindows = {W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE})
@Test
public void testRequestDrawIfNeeded() {
final WindowState startingApp = createWindow(null /* parent */,
@@ -831,7 +832,7 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
@Test
public void testNeedsRelativeLayeringToIme_startingWindow() {
WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
@@ -864,7 +865,7 @@
verify(app).notifyInsetsChanged();
}
- @UseTestDisplay(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
+ @UseTestDisplay(addWindows = {W_INPUT_METHOD, W_ACTIVITY})
@Test
public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
@@ -890,7 +891,7 @@
mDisplayContent.mInputMethodWindow = imeWindow;
final InsetsStateController controller = mDisplayContent.getInsetsStateController();
- controller.getImeSourceProvider().setWindow(imeWindow, null, null);
+ controller.getImeSourceProvider().setWindowContainer(imeWindow, null, null);
// Simulate app requests IME with updating all windows Insets State when IME is above app.
mDisplayContent.setImeLayeringTarget(app);
@@ -914,7 +915,7 @@
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = { W_ACTIVITY })
+ @UseTestDisplay(addWindows = {W_ACTIVITY})
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
@@ -940,7 +941,7 @@
assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE })
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE})
@Test
public void testNotificationShadeHasImeInsetsWhenMultiWindow() {
WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
@@ -954,7 +955,7 @@
mNotificationShadeWindow.setHasSurface(true);
mNotificationShadeWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
assertTrue(mNotificationShadeWindow.canBeImeTarget());
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
mImeWindow, null, null);
mDisplayContent.computeImeTarget(true);
@@ -963,8 +964,7 @@
.setSourceVisible(ITYPE_IME, true);
// Verify notificationShade can still get IME insets even windowing mode is multi-window.
- InsetsState state = mDisplayContent.getInsetsStateController().getInsetsForWindow(
- mNotificationShadeWindow);
+ InsetsState state = mNotificationShadeWindow.getInsetsState();
assertNotNull(state.peekSource(ITYPE_IME));
assertTrue(state.getSource(ITYPE_IME).isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5dbb4c5..bd1f9d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -941,6 +941,7 @@
private boolean mOnTop = false;
private ActivityInfo.WindowLayout mWindowLayout;
private boolean mVisible = true;
+ private ActivityOptions mLaunchIntoPipOpts;
ActivityBuilder(ActivityTaskManagerService service) {
mService = service;
@@ -1076,6 +1077,11 @@
return this;
}
+ ActivityBuilder setLaunchIntoPipActivityOptions(ActivityOptions opts) {
+ mLaunchIntoPipOpts = opts;
+ return this;
+ }
+
ActivityRecord build() {
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
try {
@@ -1132,7 +1138,9 @@
}
ActivityOptions options = null;
- if (mLaunchTaskBehind) {
+ if (mLaunchIntoPipOpts != null) {
+ options = mLaunchIntoPipOpts;
+ } else if (mLaunchTaskBehind) {
options = ActivityOptions.makeTaskLaunchBehind();
}
final ActivityRecord activity = new ActivityRecord.Builder(mService)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 74cff10..4b5f330a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -262,7 +262,7 @@
@Test
public void testSetInsetsFrozen_notAffectImeWindowState() {
// Pre-condition: make the IME window be controlled by IME insets provider.
- mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+ mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
mDisplayContent.mInputMethodWindow, null, null);
// Simulate an app window to be the IME layering target, assume the app window has no
diff --git a/services/translation/OWNERS b/services/translation/OWNERS
index a1e663a..440f9a8 100644
--- a/services/translation/OWNERS
+++ b/services/translation/OWNERS
@@ -1,8 +1,3 @@
# Bug component: 994311
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+include /core/java/android/view/translation/OWNERS
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
index 27e8d69..ab8f69b 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java
@@ -22,6 +22,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -39,6 +40,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
class BroadcastResponseStatsTracker {
private static final String TAG = "ResponseStatsTracker";
@@ -172,28 +175,27 @@
}
}
- @NonNull BroadcastResponseStats queryBroadcastResponseStats(int callingUid,
- @NonNull String packageName, long id, @UserIdInt int userId) {
- final BroadcastResponseStats aggregatedResponseStats =
- new BroadcastResponseStats(packageName);
+ @NonNull List<BroadcastResponseStats> queryBroadcastResponseStats(int callingUid,
+ @Nullable String packageName, @IntRange(from = 0) long id, @UserIdInt int userId) {
+ final List<BroadcastResponseStats> broadcastResponseStatsList = new ArrayList<>();
synchronized (mLock) {
final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
mUserResponseStats.get(callingUid);
if (responseStatsForCaller == null) {
- return aggregatedResponseStats;
+ return broadcastResponseStatsList;
}
final UserBroadcastResponseStats responseStatsForUser =
responseStatsForCaller.get(userId);
if (responseStatsForUser == null) {
- return aggregatedResponseStats;
+ return broadcastResponseStatsList;
}
- responseStatsForUser.aggregateBroadcastResponseStats(aggregatedResponseStats,
- packageName, id);
+ responseStatsForUser.populateAllBroadcastResponseStats(
+ broadcastResponseStatsList, packageName, id);
}
- return aggregatedResponseStats;
+ return broadcastResponseStatsList;
}
- void clearBroadcastResponseStats(int callingUid, @NonNull String packageName, long id,
+ void clearBroadcastResponseStats(int callingUid, @Nullable String packageName, long id,
@UserIdInt int userId) {
synchronized (mLock) {
final SparseArray<UserBroadcastResponseStats> responseStatsForCaller =
@@ -210,6 +212,16 @@
}
}
+ void clearBroadcastEvents(int callingUid, @UserIdInt int userId) {
+ synchronized (mLock) {
+ final UserBroadcastEvents userBroadcastEvents = mUserBroadcastEvents.get(userId);
+ if (userBroadcastEvents == null) {
+ return;
+ }
+ userBroadcastEvents.clear(callingUid);
+ }
+ }
+
void onUserRemoved(@UserIdInt int userId) {
synchronized (mLock) {
mUserBroadcastEvents.remove(userId);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4a761a7..06aa8f0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -49,7 +49,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppLaunchEstimateInfo;
import android.app.usage.AppStandbyInfo;
-import android.app.usage.BroadcastResponseStats;
+import android.app.usage.BroadcastResponseStatsList;
import android.app.usage.ConfigurationStats;
import android.app.usage.EventStats;
import android.app.usage.IUsageStatsManager;
@@ -2686,16 +2686,15 @@
@Override
@NonNull
- public BroadcastResponseStats queryBroadcastResponseStats(
- @NonNull String packageName,
- @IntRange(from = 1) long id,
+ public BroadcastResponseStatsList queryBroadcastResponseStats(
+ @Nullable String packageName,
+ @IntRange(from = 0) long id,
@NonNull String callingPackage,
@UserIdInt int userId) {
- Objects.requireNonNull(packageName);
Objects.requireNonNull(callingPackage);
// TODO: Move to Preconditions utility class
- if (id <= 0) {
- throw new IllegalArgumentException("id needs to be >0");
+ if (id < 0) {
+ throw new IllegalArgumentException("id needs to be >=0");
}
final int callingUid = Binder.getCallingUid();
@@ -2708,8 +2707,9 @@
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
userId, false /* allowAll */, false /* requireFull */,
"queryBroadcastResponseStats" /* name */, callingPackage);
- return mResponseStatsTracker.queryBroadcastResponseStats(
- callingUid, packageName, id, userId);
+ return new BroadcastResponseStatsList(
+ mResponseStatsTracker.queryBroadcastResponseStats(
+ callingUid, packageName, id, userId));
}
@Override
@@ -2718,10 +2718,9 @@
@IntRange(from = 1) long id,
@NonNull String callingPackage,
@UserIdInt int userId) {
- Objects.requireNonNull(packageName);
Objects.requireNonNull(callingPackage);
- if (id <= 0) {
- throw new IllegalArgumentException("id needs to be >0");
+ if (id < 0) {
+ throw new IllegalArgumentException("id needs to be >=0");
}
final int callingUid = Binder.getCallingUid();
@@ -2737,6 +2736,23 @@
mResponseStatsTracker.clearBroadcastResponseStats(callingUid,
packageName, id, userId);
}
+
+ @Override
+ public void clearBroadcastEvents(@NonNull String callingPackage, @UserIdInt int userId) {
+ Objects.requireNonNull(callingPackage);
+
+ final int callingUid = Binder.getCallingUid();
+ if (!hasPermission(callingPackage)) {
+ throw new SecurityException(
+ "Caller does not have the permission needed to call this API; "
+ + "callingPackage=" + callingPackage
+ + ", callingUid=" + callingUid);
+ }
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid,
+ userId, false /* allowAll */, false /* requireFull */,
+ "clearBroadcastResponseStats" /* name */, callingPackage);
+ mResponseStatsTracker.clearBroadcastEvents(callingUid, userId);
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
diff --git a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java
index 819644846..0ec59c3 100644
--- a/services/usage/java/com/android/server/usage/UserBroadcastEvents.java
+++ b/services/usage/java/com/android/server/usage/UserBroadcastEvents.java
@@ -51,6 +51,10 @@
}
void onUidRemoved(int uid) {
+ clear(uid);
+ }
+
+ void clear(int uid) {
for (int i = mBroadcastEvents.size() - 1; i >= 0; --i) {
final LongSparseArray<BroadcastEvent> broadcastEvents = mBroadcastEvents.valueAt(i);
for (int j = broadcastEvents.size() - 1; j >= 0; --j) {
diff --git a/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java b/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java
index ac2a320..1828a71 100644
--- a/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java
+++ b/services/usage/java/com/android/server/usage/UserBroadcastResponseStats.java
@@ -16,6 +16,7 @@
package com.android.server.usage;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.BroadcastResponseStats;
@@ -23,6 +24,8 @@
import com.android.internal.util.IndentingPrintWriter;
+import java.util.List;
+
class UserBroadcastResponseStats {
/**
* Contains the mapping of a BroadcastEvent type to it's aggregated stats.
@@ -39,31 +42,38 @@
BroadcastEvent broadcastEvent) {
BroadcastResponseStats responseStats = mResponseStats.get(broadcastEvent);
if (responseStats == null) {
- responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage());
+ responseStats = new BroadcastResponseStats(broadcastEvent.getTargetPackage(),
+ broadcastEvent.getIdForResponseEvent());
mResponseStats.put(broadcastEvent, responseStats);
}
return responseStats;
}
- void aggregateBroadcastResponseStats(
- @NonNull BroadcastResponseStats responseStats,
- @NonNull String packageName, long id) {
+ void populateAllBroadcastResponseStats(
+ @NonNull List<BroadcastResponseStats> broadcastResponseStatsList,
+ @Nullable String packageName, @IntRange(from = 0) long id) {
for (int i = mResponseStats.size() - 1; i >= 0; --i) {
final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
- if (broadcastEvent.getIdForResponseEvent() == id
- && broadcastEvent.getTargetPackage().equals(packageName)) {
- responseStats.addCounts(mResponseStats.valueAt(i));
+ if (id != 0 && id != broadcastEvent.getIdForResponseEvent()) {
+ continue;
}
+ if (packageName != null && !packageName.equals(broadcastEvent.getTargetPackage())) {
+ continue;
+ }
+ broadcastResponseStatsList.add(mResponseStats.valueAt(i));
}
}
- void clearBroadcastResponseStats(@NonNull String packageName, long id) {
+ void clearBroadcastResponseStats(@Nullable String packageName, @IntRange(from = 0) long id) {
for (int i = mResponseStats.size() - 1; i >= 0; --i) {
final BroadcastEvent broadcastEvent = mResponseStats.keyAt(i);
- if (broadcastEvent.getIdForResponseEvent() == id
- && broadcastEvent.getTargetPackage().equals(packageName)) {
- mResponseStats.removeAt(i);
+ if (id != 0 && id != broadcastEvent.getIdForResponseEvent()) {
+ continue;
}
+ if (packageName != null && !packageName.equals(broadcastEvent.getTargetPackage())) {
+ continue;
+ }
+ mResponseStats.removeAt(i);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index fd9b995..42a5af7 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -36,7 +36,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
/**
@@ -107,12 +106,6 @@
return false;
}
- /**
- * List of connected MIDI devices
- */
- private final HashMap<String, UsbMidiDevice>
- mMidiDevices = new HashMap<String, UsbMidiDevice>();
-
// UsbMidiDevice for USB peripheral mode (gadget) device
private UsbMidiDevice mPeripheralMidiDevice = null;
@@ -256,45 +249,6 @@
}
}
- // look for MIDI devices
- boolean hasMidi = parser.hasMIDIInterface();
- int midiNumInputs = parser.calculateNumLegacyMidiInputs();
- int midiNumOutputs = parser.calculateNumLegacyMidiOutputs();
- if (DEBUG) {
- Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
- Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs);
- }
- if (hasMidi && mHasMidiFeature) {
- int device = 0;
- Bundle properties = new Bundle();
- String manufacturer = usbDevice.getManufacturerName();
- String product = usbDevice.getProductName();
- String version = usbDevice.getVersion();
- String name;
- if (manufacturer == null || manufacturer.isEmpty()) {
- name = product;
- } else if (product == null || product.isEmpty()) {
- name = manufacturer;
- } else {
- name = manufacturer + " " + product;
- }
- properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
- properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
- properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
- properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
- properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
- usbDevice.getSerialNumber());
- properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
- properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
- properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
-
- UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
- cardRec.getCardNum(), 0 /*device*/, midiNumInputs, midiNumOutputs);
- if (usbMidiDevice != null) {
- mMidiDevices.put(deviceAddress, usbMidiDevice);
- }
- }
-
logDevices("deviceAdded()");
if (DEBUG) {
@@ -315,13 +269,6 @@
selectDefaultDevice(); // if there any external devices left, select one of them
}
- // MIDI
- UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
- if (usbMidiDevice != null) {
- Slog.i(TAG, "USB MIDI Device Removed: " + usbMidiDevice);
- IoUtils.closeQuietly(usbMidiDevice);
- }
-
logDevices("usbDeviceRemoved()");
}
@@ -377,12 +324,6 @@
usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
}
- for (String deviceAddr : mMidiDevices.keySet()) {
- // A UsbMidiDevice does not have a handle to the UsbDevice anymore
- mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
- UsbAlsaManagerProto.MIDI_DEVICES);
- }
-
dump.end(token);
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 2fb67d7..7f70301 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -498,6 +498,7 @@
// current USB state
private boolean mHostConnected;
+ private boolean mUsbAccessoryConnected;
private boolean mSourcePower;
private boolean mSinkPower;
private boolean mConfigured;
@@ -964,10 +965,10 @@
break;
case MSG_UPDATE_HOST_STATE:
Iterator devices = (Iterator) msg.obj;
- boolean connected = (msg.arg1 == 1);
+ mUsbAccessoryConnected = (msg.arg1 == 1);
if (DEBUG) {
- Slog.i(TAG, "HOST_STATE connected:" + connected);
+ Slog.i(TAG, "HOST_STATE connected:" + mUsbAccessoryConnected);
}
mHideUsbNotification = false;
@@ -1221,7 +1222,7 @@
} else if (mSourcePower) {
titleRes = com.android.internal.R.string.usb_supplying_notification_title;
id = SystemMessage.NOTE_USB_SUPPLYING;
- } else if (mHostConnected && mSinkPower && mUsbCharging) {
+ } else if (mHostConnected && mSinkPower && (mUsbCharging || mUsbAccessoryConnected)) {
titleRes = com.android.internal.R.string.usb_charging_notification_title;
id = SystemMessage.NOTE_USB_CHARGING;
}
diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
similarity index 65%
rename from services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
rename to services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 13d404c..0fa79df 100644
--- a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -20,6 +20,7 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
@@ -43,16 +44,20 @@
import java.util.ArrayList;
/**
- * A MIDI device that opens device connections to MIDI 2.0 endpoints.
+ * Opens device connections to MIDI 1.0 or MIDI 2.0 endpoints.
+ * This endpoint will not use ALSA and opens a UsbDeviceConnection directly.
*/
-public final class UsbUniversalMidiDevice implements Closeable {
- private static final String TAG = "UsbUniversalMidiDevice";
+public final class UsbDirectMidiDevice implements Closeable {
+ private static final String TAG = "UsbDirectMidiDevice";
private static final boolean DEBUG = false;
private Context mContext;
private UsbDevice mUsbDevice;
private UsbDescriptorParser mParser;
private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
+ private final boolean mIsUniversalMidiDevice;
+ private final String mUniqueUsbDeviceIdentifier;
+ private final boolean mShouldCallSetInterface;
// USB outputs are MIDI inputs
private final InputReceiverProxy[] mMidiInputPortReceivers;
@@ -64,6 +69,11 @@
// event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers;
+ // Arbitrary number for timeout to not continue sending/receiving number from
+ // an inactive device. This number tries to balances the number of cycles and
+ // not being permanently stuck.
+ private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 100;
+
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
@@ -74,6 +84,8 @@
private final Object mLock = new Object();
private boolean mIsOpen;
+ private final UsbMidiPacketConverter mUsbMidiPacketConverter = new UsbMidiPacketConverter();
+
private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
@Override
@@ -140,13 +152,15 @@
}
/**
- * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams
+ * Creates an UsbDirectMidiDevice based on the input parameters. Read/Write streams
* will be created individually as some devices don't have the same number of
* inputs and outputs.
*/
- public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice,
- UsbDescriptorParser parser) {
- UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser);
+ public static UsbDirectMidiDevice create(Context context, UsbDevice usbDevice,
+ UsbDescriptorParser parser, boolean isUniversalMidiDevice,
+ String uniqueUsbDeviceIdentifier) {
+ UsbDirectMidiDevice midiDevice = new UsbDirectMidiDevice(usbDevice, parser,
+ isUniversalMidiDevice, uniqueUsbDeviceIdentifier);
if (!midiDevice.register(context)) {
IoUtils.closeQuietly(midiDevice);
Log.e(TAG, "createDeviceServer failed");
@@ -155,11 +169,22 @@
return midiDevice;
}
- private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) {
+ private UsbDirectMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser,
+ boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier) {
mUsbDevice = usbDevice;
mParser = parser;
+ mUniqueUsbDeviceIdentifier = uniqueUsbDeviceIdentifier;
+ mIsUniversalMidiDevice = isUniversalMidiDevice;
- mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+ // Set interface should only be called when alternate interfaces exist.
+ // Otherwise, USB devices may not handle this gracefully.
+ mShouldCallSetInterface = (parser.calculateMidiInterfaceDescriptorsCount() > 1);
+
+ if (isUniversalMidiDevice) {
+ mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+ } else {
+ mUsbInterfaces = parser.findLegacyMidiInterfaceDescriptors();
+ }
int numInputs = 0;
int numOutputs = 0;
@@ -182,8 +207,8 @@
mNumInputs = numInputs;
mNumOutputs = numOutputs;
- Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and "
- + numOutputs + " outputs");
+ Log.d(TAG, "Created UsbDirectMidiDevice with " + numInputs + " inputs and "
+ + numOutputs + " outputs. isUniversalMidiDevice: " + isUniversalMidiDevice);
// Create MIDI port receivers based on the number of output ports. The
// output of USB is the input of MIDI.
@@ -218,23 +243,25 @@
if (doesInterfaceContainInput
&& doesInterfaceContainOutput) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
-
- // The ALSA does not handle switching to the MIDI 2.0 interface correctly
- // and stops exposing /dev/snd/midiC1D0 after calling connection.setInterface().
- // Thus, simply use the control interface (interface zero).
+ UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
+ if (!updateUsbInterface(usbInterface, connection)) {
+ continue;
+ }
int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
- 0,
+ interfaceDescriptor.getInterfaceNumber(),
interfaceDescriptor.getAlternateSetting());
+
connection.close();
return defaultMidiProtocol;
}
}
- Log.d(TAG, "Cannot find interface with both input and output endpoints");
+ Log.w(TAG, "Cannot find interface with both input and output endpoints");
return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
}
private boolean openLocked() {
+ Log.d(TAG, "openLocked()");
UsbManager manager = mContext.getSystemService(UsbManager.class);
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
@@ -258,8 +285,10 @@
}
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
- connection.setInterface(interfaceDescriptor.toAndroid(mParser));
- connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true);
+ UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
+ if (!updateUsbInterface(usbInterface, connection)) {
+ continue;
+ }
mUsbDeviceConnections.add(connection);
mInputUsbEndpoints.add(inputEndpoints);
mOutputUsbEndpoints.add(outputEndpoints);
@@ -283,14 +312,17 @@
for (int endpointIndex = 0;
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
- final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
- final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portF = portNumber;
+ final UsbDeviceConnection connectionFinal =
+ mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint endpointFinal =
+ mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+ final int portFinal = portNumber;
- new Thread("UsbUniversalMidiDevice input thread " + portF) {
+ new Thread("UsbDirectMidiDevice input thread " + portFinal) {
@Override
public void run() {
- byte[] inputBuffer = new byte[epF.getMaxPacketSize()];
+ byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
+ Log.d(TAG, "input buffer size: " + inputBuffer.length);
try {
while (true) {
// Record time of event immediately after waking.
@@ -298,20 +330,34 @@
synchronized (mLock) {
if (!mIsOpen) break;
- int nRead = connectionF.bulkTransfer(epF, inputBuffer,
- inputBuffer.length, 0);
-
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- swapEndiannessPerWord(inputBuffer, inputBuffer.length);
+ int nRead = connectionFinal.bulkTransfer(endpointFinal,
+ inputBuffer, inputBuffer.length,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
if (nRead > 0) {
if (DEBUG) {
- logByteArray("Input ", inputBuffer, 0,
- nRead);
+ logByteArray("Input before conversion ", inputBuffer,
+ 0, nRead);
}
- outputReceivers[portF].send(inputBuffer, 0, nRead,
- timestamp);
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(inputBuffer,
+ nRead);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.usbMidiToRawMidi(
+ inputBuffer, nRead);
+ }
+
+ if (DEBUG) {
+ logByteArray("Input after conversion ", convertedArray,
+ 0, convertedArray.length);
+ }
+
+ outputReceivers[portFinal].send(convertedArray, 0,
+ convertedArray.length, timestamp);
}
}
}
@@ -333,19 +379,20 @@
for (int endpointIndex = 0;
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
- final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
- final UsbEndpoint epF =
+ final UsbDeviceConnection connectionFinal =
+ mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint endpointFinal =
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portF = portNumber;
- final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF];
+ final int portFinal = portNumber;
+ final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
- new Thread("UsbUniversalMidiDevice output thread " + portF) {
+ new Thread("UsbDirectMidiDevice output thread " + portFinal) {
@Override
public void run() {
while (true) {
MidiEvent event;
try {
- event = (MidiEvent) eventSchedulerF.waitNextEvent();
+ event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
} catch (InterruptedException e) {
// try again
continue;
@@ -354,16 +401,32 @@
break;
}
- // For USB, each 32 bit word of a UMP is
- // sent with the least significant byte first.
- swapEndiannessPerWord(event.data, event.count);
-
if (DEBUG) {
- logByteArray("Output ", event.data, 0,
+ logByteArray("Output before conversion ", event.data, 0,
event.count);
}
- connectionF.bulkTransfer(epF, event.data, event.count, 0);
- eventSchedulerF.addEventToPool(event);
+
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(event.data,
+ event.count);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.rawMidiToUsbMidi(
+ event.data, event.count);
+ }
+
+ if (DEBUG) {
+ logByteArray("Output after conversion ", convertedArray, 0,
+ convertedArray.length);
+ }
+
+ connectionFinal.bulkTransfer(endpointFinal, convertedArray,
+ convertedArray.length,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
+ eventSchedulerFinal.addEventToPool(event);
}
Log.d(TAG, "output thread exit");
}
@@ -381,11 +444,15 @@
mContext = context;
MidiManager midiManager = context.getSystemService(MidiManager.class);
if (midiManager == null) {
- Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()");
+ Log.e(TAG, "No MidiManager in UsbDirectMidiDevice.create()");
return false;
}
- mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+ if (mIsUniversalMidiDevice) {
+ mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+ } else {
+ mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UNKNOWN;
+ }
Bundle properties = new Bundle();
String manufacturer = mUsbDevice.getManufacturerName();
@@ -397,8 +464,15 @@
} else if (product == null || product.isEmpty()) {
name = manufacturer;
} else {
- name = manufacturer + " " + product + " MIDI 2.0";
+ name = manufacturer + " " + product;
}
+ name += "#" + mUniqueUsbDeviceIdentifier;
+ if (mIsUniversalMidiDevice) {
+ name += " MIDI 2.0";
+ } else {
+ name += " MIDI 1.0";
+ }
+ Log.e(TAG, name);
properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
@@ -430,10 +504,13 @@
}
private void closeLocked() {
+ Log.d(TAG, "closeLocked()");
for (int i = 0; i < mEventSchedulers.length; i++) {
mMidiInputPortReceivers[i].setReceiver(null);
mEventSchedulers[i].close();
}
+ mEventSchedulers = null;
+
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
connection.close();
}
@@ -444,15 +521,19 @@
mIsOpen = false;
}
- private void swapEndiannessPerWord(byte[] array, int size) {
- for (int i = 0; i + 3 < size; i += 4) {
- byte tmp = array[i];
- array[i] = array[i + 3];
- array[i + 3] = tmp;
- tmp = array[i + 1];
- array[i + 1] = array[i + 2];
- array[i + 2] = tmp;
+ private byte[] swapEndiannessPerWord(byte[] inputArray, int size) {
+ int numberOfExcessBytes = size & 3;
+ if (numberOfExcessBytes != 0) {
+ Log.e(TAG, "size not multiple of 4: " + size);
}
+ byte[] outputArray = new byte[size - numberOfExcessBytes];
+ for (int i = 0; i + 3 < size; i += 4) {
+ outputArray[i] = inputArray[i + 3];
+ outputArray[i + 1] = inputArray[i + 2];
+ outputArray[i + 2] = inputArray[i + 1];
+ outputArray[i + 3] = inputArray[i];
+ }
+ return outputArray;
}
private static void logByteArray(String prefix, byte[] value, int offset, int count) {
@@ -465,4 +546,24 @@
}
Log.d(TAG, builder.toString());
}
+
+ private boolean updateUsbInterface(UsbInterface usbInterface,
+ UsbDeviceConnection connection) {
+ if (usbInterface == null) {
+ Log.e(TAG, "Usb Interface is null");
+ return false;
+ }
+ if (!connection.claimInterface(usbInterface, true)) {
+ Log.e(TAG, "Can't claim interface");
+ return false;
+ }
+ if (mShouldCallSetInterface) {
+ if (!connection.setInterface(usbInterface)) {
+ Log.w(TAG, "Can't set interface");
+ }
+ } else {
+ Log.w(TAG, "no alternate interface");
+ }
+ return true;
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 94cc826..a70b0332 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -50,9 +50,12 @@
import libcore.io.IoUtils;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
+import java.util.Random;
/**
* UsbHostManager manages USB state in host mode.
@@ -94,10 +97,12 @@
private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
/**
- * List of connected MIDI devices
+ * List of connected MIDI devices. Key on deviceAddress.
*/
- private final HashMap<String, UsbUniversalMidiDevice>
- mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>();
+ private final HashMap<String, ArrayList<UsbDirectMidiDevice>>
+ mMidiDevices = new HashMap<String, ArrayList<UsbDirectMidiDevice>>();
+ private final HashSet<String> mMidiUniqueCodes = new HashSet<String>();
+ private final Random mRandom = new Random();
private final boolean mHasMidiFeature;
/*
@@ -423,15 +428,35 @@
mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
if (mHasMidiFeature) {
+ // Use a 3 digit code to associate MIDI devices with one another.
+ // Each MIDI device already has mId for uniqueness. mId is generated
+ // sequentially. For clarity, this code is not generated sequentially.
+ String uniqueUsbDeviceIdentifier = generateNewUsbDeviceIdentifier();
+
+ ArrayList<UsbDirectMidiDevice> midiDevices =
+ new ArrayList<UsbDirectMidiDevice>();
if (parser.containsUniversalMidiDeviceEndpoint()) {
- UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext,
- newDevice, parser);
+ UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, true, uniqueUsbDeviceIdentifier);
if (midiDevice != null) {
- mMidiDevices.put(deviceAddress, midiDevice);
+ midiDevices.add(midiDevice);
} else {
Slog.e(TAG, "Universal Midi Device is null.");
}
}
+ if (parser.containsLegacyMidiDeviceEndpoint()) {
+ UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, false, uniqueUsbDeviceIdentifier);
+ if (midiDevice != null) {
+ midiDevices.add(midiDevice);
+ } else {
+ Slog.e(TAG, "Legacy Midi Device is null.");
+ }
+ }
+
+ if (!midiDevices.isEmpty()) {
+ mMidiDevices.put(deviceAddress, midiDevices);
+ }
}
// Tracking
@@ -469,10 +494,13 @@
mPermissionManager.usbDeviceRemoved(device);
// MIDI
- UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
- if (midiDevice != null) {
- Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress);
- IoUtils.closeQuietly(midiDevice);
+ ArrayList<UsbDirectMidiDevice> midiDevices =
+ mMidiDevices.remove(deviceAddress);
+ for (UsbDirectMidiDevice midiDevice : midiDevices) {
+ if (midiDevice != null) {
+ Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
+ IoUtils.closeQuietly(midiDevice);
+ }
}
getCurrentUserSettings().usbDeviceRemoved(device);
@@ -606,6 +634,19 @@
return true;
}
+ // Generate a 3 digit code.
+ private String generateNewUsbDeviceIdentifier() {
+ String code;
+ do {
+ code = "";
+ for (int i = 0; i < 3; i++) {
+ code += mRandom.nextInt(10);
+ }
+ } while (mMidiUniqueCodes.contains(code));
+ mMidiUniqueCodes.add(code);
+ return code;
+ }
+
private native void monitorUsbHostBus();
private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
new file mode 100644
index 0000000..7c93c76
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Converts between MIDI packets and USB MIDI 1.0 packets.
+ */
+public class UsbMidiPacketConverter {
+
+ // Refer to Table 4-1 in USB MIDI 1.0 spec.
+ private static final int[] PAYLOAD_SIZE = new int[]{
+ /* 0x00 */ -1, // Miscellaneous function codes. Reserved for future extensions.
+ /* 0x01 */ -1, // Cable events. Reserved for future expansion.
+ /* 0x02 */ 2, // Two-byte System Common messages like MTC, SongSelect, etc
+ /* 0x03 */ 3, // Three-byte System Common messages like SPP, etc.
+ /* 0x04 */ 3, // SysEx starts or continues
+ /* 0x05 */ 1, // Single-byte System Common Message or single-byte SysEx ends.
+ /* 0x06 */ 2, // SysEx ends with following two bytes.
+ /* 0x07 */ 3, // SysEx ends with following three bytes.
+ /* 0x08 */ 3, // Note-off
+ /* 0x09 */ 3, // Note-on
+ /* 0x0a */ 3, // Poly-KeyPress
+ /* 0x0b */ 3, // Control Change
+ /* 0x0c */ 2, // Program Change
+ /* 0x0d */ 2, // Channel Pressure
+ /* 0x0e */ 3, // PitchBend Change
+ /* 0x0f */ 1 // Single Byte
+ };
+
+ // Each System MIDI message is a certain size. These can be mapped to a
+ // Code Index number defined in Table 4-1 of USB MIDI 1.0.
+ private static final int[] CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE = new int[]{
+ /* 0x00 */ -1, // Start of Exclusive. Special case.
+ /* 0x01 */ 2, // MIDI Time Code. Two byte message
+ /* 0x02 */ 3, // Song Point Pointer. Three byte message
+ /* 0x03 */ 2, // Song Select. Two byte message
+ /* 0x04 */ -1, // Undefined MIDI System Common
+ /* 0x05 */ -1, // Undefined MIDI System Common
+ /* 0x06 */ 5, // Tune Request. One byte message
+ /* 0x07 */ -1, // End of Exclusive. Special case.
+ /* 0x08 */ 5, // Timing clock. One byte message
+ /* 0x09 */ -1, // Undefined MIDI System Real-time
+ /* 0x0a */ 5, // Start. One byte message
+ /* 0x0b */ 5, // Continue. One byte message
+ /* 0x0c */ 5, // Stop. One byte message
+ /* 0x0d */ -1, // Undefined MIDI System Real-time
+ /* 0x0e */ 5, // Active Sensing. One byte message
+ /* 0x0f */ 5 // System Reset. One byte message
+ };
+
+ // These code index numbers also come from Table 4-1 in USB MIDI 1.0 spec.
+ private static final byte CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES = 0x4;
+ private static final byte CODE_INDEX_NUMBER_SINGLE_BYTE = 0xF;
+ private static final byte CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE = (byte) 0x5;
+
+ // System messages are defined in MIDI.
+ private static final byte FIRST_SYSTEM_MESSAGE_VALUE = (byte) 0xF0;
+ private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
+ private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
+
+ private UsbMidiEncoder mUsbMidiEncoder = new UsbMidiEncoder();
+ private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
+
+ /**
+ * Converts a USB MIDI array into a raw MIDI array.
+ *
+ * @param usbMidiBytes the USB MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ * @return byte array of raw MIDI packets
+ */
+ public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
+ return mUsbMidiDecoder.decode(usbMidiBytes, size);
+ }
+
+ /**
+ * Converts a raw MIDI array into a USB MIDI array.
+ *
+ * @param midiBytes the raw MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ * @return byte array of USB MIDI packets
+ */
+ public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size) {
+ return mUsbMidiEncoder.encode(midiBytes, size);
+ }
+
+ private class UsbMidiDecoder {
+ // Decodes the data from USB MIDI to raw MIDI.
+ // Each valid 4 byte input maps to a 1-3 byte output.
+ // Reference the USB MIDI 1.0 spec for more info.
+ public byte[] decode(byte[] usbMidiBytes, int size) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ for (int i = 0; i + 3 < size; i += 4) {
+ int codeIndex = usbMidiBytes[i] & 0x0f;
+ int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
+ if (numPayloadBytes < 0) {
+ continue;
+ }
+ outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
+ }
+ return outputStream.toByteArray();
+ }
+ }
+
+ private class UsbMidiEncoder {
+ // In order to facilitate large scale transfers, SysEx can be sent in multiple packets.
+ // If encode() is called without an SysEx end, we must continue SysEx for the next packet.
+ // All other packets should be 3 bytes or less and must be not be broken between packets.
+ private byte[] mStoredSystemExclusiveBytes = new byte[3];
+ private int mNumStoredSystemExclusiveBytes = 0;
+ private boolean mHasSystemExclusiveStarted = false;
+
+ private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
+
+ // Encodes the data from raw MIDI to USB MIDI.
+ // Each valid 1-3 byte input maps to a 4 byte output.
+ // Reference the USB MIDI 1.0 spec for more info.
+ // MidiFramer is not needed here as this code handles partial packets.
+ // Long SysEx messages split between packets will encode and return a
+ // byte stream even if the SysEx end has not been sent.
+ // If there are less than 3 remaining data bytes in a SysEx message left,
+ // these bytes will be combined with the next set of packets.
+ public byte[] encode(byte[] midiBytes, int size) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ int curLocation = 0;
+ while (curLocation < size) {
+ if (midiBytes[curLocation] >= 0) { // Data byte
+ if (mHasSystemExclusiveStarted) {
+ mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
+ midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes++;
+ if (mNumStoredSystemExclusiveBytes == 3) {
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
+ outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
+ mNumStoredSystemExclusiveBytes = 0;
+ }
+ } else {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ }
+ curLocation++;
+ continue;
+ } else if (midiBytes[curLocation] != SYSEX_END_EXCLUSIVE) {
+ // SysEx operation was interrupted. Pass the data directly down.
+ if (mHasSystemExclusiveStarted) {
+ int index = 0;
+ while (index < mNumStoredSystemExclusiveBytes) {
+ writeSingleByte(outputStream, mStoredSystemExclusiveBytes[index]);
+ index++;
+ }
+ mNumStoredSystemExclusiveBytes = 0;
+ mHasSystemExclusiveStarted = false;
+ }
+ }
+
+ if (midiBytes[curLocation] < FIRST_SYSTEM_MESSAGE_VALUE) { // Channel message
+ byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
+ int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
+ if (curLocation + channelMessageSize <= size) {
+ outputStream.write(codeIndexNumber);
+ outputStream.write(midiBytes, curLocation, channelMessageSize);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
+ curLocation += channelMessageSize;
+ } else { // The packet is missing data. Use single byte messages.
+ while (curLocation < size) {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ }
+ }
+ } else if (midiBytes[curLocation] == SYSEX_START_EXCLUSIVE) {
+ mHasSystemExclusiveStarted = true;
+ mStoredSystemExclusiveBytes[0] = midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes = 1;
+ curLocation++;
+ } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
+ // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
+ + mNumStoredSystemExclusiveBytes);
+ mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
+ midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes++;
+ outputStream.write(mStoredSystemExclusiveBytes, 0,
+ mNumStoredSystemExclusiveBytes);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - mNumStoredSystemExclusiveBytes);
+ mHasSystemExclusiveStarted = false;
+ mNumStoredSystemExclusiveBytes = 0;
+ curLocation++;
+ } else {
+ int systemType = midiBytes[curLocation] & 0x0f;
+ int codeIndexNumber = CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE[systemType];
+ if (codeIndexNumber < 0) { // Unknown type. Use single byte messages.
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ } else {
+ int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
+ if (curLocation + systemMessageSize <= size) {
+ outputStream.write(codeIndexNumber);
+ outputStream.write(midiBytes, curLocation, systemMessageSize);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
+ curLocation += systemMessageSize;
+ } else { // The packet is missing data. Use single byte messages.
+ while (curLocation < size) {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ }
+ }
+ }
+ }
+ }
+ return outputStream.toByteArray();
+ }
+
+ private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
+ outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
+ outputStream.write(byteToWrite);
+ outputStream.write(0);
+ outputStream.write(0);
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index f3308bb..c0ecf58 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -63,6 +63,8 @@
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -883,6 +885,7 @@
}
}
+ @NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 6e68a91..cd6ea68 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -198,14 +198,20 @@
if (mCurInterfaceDescriptor != null) {
switch (mCurInterfaceDescriptor.getUsbClass()) {
case UsbDescriptor.CLASSID_AUDIO:
- descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
+ descriptor =
+ UsbACInterface.allocDescriptor(this, stream, length, type);
+ if (descriptor instanceof UsbMSMidiHeader) {
+ mCurInterfaceDescriptor.setMidiHeaderInterfaceDescriptor(
+ descriptor);
+ }
break;
case UsbDescriptor.CLASSID_VIDEO:
if (DEBUG) {
Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO");
}
- descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type);
+ descriptor =
+ UsbVCInterface.allocDescriptor(this, stream, length, type);
break;
case UsbDescriptor.CLASSID_AUDIOVIDEO:
@@ -218,7 +224,6 @@
Log.w(TAG, " Unparsed Class-specific");
break;
}
- mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
}
break;
@@ -674,6 +679,23 @@
public boolean containsUniversalMidiDeviceEndpoint() {
ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
findUniversalMidiInterfaceDescriptors();
+ return doesInterfaceContainEndpoint(interfaceDescriptors);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean containsLegacyMidiDeviceEndpoint() {
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
+ findLegacyMidiInterfaceDescriptors();
+ return doesInterfaceContainEndpoint(interfaceDescriptors);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean doesInterfaceContainEndpoint(
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) {
int outputCount = 0;
int inputCount = 0;
for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
@@ -698,10 +720,24 @@
* @hide
*/
public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
+ return findMidiInterfaceDescriptors(MS_MIDI_2_0);
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() {
+ return findMidiInterfaceDescriptors(MS_MIDI_1_0);
+ }
+
+ /**
+ * @hide
+ */
+ private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
- ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces =
+ ArrayList<UsbInterfaceDescriptor> midiInterfaces =
new ArrayList<UsbInterfaceDescriptor>();
for (UsbDescriptor descriptor : descriptors) {
@@ -709,14 +745,14 @@
if (descriptor instanceof UsbInterfaceDescriptor) {
UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
- UsbDescriptor classSpecificDescriptor =
- interfaceDescriptor.getClassSpecificInterfaceDescriptor();
- if (classSpecificDescriptor != null) {
- if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
UsbMSMidiHeader midiHeader =
- (UsbMSMidiHeader) classSpecificDescriptor;
- if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) {
- universalMidiInterfaces.add(interfaceDescriptor);
+ (UsbMSMidiHeader) midiHeaderDescriptor;
+ if (midiHeader.getMidiStreamingClass() == type) {
+ midiInterfaces.add(interfaceDescriptor);
}
}
}
@@ -726,10 +762,13 @@
+ " t:0x" + Integer.toHexString(descriptor.getType()));
}
}
- return universalMidiInterfaces;
+ return midiInterfaces;
}
- private int calculateNumLegacyMidiPorts(boolean isOutput) {
+ /**
+ * @hide
+ */
+ public int calculateMidiInterfaceDescriptorsCount() {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
@@ -738,22 +777,12 @@
if (descriptor instanceof UsbInterfaceDescriptor) {
UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
- UsbDescriptor classSpecificDescriptor =
- interfaceDescriptor.getClassSpecificInterfaceDescriptor();
- if (classSpecificDescriptor != null) {
- if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
UsbMSMidiHeader midiHeader =
- (UsbMSMidiHeader) classSpecificDescriptor;
- if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) {
- continue;
- }
- }
- }
- for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
- UsbEndpointDescriptor endpoint =
- interfaceDescriptor.getEndpointDescriptor(i);
- // 0 is output, 1 << 7 is input.
- if ((endpoint.getDirection() == 0) == isOutput) {
+ (UsbMSMidiHeader) midiHeaderDescriptor;
count++;
}
}
@@ -769,20 +798,6 @@
/**
* @hide
*/
- public int calculateNumLegacyMidiInputs() {
- return calculateNumLegacyMidiPorts(false /*isOutput*/);
- }
-
- /**
- * @hide
- */
- public int calculateNumLegacyMidiOutputs() {
- return calculateNumLegacyMidiPorts(true /*isOutput*/);
- }
-
- /**
- * @hide
- */
public float getInputHeadsetProbability() {
if (hasMIDIInterface()) {
return 0.0f;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index ca4613b..9ddcb10 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -42,7 +42,8 @@
private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
new ArrayList<UsbEndpointDescriptor>();
- private UsbDescriptor mClassSpecificInterfaceDescriptor;
+ // Used for MIDI only.
+ private UsbDescriptor mMidiHeaderInterfaceDescriptor;
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
@@ -107,12 +108,12 @@
mEndpointDescriptors.add(endpoint);
}
- public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) {
- mClassSpecificInterfaceDescriptor = descriptor;
+ public void setMidiHeaderInterfaceDescriptor(UsbDescriptor descriptor) {
+ mMidiHeaderInterfaceDescriptor = descriptor;
}
- public UsbDescriptor getClassSpecificInterfaceDescriptor() {
- return mClassSpecificInterfaceDescriptor;
+ public UsbDescriptor getMidiHeaderInterfaceDescriptor() {
+ return mMidiHeaderInterfaceDescriptor;
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2c39863..eb3df1c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5395,7 +5395,7 @@
defaults.putPersistableBundle(
KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle());
- defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true);
+ defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, false);
defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true);
defaults.putBoolean(KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, false);
defaults.putBoolean(KEY_REGISTRATION_EVENT_PACKAGE_SUPPORTED_BOOL, true);
@@ -5410,7 +5410,7 @@
defaults.putInt(KEY_SIP_TIMER_H_MILLIS_INT, 128000);
defaults.putInt(KEY_SIP_TIMER_J_MILLIS_INT, 128000);
defaults.putInt(KEY_SIP_SERVER_PORT_NUMBER_INT, 5060);
- defaults.putInt(KEY_REQUEST_URI_TYPE_INT, REQUEST_URI_FORMAT_SIP);
+ defaults.putInt(KEY_REQUEST_URI_TYPE_INT, REQUEST_URI_FORMAT_TEL);
defaults.putInt(KEY_SIP_PREFERRED_TRANSPORT_INT, PREFERRED_TRANSPORT_DYNAMIC_UDP_TCP);
defaults.putInt(KEY_IPV4_SIP_MTU_SIZE_CELLULAR_INT, 1500);
defaults.putInt(KEY_IPV6_SIP_MTU_SIZE_CELLULAR_INT, 1500);
@@ -6431,11 +6431,11 @@
defaults.putBoolean(KEY_MULTIENDPOINT_SUPPORTED_BOOL, false);
defaults.putBoolean(KEY_SESSION_TIMER_SUPPORTED_BOOL, true);
defaults.putBoolean(KEY_OIP_SOURCE_FROM_HEADER_BOOL, false);
- defaults.putBoolean(KEY_PRACK_SUPPORTED_FOR_18X_BOOL, true);
+ defaults.putBoolean(KEY_PRACK_SUPPORTED_FOR_18X_BOOL, false);
defaults.putBoolean(KEY_VOICE_QOS_PRECONDITION_SUPPORTED_BOOL, true);
defaults.putBoolean(KEY_VOICE_ON_DEFAULT_BEARER_SUPPORTED_BOOL, false);
- defaults.putInt(KEY_SESSION_REFRESHER_TYPE_INT, SESSION_REFRESHER_TYPE_UNKNOWN);
+ defaults.putInt(KEY_SESSION_REFRESHER_TYPE_INT, SESSION_REFRESHER_TYPE_UAC);
defaults.putInt(KEY_SESSION_PRIVACY_TYPE_INT, SESSION_PRIVACY_TYPE_HEADER);
defaults.putInt(KEY_SESSION_REFRESH_METHOD_INT,
SESSION_REFRESH_METHOD_UPDATE_PREFERRED);
@@ -6457,7 +6457,9 @@
KEY_AUDIO_INACTIVITY_CALL_END_REASONS_INT_ARRAY,
new int[] {
Ims.RTCP_INACTIVITY_ON_CONNECTED,
- Ims.RTP_INACTIVITY_ON_CONNECTED
+ Ims.RTP_INACTIVITY_ON_CONNECTED,
+ Ims.E911_RTCP_INACTIVITY_ON_CONNECTED,
+ Ims.RTCP_INACTIVITY_ON_HOLD
});
defaults.putIntArray(
@@ -6821,7 +6823,7 @@
AccessNetworkType.IWLAN
});
- defaults.putInt(KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT, 20000);
+ defaults.putInt(KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT, 10000);
defaults.putInt(KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT, 5000);
return defaults;
@@ -7494,7 +7496,7 @@
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
- defaults.putBoolean(KEY_UT_REQUIRES_IMS_REGISTRATION_BOOL, true);
+ defaults.putBoolean(KEY_UT_REQUIRES_IMS_REGISTRATION_BOOL, false);
defaults.putBoolean(KEY_USE_CSFB_ON_XCAP_OVER_UT_FAILURE_BOOL, true);
defaults.putBoolean(KEY_UT_SUPPORTED_WHEN_PS_DATA_OFF_BOOL, true);
defaults.putBoolean(KEY_NETWORK_INITIATED_USSD_OVER_IMS_SUPPORTED_BOOL, true);
@@ -8124,6 +8126,13 @@
"telephony_data_handover_retry_rules_string_array";
/**
+ * Indicates whether delay tearing down IMS data network until voice call ends.
+ * @hide
+ */
+ public static final String KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL =
+ "delay_ims_tear_down_until_call_end_bool";
+
+ /**
* The patterns of missed incoming call sms. This is the regular expression used for
* matching the missed incoming call's date, time, and caller id. The pattern should match
* fields for at least month, day, hour, and minute. Year is optional although it is encouraged.
@@ -8217,7 +8226,9 @@
"store_sim_pin_for_unattended_reboot_bool";
/**
- * Determine whether "Enable 2G" toggle can be shown.
+ * Allow whether the user can use the "Allow 2G" toggle in Settings.
+ *
+ * If {@code true} then the toggle is disabled (i.e. grayed out).
*
* Used to trade privacy/security against potentially reduced carrier coverage for some
* carriers.
@@ -8968,6 +8979,7 @@
KEY_TELEPHONY_DATA_HANDOVER_RETRY_RULES_STRING_ARRAY, new String[] {
"retry_interval=1000|2000|4000|8000|16000, maximum_retries=5"
});
+ sDefaults.putBoolean(KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, false);
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 837124f..ca6dc2d 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -22,6 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Objects;
@@ -76,7 +78,8 @@
/**
* @hide
*/
- DataSpecificRegistrationInfo(
+ @VisibleForTesting
+ public DataSpecificRegistrationInfo(
int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable,
boolean isEnDcAvailable, @Nullable VopsSupportInfo vops) {
this.maxDataCalls = maxDataCalls;
@@ -186,7 +189,7 @@
/**
* @return The VOPS (Voice over Packet Switched) support information.
*
- * The instance of {@link LTEVopsSupportInfo}, or {@link NrVopsSupportInfo},
+ * The instance of {@link LteVopsSupportInfo}, or {@link NrVopsSupportInfo},
* null if there is there is no VOPS support information available.
*/
@Nullable
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index c18443e..c701e44 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -242,13 +242,16 @@
* @param cellIdentity The identity representing a unique cell or wifi AP. Set to null if the
* information is not available.
* @param rplmn the registered plmn or the last plmn for attempted registration if reg failed.
+ * @param voiceSpecificInfo Voice specific registration information.
+ * @param dataSpecificInfo Data specific registration information.
*/
private NetworkRegistrationInfo(@Domain int domain, @TransportType int transportType,
- @RegistrationState int registrationState,
- @NetworkType int accessNetworkTechnology, int rejectCause,
- boolean emergencyOnly,
- @Nullable @ServiceType List<Integer> availableServices,
- @Nullable CellIdentity cellIdentity, @Nullable String rplmn) {
+ @RegistrationState int registrationState,
+ @NetworkType int accessNetworkTechnology, int rejectCause,
+ boolean emergencyOnly, @Nullable @ServiceType List<Integer> availableServices,
+ @Nullable CellIdentity cellIdentity, @Nullable String rplmn,
+ @Nullable VoiceSpecificRegistrationInfo voiceSpecificInfo,
+ @Nullable DataSpecificRegistrationInfo dataSpecificInfo) {
mDomain = domain;
mTransportType = transportType;
mRegistrationState = registrationState;
@@ -262,6 +265,10 @@
mEmergencyOnly = emergencyOnly;
mNrState = NR_STATE_NONE;
mRplmn = rplmn;
+ mVoiceSpecificInfo = voiceSpecificInfo;
+ mDataSpecificInfo = dataSpecificInfo;
+
+ updateNrState();
}
/**
@@ -276,10 +283,9 @@
boolean cssSupported, int roamingIndicator, int systemIsInPrl,
int defaultRoamingIndicator) {
this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
- emergencyOnly, availableServices, cellIdentity, rplmn);
-
- mVoiceSpecificInfo = new VoiceSpecificRegistrationInfo(cssSupported, roamingIndicator,
- systemIsInPrl, defaultRoamingIndicator);
+ emergencyOnly, availableServices, cellIdentity, rplmn,
+ new VoiceSpecificRegistrationInfo(cssSupported, roamingIndicator,
+ systemIsInPrl, defaultRoamingIndicator), null);
}
/**
@@ -295,11 +301,9 @@
boolean isNrAvailable, boolean isEndcAvailable,
@Nullable VopsSupportInfo vopsSupportInfo) {
this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
- emergencyOnly, availableServices, cellIdentity, rplmn);
- mDataSpecificInfo = new DataSpecificRegistrationInfo(
- maxDataCalls, isDcNrRestricted, isNrAvailable,
- isEndcAvailable, vopsSupportInfo);
- updateNrState();
+ emergencyOnly, availableServices, cellIdentity, rplmn, null,
+ new DataSpecificRegistrationInfo(maxDataCalls, isDcNrRestricted, isNrAvailable,
+ isEndcAvailable, vopsSupportInfo));
}
private NetworkRegistrationInfo(Parcel source) {
@@ -804,6 +808,12 @@
@NonNull
private String mRplmn = "";
+ @Nullable
+ private DataSpecificRegistrationInfo mDataSpecificRegistrationInfo;
+
+ @Nullable
+ private VoiceSpecificRegistrationInfo mVoiceSpecificRegistrationInfo;
+
/**
* Default constructor for Builder.
*/
@@ -930,6 +940,30 @@
}
/**
+ * Set voice specific registration information.
+ *
+ * @param info The voice specific registration information.
+ * @return The builder.
+ * @hide
+ */
+ public @NonNull Builder setVoiceSpecificInfo(@NonNull VoiceSpecificRegistrationInfo info) {
+ mVoiceSpecificRegistrationInfo = info;
+ return this;
+ }
+
+ /**
+ * Set data specific registration information.
+ *
+ * @param info The data specific registration information.
+ * @return The builder.
+ * @hide
+ */
+ public @NonNull Builder setDataSpecificInfo(@NonNull DataSpecificRegistrationInfo info) {
+ mDataSpecificRegistrationInfo = info;
+ return this;
+ }
+
+ /**
* Build the NetworkRegistrationInfo.
* @return the NetworkRegistrationInfo object.
* @hide
@@ -938,7 +972,8 @@
public @NonNull NetworkRegistrationInfo build() {
return new NetworkRegistrationInfo(mDomain, mTransportType, mRegistrationState,
mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
- mCellIdentity, mRplmn);
+ mCellIdentity, mRplmn, mVoiceSpecificRegistrationInfo,
+ mDataSpecificRegistrationInfo);
}
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 613b0a6..f57c32c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6967,6 +6967,11 @@
*
* Input parameters equivalent to TS 27.007 AT+CCHO command.
*
+ * It is strongly recommended that callers of this should firstly create a new TelephonyManager
+ * instance by calling {@link TelephonyManager#createForSubscriptionId(int)}. Failure to do so
+ * can result in unpredictable and detrimental behavior like callers can end up talking to the
+ * wrong SIM card.
+ *
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
@@ -7060,6 +7065,8 @@
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
+ } catch (IllegalStateException ex) {
+ Rlog.e(TAG, "iccCloseLogicalChannel IllegalStateException", ex);
}
return false;
}
@@ -7107,6 +7114,10 @@
* Closes a previously opened logical channel to the ICC card.
*
* Input parameters equivalent to TS 27.007 AT+CCHC command.
+ * It is strongly recommended that callers of this API should firstly create
+ * new TelephonyManager instance by calling
+ * {@link TelephonyManager#createForSubscriptionId(int)}. Failure to do so can result in
+ * unpredictable and detrimental behavior like callers can end up talking to the wrong SIM card.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
@@ -7115,6 +7126,7 @@
* @param channel is the channel id to be closed as returned by a successful
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
+ * @throws IllegalArgumentException if input parameters are wrong. e.g., invalid channel
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
public boolean iccCloseLogicalChannel(int channel) {
@@ -7122,8 +7134,6 @@
return iccCloseLogicalChannel(getSubId(), channel);
} catch (IllegalStateException ex) {
Rlog.e(TAG, "iccCloseLogicalChannel IllegalStateException", ex);
- } catch (IllegalArgumentException ex) {
- Rlog.e(TAG, "iccCloseLogicalChannel IllegalArgumentException", ex);
}
return false;
}
@@ -7154,6 +7164,8 @@
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
+ } catch (IllegalStateException ex) {
+ Rlog.e(TAG, "iccCloseLogicalChannel IllegalStateException", ex);
}
return false;
}
@@ -7255,6 +7267,11 @@
*
* Input parameters equivalent to TS 27.007 AT+CGLA command.
*
+ * It is strongly recommended that callers of this API should firstly create a new
+ * TelephonyManager instance by calling
+ * {@link TelephonyManager#createForSubscriptionId(int)}. Failure to do so can result in
+ * unpredictable and detrimental behavior like callers can end up talking to the wrong SIM card.
+ *
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
* app has carrier privileges (see {@link #hasCarrierPrivileges}).
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index a49a61b5..b6ae530 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -25,15 +25,20 @@
import android.annotation.SystemApi;
import android.app.Activity;
import android.app.PendingIntent;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
+import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccCardManager.ResetOption;
+import android.util.Log;
import com.android.internal.telephony.euicc.IEuiccController;
@@ -57,6 +62,7 @@
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_EUICC)
public class EuiccManager {
+ private static final String TAG = "EuiccManager";
/**
* Intent action to launch the embedded SIM (eUICC) management settings screen.
@@ -811,6 +817,14 @@
*/
public static final int ERROR_INVALID_PORT = 10017;
+ /**
+ * Apps targeting on Android T and beyond will get exception whenever switchToSubscription
+ * without portIndex is called for disable subscription.
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ public static final long SWITCH_WITHOUT_PORT_INDEX_EXCEPTION_ON_DISABLE = 218393363L;
private final Context mContext;
private int mCardId;
@@ -1127,7 +1141,7 @@
* intent to prompt the user to accept the download. The caller should also be authorized to
* manage the subscription to be enabled.
*
- * <p> From Android T, devices might support MEP(Multiple Enabled Profile), the subscription
+ * <p> From Android T, devices might support MEP(Multiple Enabled Profiles), the subscription
* can be installed on different port from the eUICC. Calling apps with carrier privilege
* (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions
* can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to
@@ -1138,10 +1152,12 @@
*
* @param subscriptionId the ID of the subscription to enable. May be
* {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the
- * current profile without activating another profile to replace it. If it's a disable
- * operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS}
- * permission, or the calling app must be authorized to manage the active subscription on
- * the target eUICC.
+ * current profile without activating another profile to replace it. Calling apps targeting
+ * on android T must use {@link #switchToSubscription(int, int, PendingIntent)} API for
+ * disable profile, port index can be found from {@link SubscriptionInfo#getPortIndex()}.
+ * If it's a disable operation, requires the
+ * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or the
+ * calling app must be authorized to manage the active subscription on the target eUICC.
* @param callbackIntent a PendingIntent to launch when the operation completes.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
@@ -1151,6 +1167,18 @@
return;
}
try {
+ // TODO: Uncomment below compat change code once callers are ported to use
+ // switchToSubscription with portIndex for disable operation.
+ // if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ // && getIEuiccController().isCompatChangeEnabled(mContext.getOpPackageName(),
+ // SWITCH_WITHOUT_PORT_INDEX_EXCEPTION_ON_DISABLE)) {
+ // // Apps targeting on Android T and beyond will get exception whenever
+ // // switchToSubscription without portIndex is called with INVALID_SUBSCRIPTION_ID.
+ // Log.e(TAG, "switchToSubscription without portIndex is not allowed for"
+ // + " disable operation");
+ // throw new IllegalArgumentException("Must use switchToSubscription with portIndex"
+ // + " API for disable operation");
+ // }
getIEuiccController().switchToSubscription(mCardId,
subscriptionId, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
@@ -1180,7 +1208,10 @@
* current profile without activating another profile to replace it. If it's a disable
* operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS}
* permission, or the calling app must be authorized to manage the active subscription on
- * the target eUICC.
+ * the target eUICC. From Android T, multiple enabled profiles is supported. Calling apps
+ * targeting on android T must use {@link #switchToSubscription(int, int, PendingIntent)}
+ * API for disable profile, port index can be found from
+ * {@link SubscriptionInfo#getPortIndex()}.
* @param portIndex the index of the port to target for the enabled subscription
* @param callbackIntent a PendingIntent to launch when the operation completes.
*/
@@ -1192,6 +1223,17 @@
return;
}
try {
+ boolean canWriteEmbeddedSubscriptions = mContext.checkCallingOrSelfPermission(
+ Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ == PackageManager.PERMISSION_GRANTED;
+ // If the caller is not privileged caller and does not have the carrier privilege over
+ // any active subscription, do not continue.
+ if (!canWriteEmbeddedSubscriptions && !getIEuiccController()
+ .hasCarrierPrivilegesForPackageOnAnyPhone(mContext.getOpPackageName())) {
+ Log.e(TAG, "Not permitted to use switchToSubscription with portIndex");
+ throw new SecurityException(
+ "Must have carrier privileges to use switchToSubscription with portIndex");
+ }
getIEuiccController().switchToSubscriptionWithPort(mCardId,
subscriptionId, portIndex, mContext.getOpPackageName(), callbackIntent);
} catch (RemoteException e) {
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index dda95b1..19f1a5b 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -55,4 +55,6 @@
List<String> getSupportedCountries(boolean isSupported);
boolean isSupportedCountry(String countryIso);
boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
+ boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage);
+ boolean isCompatChangeEnabled(String callingPackage, long changeId);
}
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 98d13e8..566c725 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -26,16 +26,8 @@
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6600s" />
<option name="hidden-api-checks" value="false" />
- <option name="device-listeners"
- value="com.android.server.wm.flicker.TraceFileReadyListener" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="pull-pattern-keys" value="(\w)+\.winscope" />
- <option name="pull-pattern-keys" value="(\w)+\.mp4" />
- <option name="collect-on-run-ended-only" value="false" />
- <option name="clean-up" value="true" />
- </metrics_collector>
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/sdcard/flicker" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 429541c..71e576a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -44,6 +44,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 219749605)
class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
@@ -119,4 +120,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 5f0176e..ba5698c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -61,6 +61,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group2
+@FlakyTest(bugId = 219757170)
class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
@@ -87,7 +88,7 @@
}
transitions {
device.reopenAppFromOverview(wmHelper)
- wmHelper.waitImeShown()
+ require(wmHelper.waitImeShown()) { "IME didn't show in time" }
}
teardown {
test {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 065b1c2..f834820 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -16,16 +16,19 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,7 +57,13 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppColdTest(testSpec: FlickerTestParameter)
+ : OpenAppFromLauncherTransition(testSpec) {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt
new file mode 100644
index 0000000..0d2869c
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest_ShellTransit.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.wm.flicker.launch
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppColdTest`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] and wait animation to complete
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@FlakyTest(bugId = 219688533)
+class OpenAppColdTest_ShellTransit(testSpec: FlickerTestParameter) : OpenAppColdTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 797919b..00fee82 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -16,22 +16,22 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.Presubmit
-import android.view.Display
import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.Display
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,8 +62,13 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
+open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
: OpenAppFromLauncherTransition(testSpec) {
+ @Before
+ open fun before() {
+ assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
@@ -134,37 +139,6 @@
@Test
override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart()
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun appWindowReplacesLauncherAsTopWindow() {
- assumeFalse(isShellTransitionsEnabled)
- super.appWindowReplacesLauncherAsTopWindow()
- }
-
- @FlakyTest(bugId = 216266712)
- @Test
- fun appWindowReplacesLauncherAsTopWindow_shellTransit() {
- assumeTrue(isShellTransitionsEnabled)
- super.appWindowReplacesLauncherAsTopWindow()
- }
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- assumeFalse(isShellTransitionsEnabled)
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 218470989)
- @Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry_shellTransit() {
- assumeTrue(isShellTransitionsEnabled)
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
- }
-
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
new file mode 100644
index 0000000..1c06495
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.wm.flicker.launch
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app from the recents app view (the overview)
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
+ *
+ * Actions:
+ * Launch [testApp]
+ * Press recents
+ * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
+ * complete (only this action is traced)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@FlakyTest(bugId = 219688533)
+class OpenAppFromOverviewTest_ShellTransit(testSpec: FlickerTestParameter)
+ : OpenAppFromOverviewTest(testSpec) {
+ @Before
+ override fun before() {
+ assumeTrue(isShellTransitionsEnabled)
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 216266712)
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() =
+ super.appWindowReplacesLauncherAsTopWindow()
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 218470989)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index f75c50e..b0e53e9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -16,19 +16,22 @@
package com.android.server.wm.flicker.launch
+import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,11 +60,16 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-class OpenAppNonResizeableTest(testSpec: FlickerTestParameter)
+open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter)
: OpenAppFromLockTransition(testSpec) {
override val testApp = NonResizeableAppHelper(instrumentation)
private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
* and remains visible at the end
@@ -148,6 +156,11 @@
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
+ @FlakyTest(bugId = 218470989)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
new file mode 100644
index 0000000..8a08d07
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app while the device is locked
+ *
+ * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ *
+ * Actions:
+ * Lock the device.
+ * Launch an app on top of the lock screen [testApp] and wait animation to complete
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@FlakyTest(bugId = 219688533)
+class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
+ : OpenAppNonResizeableTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index d2f6c7f1..53560cc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.launch
import android.app.Instrumentation
-import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 3159bf1..2562098 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -17,14 +17,17 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,8 +57,13 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-class OpenAppWarmTest(testSpec: FlickerTestParameter)
+open class OpenAppWarmTest(testSpec: FlickerTestParameter)
: OpenAppFromLauncherTransition(testSpec) {
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
/**
* Defines the transition used to run the test
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt
new file mode 100644
index 0000000..3958dd2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest_ShellTransit.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.wm.flicker.launch
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test warm launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppWarmTest`
+ *
+ * Actions:
+ * Launch [testApp]
+ * Press home
+ * Relaunch an app [testApp] and wait animation to complete (only this action is traced)
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@FlakyTest(bugId = 219688533)
+class OpenAppWarmTest_ShellTransit(testSpec: FlickerTestParameter)
+ : OpenAppWarmTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 5301e02..f21b1d6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -33,12 +33,15 @@
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,7 +64,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) {
+open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp1 = SimpleAppHelper(instrumentation)
@@ -69,6 +72,11 @@
private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
@@ -326,4 +334,4 @@
)
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
new file mode 100644
index 0000000..cffed81
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.quickswitch
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching back to previous app from last opened app
+ *
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
+ *
+ * Actions:
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@FlakyTest(bugId = 219690120)
+class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter)
+ : QuickSwitchBetweenTwoAppsBackTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index f603f6e..2b944c6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -112,10 +112,7 @@
* Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
* doesn't flicker, and disappears before the transition is complete
*/
- @Presubmit
- @Test
- fun rotationLayerAppearsAndVanishes() {
- Assume.assumeFalse(isShellTransitionsEnabled)
+ fun rotationLayerAppearsAndVanishesAssertion() {
testSpec.assertLayers {
this.isVisible(testApp.component)
.then()
@@ -126,11 +123,26 @@
}
}
+ /**
+ * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
+ * doesn't flicker, and disappears before the transition is complete
+ */
+ @Presubmit
+ @Test
+ fun rotationLayerAppearsAndVanishes() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ rotationLayerAppearsAndVanishesAssertion()
+ }
+
+ /**
+ * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
+ * doesn't flicker, and disappears before the transition is complete
+ */
@FlakyTest(bugId = 218484127)
@Test
fun rotationLayerAppearsAndVanishes_shellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- rotationLayerAppearsAndVanishes()
+ rotationLayerAppearsAndVanishesAssertion()
}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index d1bdeed..0becadf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -153,4 +153,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 3ae484b..15fd5e1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,17 +17,20 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
import android.view.WindowManager
import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -76,11 +79,16 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class SeamlessAppRotationTest(
+open class SeamlessAppRotationTest(
testSpec: FlickerTestParameter
) : RotationTransition(testSpec) {
override val testApp = SeamlessRotationAppHelper(instrumentation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
new file mode 100644
index 0000000..d397d59
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.wm.flicker.rotation
+
+import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening an app and cycling through app rotations using seamless rotations
+ *
+ * Currently runs:
+ * 0 -> 90 degrees
+ * 0 -> 90 degrees (with starved UI thread)
+ * 90 -> 0 degrees
+ * 90 -> 0 degrees (with starved UI thread)
+ *
+ * Actions:
+ * Launch an app in fullscreen and supporting seamless rotation (via intent)
+ * Set initial device orientation
+ * Start tracing
+ * Change device orientation
+ * Stop tracing
+ *
+ * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
+ *
+ * To run only the presubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
+ *
+ * To run only the postsubmit assertions add: `--
+ * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
+ * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
+ *
+ * To run only the flaky assertions add: `--
+ * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [RotationTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+@FlakyTest(bugId = 219689723)
+class SeamlessAppRotationTest_ShellTransit(
+ testSpec: FlickerTestParameter
+) : SeamlessAppRotationTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/util/UserIconsTest.java b/tests/Internal/src/com/android/internal/util/UserIconsTest.java
new file mode 100644
index 0000000..cc7b20b
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/util/UserIconsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UserIconsTest {
+
+ @Test
+ public void convertToBitmapAtUserIconSize_sizeIsCorrect() {
+ Resources res = InstrumentationRegistry.getTargetContext().getResources();
+ Drawable icon = UserIcons.getDefaultUserIcon(res, 0, true);
+ Bitmap bitmap = UserIcons.convertToBitmapAtUserIconSize(res, icon);
+ int expectedSize = res.getDimensionPixelSize(R.dimen.user_icon_size);
+
+ assertThat(bitmap.getWidth()).isEqualTo(expectedSize);
+ assertThat(bitmap.getHeight()).isEqualTo(expectedSize);
+ }
+
+}
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index c59a41e..9a9e42b 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -37,8 +37,6 @@
"vts",
],
data: [
- ":NotoSerif-Regular.ttf",
- ":NotoSerif-Bold.ttf",
":UpdatableSystemFontTestCertDer",
":UpdatableSystemFontTest_NotoColorEmoji.ttf",
":UpdatableSystemFontTest_NotoColorEmoji.sig",
@@ -48,7 +46,9 @@
":UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig",
":UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf",
":UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig",
+ ":UpdatableSystemFontTest_NotoSerif-Regular.ttf",
":UpdatableSystemFontTest_NotoSerif-Regular.sig",
+ ":UpdatableSystemFontTest_NotoSerif-Bold.ttf",
":UpdatableSystemFontTest_NotoSerif-Bold.sig",
],
sdk_version: "test_current",
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index 6effa7b..9e2a4b6 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -28,11 +28,7 @@
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
- <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
- <option name="push" value="NotoSerif-Regular.ttf->/data/local/tmp/NotoSerif-Regular.ttf" />
- <option name="push" value="NotoSerif-Bold.ttf->/data/local/tmp/NotoSerif-Bold.ttf" />
- <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig" />
- <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig" />
+ <option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.ttf" />
<option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig" />
<option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.ttf" />
<option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.sig" />
@@ -40,6 +36,10 @@
<option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig" />
<option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf" />
<option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig" />
+ <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.ttf" />
+ <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig" />
+ <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig" />
+ <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.ttf" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 87fda0d..cbe13d9 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -84,7 +84,7 @@
private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji";
private static final String NOTO_COLOR_EMOJI_TTF =
- "/data/local/tmp/NotoColorEmoji.ttf";
+ "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.ttf";
private static final String NOTO_COLOR_EMOJI_SIG =
"/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig";
// A font with revision == 0.
@@ -105,13 +105,13 @@
private static final String NOTO_SERIF_REGULAR_POSTSCRIPT_NAME = "NotoSerif";
private static final String NOTO_SERIF_REGULAR_TTF =
- "/data/local/tmp/NotoSerif-Regular.ttf";
+ "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.ttf";
private static final String NOTO_SERIF_REGULAR_SIG =
"/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig";
private static final String NOTO_SERIF_BOLD_POSTSCRIPT_NAME = "NotoSerif-Bold";
private static final String NOTO_SERIF_BOLD_TTF =
- "/data/local/tmp/NotoSerif-Bold.ttf";
+ "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.ttf";
private static final String NOTO_SERIF_BOLD_SIG =
"/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig";
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
index 64b698d..0bdb3a8 100644
--- a/tests/UpdatableSystemFontTest/testdata/Android.bp
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -21,11 +21,19 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-// An existing module name is reused to avoid merge conflicts.
-// TODO: fix the font file name.
filegroup {
name: "UpdatableSystemFontTest_NotoColorEmoji.ttf",
- srcs: ["NotoColorEmoji.ttf"],
+ srcs: ["UpdatableSystemFontTest_NotoColorEmoji.ttf"],
+}
+
+filegroup {
+ name: "UpdatableSystemFontTest_NotoSerif-Regular.ttf",
+ srcs: ["UpdatableSystemFontTest_NotoSerif-Regular.ttf"],
+}
+
+filegroup {
+ name: "UpdatableSystemFontTest_NotoSerif-Bold.ttf",
+ srcs: ["UpdatableSystemFontTest_NotoSerif-Bold.ttf"],
}
filegroup {
@@ -43,10 +51,6 @@
srcs: ["UpdatableSystemFontTestCert.der"],
}
-genrule_defaults {
- name: "updatable_system_font_increment_font_revision_default",
-}
-
genrule {
name: "UpdatableSystemFontTest_NotoColorEmojiV0.ttf",
srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"],
@@ -124,13 +128,13 @@
genrule {
name: "UpdatableSystemFontTest_NotoSerif-Regular.sig",
defaults: ["updatable_system_font_sig_gen_default"],
- srcs: [":NotoSerif-Regular.ttf"],
+ srcs: ["UpdatableSystemFontTest_NotoSerif-Regular.ttf"],
out: ["UpdatableSystemFontTest_NotoSerif-Regular.sig"],
}
genrule {
name: "UpdatableSystemFontTest_NotoSerif-Bold.sig",
defaults: ["updatable_system_font_sig_gen_default"],
- srcs: [":NotoSerif-Bold.ttf"],
+ srcs: ["UpdatableSystemFontTest_NotoSerif-Bold.ttf"],
out: ["UpdatableSystemFontTest_NotoSerif-Bold.sig"],
}
diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf
similarity index 100%
rename from tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttf
rename to tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttf
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx
similarity index 100%
rename from tests/UpdatableSystemFontTest/testdata/NotoColorEmoji.ttx
rename to tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoColorEmoji.ttx
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf
new file mode 100644
index 0000000..66c1bd2
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttf
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx
new file mode 100644
index 0000000..8c4215e
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Bold.ttx
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="a"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <!-- Currently NotoSerif-Bold.ttf's fontRevision is 1.xx.
+ 100.0 will be sufficiently larger than that. -->
+ <fontRevision value="100.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Feb 16 12:00:00 2022"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="a" width="3000" lsb="93"/> <!-- 3em -->
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <!-- length will be calculated by the compiler. -->
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="0" language="0" nGroups="1">
+ <!-- The font must support at least one of the characters used
+ in OtfFontFileParser to validate the font. -->
+ <map code="0x61" name="a" />
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="a" xMin="0" yMin="0" xMax="300" yMax="300">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="300" on="1" />
+ <pt x="300" y="300" on="1" />
+ <pt x="300" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2022 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ <!-- Android identifies the target font to be updated by PostScript name.
+ To test updating NotoSerif-Bold.ttf, the PostScript needs to be
+ the same as NotoSerif-Bold.ttf here. -->
+ NotoSerif-Bold
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf
new file mode 100644
index 0000000..707ae28
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttf
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx
new file mode 100644
index 0000000..754eae3
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTest_NotoSerif-Regular.ttx
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="a"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <!-- Currently NotoSerif-Regular.ttf's fontRevision is 1.xx.
+ 100.0 will be sufficiently larger than that. -->
+ <fontRevision value="100.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Feb 16 12:00:00 2022"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="a" width="3000" lsb="93"/> <!-- 3em -->
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <!-- length will be calculated by the compiler. -->
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="0" language="0" nGroups="1">
+ <!-- The font must support at least one of the characters used
+ in OtfFontFileParser to validate the font. -->
+ <map code="0x61" name="a" />
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="a" xMin="0" yMin="0" xMax="300" yMax="300">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="300" on="1" />
+ <pt x="300" y="300" on="1" />
+ <pt x="300" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ Copyright (C) 2022 The Android Open Source Project
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ <!-- Android identifies the target font to be updated by PostScript name.
+ To test updating NotoSerif-Regular.ttf, the PostScript needs to be
+ the same as NotoSerif-Regular.ttf here. -->
+ NotoSerif
+ </namerecord>
+ <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+ </namerecord>
+ <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+ http://www.apache.org/licenses/LICENSE-2.0
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index e547400..4cfa93b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -307,7 +307,10 @@
ncCaptor.capture(),
lpCaptor.capture(),
any(),
- argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE),
+ // Subtype integer/name and extras do not have getters; cannot be tested.
+ argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE
+ && nac.getLegacyTypeName().equals(
+ VcnGatewayConnection.NETWORK_INFO_NETWORK_TYPE_STRING)),
any(),
any(),
any());