";
-
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifest);
@@ -2220,14 +2218,14 @@ public class PackageParser {
sa.recycle();
final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
- SDK_VERSION, SDK_CODENAMES, outError, pkgName);
+ SDK_VERSION, SDK_CODENAMES, outError);
if (minSdkVersion < 0) {
mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
return null;
}
final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
- targetCode, SDK_CODENAMES, outError, pkgName);
+ targetCode, SDK_CODENAMES, outError);
if (targetSdkVersion < 0) {
mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
return null;
@@ -2612,15 +2610,13 @@ public class PackageParser {
* @param platformSdkCodenames array of allowed pre-release SDK codenames
* for this platform
* @param outError output array to populate with error, if applicable
- * @param pkgName for debug logging
* @return the targetSdkVersion to use at runtime, or -1 if the package is
* not compatible with this platform
* @hide Exposed for unit testing only.
*/
public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @NonNull String[] platformSdkCodenames,
- @NonNull String[] outError,
- String pkgName) {
+ @NonNull String[] outError) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return targetVers;
@@ -2632,15 +2628,6 @@ public class PackageParser {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
- // TODO(b/294161396): add a check for a "true REL" flag.
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- targetCode)) {
- Slog.w(TAG, "Package " + pkgName + " requires development platform " + targetCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return Build.VERSION.SDK_INT;
- }
-
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + targetCode
@@ -2687,15 +2674,13 @@ public class PackageParser {
* @param platformSdkCodenames array of allowed prerelease SDK codenames
* for this platform
* @param outError output array to populate with error, if applicable
- * @param pkgName for debug logging
* @return the minSdkVersion to use at runtime, or -1 if the package is not
* compatible with this platform
* @hide Exposed for unit testing only.
*/
public static int computeMinSdkVersion(@IntRange(from = 1) int minVers,
@Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
- @NonNull String[] platformSdkCodenames, @NonNull String[] outError,
- String pkgName) {
+ @NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
// If it's a release SDK, make sure we meet the minimum SDK requirement.
if (minCode == null) {
if (minVers <= platformSdkVersion) {
@@ -2714,15 +2699,6 @@ public class PackageParser {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
- // TODO(b/294161396): add a check for a "true REL" flag.
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- minCode)) {
- Slog.w(TAG, "Package " + pkgName + " requires min development platform " + minCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return Build.VERSION.SDK_INT;
- }
-
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index be8b2a20cfb1d98005273fab7c4d6790c1356807..65f56f68ed3f071ddfacceb17adfc155106d1a08 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -486,7 +486,7 @@ public class ServiceInfo extends ComponentInfo
* Here is an example:
*
* <uses-permission
- * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+ * android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
* />
* <service
* android:name=".MySpecialForegroundService"
@@ -506,7 +506,7 @@ public class ServiceInfo extends ComponentInfo
* in both platforms.
*
* <uses-permission
- * android:name="android.permissions.FOREGROUND_SERVICE_SPECIAL_USE"
+ * android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
* android:maxSdkVersion="last_sdk_version_without_type_foo"
* />
* <service
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 3ffbe1d1c71bca9687f1756581635e9593bae05e..2ea6513c4d771690dc4401db0d83517024b14266 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -100,20 +100,6 @@
}
],
"presubmit-large":[
- {
- "name":"CtsContentTestCases",
- "options":[
- {
- "exclude-annotation":"androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation":"org.junit.Ignore"
- },
- {
- "include-filter":"android.content.pm.cts"
- }
- ]
- },
{
"name":"CtsUsesNativeLibraryTest",
"options":[
@@ -155,6 +141,20 @@
}
],
"postsubmit":[
+ {
+ "name":"CtsContentTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ },
+ {
+ "include-filter":"android.content.pm.cts"
+ }
+ ]
+ },
{
"name":"CtsAppSecurityHostTestCases",
"options":[
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index dcb1b7eb83201821fe66553bcb0c017147aba930..4f6bcb6f0be5b10fb4bbf4aacd0bcf66d4cd9919 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -578,14 +578,14 @@ public class ApkLiteParseUtils {
ParseResult targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
targetVer, targetCode, SDK_CODENAMES, input,
- allowUnknownCodenames, codePath);
+ allowUnknownCodenames);
if (targetResult.isError()) {
return input.error(targetResult);
}
targetSdkVersion = targetResult.getResult();
ParseResult minResult = FrameworkParsingPackageUtils.computeMinSdkVersion(
- minVer, minCode, SDK_VERSION, SDK_CODENAMES, input, codePath);
+ minVer, minCode, SDK_VERSION, SDK_CODENAMES, input);
if (minResult.isError()) {
return input.error(minResult);
}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 30e289f626982df6c69927ed8fe3f336cf19f662..3e1c5bb3d7ec170a47f61f222851d0aa2e773541 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -293,14 +293,11 @@ public class FrameworkParsingPackageUtils {
* {@code null} otherwise
* @param platformSdkVersion platform SDK version number, typically Build.VERSION.SDK_INT
* @param platformSdkCodenames array of allowed prerelease SDK codenames for this platform
- * @param input parsing context
- * @param pkgName for debug logging
* @return the minSdkVersion to use at runtime if successful
*/
public static ParseResult computeMinSdkVersion(@IntRange(from = 1) int minVers,
@Nullable String minCode, @IntRange(from = 1) int platformSdkVersion,
- @NonNull String[] platformSdkCodenames, @NonNull ParseInput input,
- String pkgName) {
+ @NonNull String[] platformSdkCodenames, @NonNull ParseInput input) {
// If it's a release SDK, make sure we meet the minimum SDK requirement.
if (minCode == null) {
if (minVers <= platformSdkVersion) {
@@ -319,15 +316,6 @@ public class FrameworkParsingPackageUtils {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
- // TODO(b/294161396): add a check for a "true REL" flag.
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- minCode)) {
- Slog.w(TAG, "Parsed package " + pkgName + " requires min development platform "
- + minCode + ", returning current version " + Build.VERSION.SDK_INT);
- return input.success(Build.VERSION.SDK_INT);
- }
-
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -370,38 +358,29 @@ public class FrameworkParsingPackageUtils {
* @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
* @param allowUnknownCodenames allow unknown codenames, if true this method will accept unknown
* (presumed to be future) codenames
- * @param pkgName for debug logging
* @return the targetSdkVersion to use at runtime if successful
*/
public static ParseResult computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @NonNull String[] platformSdkCodenames,
- @NonNull ParseInput input, boolean allowUnknownCodenames,
- String pkgName) {
+ @NonNull ParseInput input, boolean allowUnknownCodenames) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return input.success(targetVers);
}
- // TODO(b/294161396): add a check for a "true REL" flag.
- // If it's a pre-release SDK and the codename matches this platform, it
- // definitely targets this SDK.
- if (matchTargetCode(platformSdkCodenames, targetCode)) {
- return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
- }
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- targetCode)) {
- Slog.w(TAG, "Parsed package " + pkgName + " requires development platform " + targetCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return input.success(Build.VERSION.SDK_INT);
- }
-
try {
if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
} catch (IllegalArgumentException e) {
- return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
+ // isAtMost() throws it when encountering an older SDK codename
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage());
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, it
+ // definitely targets this SDK.
+ if (matchTargetCode(platformSdkCodenames, targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
// Otherwise, we're looking at an incompatible pre-release SDK.
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index ef3842aeb348a883f5c6974e2c0f1c89359e99c2..0f284f491c29cdcd88ae660e8ab461efaefaaab3 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -528,6 +528,10 @@ public final class AssetManager implements AutoCloseable {
if (!mOpen) {
throw new RuntimeException("AssetManager has been closed");
}
+ // Let's still check if the native object exists, given all the memory corruptions.
+ if (mObject == 0) {
+ throw new RuntimeException("AssetManager is open but the native object is gone");
+ }
}
/**
@@ -1153,6 +1157,7 @@ public final class AssetManager implements AutoCloseable {
int[] getAttributeResolutionStack(long themePtr, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes, @StyleRes int xmlStyle) {
synchronized (this) {
+ ensureValidLocked();
return nativeAttributeResolutionStack(
mObject, themePtr, xmlStyle, defStyleAttr, defStyleRes);
}
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 6c0735692db9fa129b7ee2ccb228e6026c8f1b11..c143acb34c5f8052f774897665cc87ba5e2d8e9a 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -62,7 +62,7 @@ public final class StringBlock implements Closeable {
private static final String TAG = "AssetManager";
private static final boolean localLOGV = false;
- private final long mNative;
+ private long mNative; // final, but gets modified when closed
private final boolean mUseSparse;
private final boolean mOwnsNative;
@@ -207,6 +207,7 @@ public final class StringBlock implements Closeable {
if (mOwnsNative) {
nativeDestroy(mNative);
}
+ mNative = 0;
}
}
}
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 3915a6ccb46dfdc9ab70f339561d501d3728fdf9..16fd1f7277a7789fe4040ad69fb8ac1baea5ad4d 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -73,7 +73,9 @@ public final class XmlBlock implements AutoCloseable {
private void decOpenCountLocked() {
mOpenCount--;
if (mOpenCount == 0) {
+ mStrings.close();
nativeDestroy(mNative);
+ mNative = 0;
if (mAssets != null) {
mAssets.xmlBlockGone(hashCode());
}
@@ -621,7 +623,7 @@ public final class XmlBlock implements AutoCloseable {
}
private @Nullable final AssetManager mAssets;
- private final long mNative;
+ private long mNative; // final, but gets reset on close
/*package*/ final StringBlock mStrings;
private boolean mOpen = true;
private int mOpenCount = 1;
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index fc3dc796d3eddb1fe0a872eb311fdd8133d0c3d9..946b5f329c0b74b769b8c264cb2b0101ec30a4b3 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -261,7 +261,10 @@ public final class CreateCredentialRequest implements Parcelable {
/**
* @param type the type of the credential to be stored
- * @param credentialData the full credential creation request data
+ * @param credentialData the full credential creation request data, which must at minimum
+ * contain the required fields observed at the
+ * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
+ * because they are required for properly displaying the system credential selector UI
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial creation candidate query stage
*/
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index c2a0062b43e878f98cb0e19e7637e9397a239b1a..eedb25b1aa8f29aee8979b47d36e7c0c2ba1ab89 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -123,7 +123,7 @@ public final class CredentialManager {
* credential, display a picker when multiple credentials exist, etc.
* Callers (e.g. browsers) may optionally set origin in {@link GetCredentialRequest} for an
* app different from their own, to be able to get credentials on behalf of that app. They would
- * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
* @param context the context used to launch any UI needed; use an activity context to make sure
@@ -209,9 +209,9 @@ public final class CredentialManager {
*
* This API doesn't invoke any UI. It only performs the preparation work so that you can
* later launch the remaining get-credential operation (involves UIs) through the {@link
- * #getCredential(PrepareGetCredentialResponse.PendingGetCredentialHandle, Context,
+ * #getCredential(Context, PrepareGetCredentialResponse.PendingGetCredentialHandle,
* CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to
- * the {@link #getCredential(GetCredentialRequest, Context, CancellationSignal, Executor,
+ * the {@link #getCredential(Context, GetCredentialRequest, CancellationSignal, Executor,
* OutcomeReceiver)} API that executes the whole operation in one call.
*
* @param request the request specifying type(s) of credentials to get from the user
@@ -261,7 +261,7 @@ public final class CredentialManager {
* storing the new credential, etc.
* Callers (e.g. browsers) may optionally set origin in {@link CreateCredentialRequest} for an
* app different from their own, to be able to get credentials on behalf of that app. They would
- * need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
+ * need additional permission {@code CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
* @param context the context used to launch any UI needed; use an activity context to make sure
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index d66b8f00a2e921a4deeeed7b77834000f1b3ac0a..2bd3e4c0fe08c7528b8ba22705ea81849ba08c67 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -113,7 +113,6 @@ public final class CredentialProviderInfo implements Parcelable {
/**
* Returns whether the provider is set as primary by the user.
*
- * @hide
*/
public boolean isPrimary() {
return mIsPrimary;
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 71b8f3f8e17a79feee79c129a8ebda724433dff1..e58798354d3cd166a2d2b923fb967bfa3e1f2c2c 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -15,7 +15,7 @@ instead use the generic {@link android.database} classes.
sqlite3 command-line
database tool. On your development machine, run the tool from the
platform-tools/
folder of your SDK. On the emulator, run the tool
-with adb shell, for example, adb -e shell sqlite3
.
+with adb shell, for example, adb shell sqlite3
.
The version of SQLite depends on the version of Android. See the following table:
@@ -41,15 +41,19 @@ with adb shell, for example, adb -e shell sqlite3
.
- If available, use the sqlite3 tool, for example:
-
adb -e shell sqlite3 --version
.
+ adb shell sqlite3 --version
.
- Create and query an in-memory database as shown in the following code sample:
String query = "select sqlite_version() AS sqlite_version";
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
Cursor cursor = db.rawQuery(query, null);
String sqliteVersion = "";
- if (cursor.moveToNext()) {
- sqliteVersion = cursor.getString(0);
+ try {
+ if (cursor.moveToNext()) {
+ sqliteVersion = cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
}
diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..d4a35b25f6236912d267998d161566dbff6484be
--- /dev/null
+++ b/core/java/android/flags/BooleanFlag.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value will always be the same during the lifetime of the process it is read in.
+ *
+ * @hide
+ */
+public class BooleanFlag extends BooleanFlagBase {
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ BooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public BooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..985dbe3f2f01e248f9d125d6419b0b1cf1d45929
--- /dev/null
+++ b/core/java/android/flags/BooleanFlagBase.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+abstract class BooleanFlagBase implements Flag {
+
+ private final String mNamespace;
+ private final String mName;
+ private String mLabel;
+ private String mDescription;
+ private String mCategoryName;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ BooleanFlagBase(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ mLabel = name;
+ }
+
+ public abstract Boolean getDefault();
+
+ @Override
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public BooleanFlagBase defineMetaData(String label, String description, String categoryName) {
+ mLabel = label;
+ mDescription = description;
+ mCategoryName = categoryName;
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public String getCategoryName() {
+ return mCategoryName;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getDefault() + "]";
+ }
+}
diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..271a8c5f4d15f4d572e986644ad856982f226c42
--- /dev/null
+++ b/core/java/android/flags/DynamicBooleanFlag.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value may be different from one read to the next.
+ *
+ * @hide
+ */
+public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag {
+
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/DynamicFlag.java b/core/java/android/flags/DynamicFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..68819c58c064d4ac0a097900632fd3e89ffcd92c
--- /dev/null
+++ b/core/java/android/flags/DynamicFlag.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+/**
+ * A flag for which the value may be different from one read to the next.
+ *
+ * @param The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface DynamicFlag extends Flag {
+ @Override
+ default boolean isDynamic() {
+ return true;
+ }
+}
diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d3112c35d51268ace5dedad4de9b218d16b078f
--- /dev/null
+++ b/core/java/android/flags/FeatureFlags.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class for querying constants from the system - primarily booleans.
+ *
+ * Clients using this class can define their flags and their default values in one place,
+ * can override those values on running devices for debugging and testing purposes, and can control
+ * what flags are available to be used on release builds.
+ *
+ * TODO(b/279054964): A lot. This is skeleton code right now.
+ * @hide
+ */
+public class FeatureFlags {
+ private static final String TAG = "FeatureFlags";
+ private static FeatureFlags sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ private final Set> mKnownFlags = new ArraySet<>();
+ private final Set> mDirtyFlags = new ArraySet<>();
+
+ private IFeatureFlags mIFeatureFlags;
+ private final Map> mBooleanOverrides = new HashMap<>();
+ private final Set mListeners = new HashSet<>();
+
+ /**
+ * Obtain a per-process instance of FeatureFlags.
+ * @return A singleton instance of {@link FeatureFlags}.
+ */
+ @NonNull
+ public static FeatureFlags getInstance() {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new FeatureFlags();
+ }
+ }
+
+ return sInstance;
+ }
+
+ /** See {@link FeatureFlagsFake}. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void setInstance(FeatureFlags instance) {
+ synchronized (sInstanceLock) {
+ sInstance = instance;
+ }
+ }
+
+ private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() {
+ @Override
+ public void onFlagChange(SyncableFlag flag) {
+ for (Flag> f : mKnownFlags) {
+ if (flagEqualsSyncableFlag(f, flag)) {
+ if (f instanceof DynamicFlag>) {
+ if (f instanceof DynamicBooleanFlag) {
+ String value = flag.getValue();
+ if (value == null) { // Null means any existing overrides were erased.
+ value = ((DynamicBooleanFlag) f).getDefault().toString();
+ }
+ addBooleanOverride(flag.getNamespace(), flag.getName(), value);
+ }
+ FeatureFlags.this.onFlagChange((DynamicFlag>) f);
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ private FeatureFlags() {
+ this(null);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public FeatureFlags(IFeatureFlags iFeatureFlags) {
+ mIFeatureFlags = iFeatureFlags;
+
+ if (mIFeatureFlags != null) {
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ // Shouldn't happen with things passed into tests.
+ Log.e(TAG, "Could not register callbacks!", e);
+ }
+ }
+ }
+
+ /**
+ * Construct a new {@link BooleanFlag}.
+ *
+ * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static BooleanFlag booleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new BooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Construct a new {@link FusedOffFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the
+ * flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOffFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link FusedOnFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOnFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link DynamicBooleanFlag}.
+ *
+ * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers
+ * the flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static DynamicBooleanFlag dynamicBooleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Add a listener to be alerted when a {@link DynamicFlag} changes.
+ *
+ * See also {@link #removeChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to add.
+ */
+ public void addChangeListener(@NonNull ChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was added earlier.
+ *
+ * See also {@link #addChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to remove.
+ */
+ public void removeChangeListener(@NonNull ChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ protected void onFlagChange(@NonNull DynamicFlag> flag) {
+ for (ChangeListener l : mListeners) {
+ l.onFlagChanged(flag);
+ }
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
+ *
+ * The first time a flag is read, its value is cached for the lifetime of the process.
+ */
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns false.
+ */
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return false;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns true;
+ */
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return true;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Can return a different value for the flag each time it is called if an override comes in.
+ */
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ private boolean getBooleanInternal(Flag flag) {
+ sync();
+ Map ns = mBooleanOverrides.get(flag.getNamespace());
+ Boolean value = null;
+ if (ns != null) {
+ value = ns.get(flag.getName());
+ }
+ if (value == null) {
+ throw new IllegalStateException("Boolean flag being read but was not synced: " + flag);
+ }
+
+ return value;
+ }
+
+ private > T addFlag(T flag) {
+ synchronized (FeatureFlags.class) {
+ mDirtyFlags.add(flag);
+ mKnownFlags.add(flag);
+ }
+ return flag;
+ }
+
+ /**
+ * Sync any known flags that have not yet been synced.
+ *
+ * This is called implicitly when any flag is read, and is not generally needed except in
+ * exceptional circumstances.
+ */
+ public void sync() {
+ synchronized (FeatureFlags.class) {
+ if (mDirtyFlags.isEmpty()) {
+ return;
+ }
+ syncInternal(mDirtyFlags);
+ mDirtyFlags.clear();
+ }
+ }
+
+ /**
+ * Called when new flags have been declared. Gives the implementation a chance to act on them.
+ *
+ * Guaranteed to be called from a synchronized, thread-safe context.
+ */
+ protected void syncInternal(Set> dirtyFlags) {
+ IFeatureFlags iFeatureFlags = bind();
+ List syncableFlags = new ArrayList<>();
+ for (Flag> f : dirtyFlags) {
+ syncableFlags.add(flagToSyncableFlag(f));
+ }
+
+ List serverFlags = List.of(); // Need to initialize the list with something.
+ try {
+ // New values come back from the service.
+ serverFlags = iFeatureFlags.syncFlags(syncableFlags);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ for (Flag> f : dirtyFlags) {
+ boolean found = false;
+ for (SyncableFlag sf : serverFlags) {
+ if (flagEqualsSyncableFlag(f, sf)) {
+ if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) {
+ addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue());
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (f instanceof BooleanFlag) {
+ addBooleanOverride(
+ f.getNamespace(),
+ f.getName(),
+ ((BooleanFlag) f).getDefault() ? "true" : "false");
+ }
+ }
+ }
+ }
+
+ private void addBooleanOverride(String namespace, String name, String override) {
+ Map nsOverrides = mBooleanOverrides.get(namespace);
+ if (nsOverrides == null) {
+ nsOverrides = new HashMap<>();
+ mBooleanOverrides.put(namespace, nsOverrides);
+ }
+ nsOverrides.put(name, parseBoolean(override));
+ }
+
+ private SyncableFlag flagToSyncableFlag(Flag> f) {
+ return new SyncableFlag(
+ f.getNamespace(),
+ f.getName(),
+ f.getDefault().toString(),
+ f instanceof DynamicFlag>);
+ }
+
+ private IFeatureFlags bind() {
+ if (mIFeatureFlags == null) {
+ mIFeatureFlags = IFeatureFlags.Stub.asInterface(
+ ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE));
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to listen for flag changes!");
+ }
+ }
+
+ return mIFeatureFlags;
+ }
+
+ static boolean parseBoolean(String value) {
+ // Check for a truish string.
+ boolean result = value.equalsIgnoreCase("true")
+ || value.equals("1")
+ || value.equalsIgnoreCase("t")
+ || value.equalsIgnoreCase("on");
+ if (!result) { // Expect a falsish string, else log an error.
+ if (!(value.equalsIgnoreCase("false")
+ || value.equals("0")
+ || value.equalsIgnoreCase("f")
+ || value.equalsIgnoreCase("off"))) {
+ Log.e(TAG,
+ "Tried parsing " + value + " as boolean but it doesn't look like one. "
+ + "Value expected to be one of true|false, 1|0, t|f, on|off.");
+ }
+ }
+ return result;
+ }
+
+ private static boolean flagEqualsSyncableFlag(Flag> f, SyncableFlag sf) {
+ return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace());
+ }
+
+
+ /**
+ * A simpler listener that is alerted when a {@link DynamicFlag} changes.
+ *
+ * See {@link #addChangeListener(ChangeListener)}
+ */
+ public interface ChangeListener {
+ /**
+ * Called when a {@link DynamicFlag} changes.
+ *
+ * @param flag The flag that has changed.
+ */
+ void onFlagChanged(DynamicFlag> flag);
+ }
+}
diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java
new file mode 100644
index 0000000000000000000000000000000000000000..daedcdae0b5f5ed733e0cdc2f98f3ad770c01edd
--- /dev/null
+++ b/core/java/android/flags/FeatureFlagsFake.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link FeatureFlags} for testing.
+ *
+ * Before you read a flag from using this Fake, you must set that flag using
+ * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic.
+ *
+ * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code
+ * under test, (instead of dependency injection), you can pass an instance of this fake to
+ * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null,
+ * to ensure hermetic testing - you don't want static state persisting between your test methods.
+ *
+ * @hide
+ */
+public class FeatureFlagsFake extends FeatureFlags {
+ private final Map mFlagValues = new HashMap<>();
+ private final Set mReadFlags = new HashSet<>();
+
+ public FeatureFlagsFake(IFeatureFlags iFeatureFlags) {
+ super(iFeatureFlags);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ protected void syncInternal(Set> dirtyFlags) {
+ }
+
+ /**
+ * Explicitly set a flag's value for reading in tests.
+ *
+ * You _must_ call this for every flag your code-under-test will read. Otherwise, an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those
+ * flags having a fixed value at compile time, since unit tests should still test the state of
+ * those flags as both true and false. I.e. a flag that is off might be turned on in a future
+ * build or vice versa.
+ *
+ * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags
+ * are held stable in the system, so changing a value after reading would not match
+ * real-implementation behavior.
+ *
+ * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that
+ * are registered for the supplied flag if the flag is a {@link DynamicFlag}.
+ *
+ * @param flag The BooleanFlag that you want to set a value for.
+ * @param value The value that the flag should return when accessed.
+ */
+ public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) {
+ if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) {
+ throw new RuntimeException(
+ "You can not set the value of a flag after it has been read. Tried to set "
+ + flag + " to " + value + " but it already " + mFlagValues.get(flag));
+ }
+ mFlagValues.put(flag, value);
+ if (flag instanceof DynamicBooleanFlag) {
+ onFlagChange((DynamicFlag>) flag);
+ }
+ }
+
+ private boolean requireFlag(BooleanFlagBase flag) {
+ if (!mFlagValues.containsKey(flag)) {
+ throw new IllegalStateException(
+ "Tried to access " + flag + " in test but no overrided specified. You must "
+ + "call #setFlagValue for each flag read in a test.");
+ }
+ mReadFlags.add(flag);
+
+ return mFlagValues.get(flag);
+ }
+
+}
diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java
new file mode 100644
index 0000000000000000000000000000000000000000..b97a4c8a0fe76010840d8494163beb4d5b1870ab
--- /dev/null
+++ b/core/java/android/flags/Flag.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for constants read via {@link android.flags.FeatureFlags}.
+ *
+ * @param The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface Flag {
+ /** The namespace for a flag. Should combine uniquely with its name. */
+ @NonNull
+ String getNamespace();
+
+ /** The name of the flag. Should combine uniquely with its namespace. */
+ @NonNull
+ String getName();
+
+ /** The value of this flag if no override has been set. Null values are not supported. */
+ @NonNull
+ T getDefault();
+
+ /** Returns true if the value of this flag can change at runtime. */
+ default boolean isDynamic() {
+ return false;
+ }
+
+ /**
+ * Add human-readable details to the flag. Flag client's are not required to set this.
+ *
+ * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}.
+ *
+ * @return Returns `this`, to make a fluent api.
+ */
+ Flag defineMetaData(String label, String description, String categoryName);
+
+ /**
+ * A human-readable name for the flag. Defaults to {@link #getName()}
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ @NonNull
+ default String getLabel() {
+ return getName();
+ }
+
+ /**
+ * A human-readable description for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getDescription() {
+ return null;
+ }
+
+ /**
+ * A human-readable category name for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getCategoryName() {
+ return null;
+ }
+}
diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..6844b8faafef08c8a15e0dbc9b016fc65041888f
--- /dev/null
+++ b/core/java/android/flags/FusedOffFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a false value.
+ *
+ * The flag can never be changed or overridden. It is false at compile time.
+ *
+ * @hide
+ */
+public final class FusedOffFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOffFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return false;
+ }
+
+ @Override
+ public FusedOffFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9adba7595c11413558eded02798bc2f90ce7dce
--- /dev/null
+++ b/core/java/android/flags/FusedOnFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a true value.
+ *
+ * The flag can never be changed or overridden. It is true at compile time.
+ *
+ * @hide
+ */
+public final class FusedOnFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOnFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return true;
+ }
+
+ @Override
+ public FusedOnFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
new file mode 100644
index 0000000000000000000000000000000000000000..3efcec97fe6d51dae2318afb16ca7bec421e3118
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlags.aidl
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.flags;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+
+/**
+ * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}.
+ *
+ * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to
+ * interface with the service. FeatureFlags is the "client" in this documentation.
+ *
+ * The methods allow client apps to communicate what flags they care about, and receive back
+ * current values for those flags. For stable flags, this is the finalized value until the device
+ * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the
+ * future. Clients can listen for changes to flag values so that it can react accordingly.
+ * @hide
+ */
+interface IFeatureFlags {
+ /**
+ * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of
+ * interest.
+ *
+ * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which
+ * includes what it thinks the default values of the flags are.
+ *
+ * The response will contain a list of matching SyncableFlags, whose values are set to what the
+ * value of the flags actually are. The client should update its internal state flag data to
+ * match.
+ *
+ * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the
+ * service will cache the passed-in value, and return it back out. If, however, a different
+ * client has synced that flag with the service previously, FeatureFlagsService will return the
+ * existing cached value, which may or may not be what the current client passed in. This allows
+ * FeatureFlagsService to keep clients in agreement with one another.
+ */
+ List syncFlags(in List flagList);
+
+ /**
+ * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag}
+ * changes.
+ */
+ void registerCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Remove a {@link IFeatureFlagsCallback} that was previously registered with
+ * {@link #registerCallback}.
+ */
+ void unregisterCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't
+ * cache them. See {@link #syncFlags}.
+ *
+ * You almost certainly don't want this method. This is intended for the Flag Flipper
+ * application that needs to query the state of system but doesn't want to affect it by
+ * doing so. All other clients should use {@link syncFlags}.
+ */
+ List queryFlags(in List flagList);
+
+ /**
+ * Change a flags value in the system.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void overrideFlag(in SyncableFlag flag);
+
+ /**
+ * Restore a flag to its default value.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void resetFlag(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/core/java/android/flags/IFeatureFlagsCallback.aidl
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
rename to core/java/android/flags/IFeatureFlagsCallback.aidl
index 9e026576e8427df08a99d0ea91acec9fcf856e96..f708667ea4c42b297e667f0551519879eb2d76d0 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
+++ b/core/java/android/flags/IFeatureFlagsCallback.aidl
@@ -12,17 +12,20 @@
* 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.multishade.shared.model
+ package android.flags;
+
+import android.flags.SyncableFlag;
-/** Enumerates all known shade IDs. */
-enum class ShadeId {
- /** ID of the shade on the left in dual shade configurations. */
- LEFT,
- /** ID of the shade on the right in dual shade configurations. */
- RIGHT,
- /** ID of the single shade in single shade configurations. */
- SINGLE,
-}
+/**
+ * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag}
+ * changes.
+ *
+ * DynamicFlags can change at run time. Stable flags will never result in a call to this method.
+ *
+ * @hide
+ */
+oneway interface IFeatureFlagsCallback {
+ void onFlagChange(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..fa125c4a159cc10c12792677d0d27c086106a035
--- /dev/null
+++ b/core/java/android/flags/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1306523
+
+mankoff@google.com
+pixel@google.com
+
+dsandler@android.com
+
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/Quantizer.java b/core/java/android/flags/SyncableFlag.aidl
similarity index 82%
rename from packages/SystemUI/monet/src/com/android/systemui/monet/quantize/Quantizer.java
rename to core/java/android/flags/SyncableFlag.aidl
index 136e5a55c191434bf17a490e512d37e042e90d54..1526ec148967d0eda4bb4e50fae297578b1f1769 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/Quantizer.java
+++ b/core/java/android/flags/SyncableFlag.aidl
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.monet.quantize;
+package android.flags;
-interface Quantizer {
- QuantizerResult quantize(int[] pixels, int maxColors);
-}
+/**
+ * A parcelable data class for serializing {@link Flag} across a Binder.
+ */
+parcelable SyncableFlag;
\ No newline at end of file
diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java
new file mode 100644
index 0000000000000000000000000000000000000000..449bcc3c49f530bdaf1b91b5eaecd229cfdea57d
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SyncableFlag implements Parcelable {
+ private final String mNamespace;
+ private final String mName;
+ private final String mValue;
+ private final boolean mDynamic;
+ private final boolean mOverridden;
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic) {
+ this(namespace, name, value, dynamic, false);
+ }
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic,
+ boolean overridden
+ ) {
+ mNamespace = namespace;
+ mName = name;
+ mValue = value;
+ mDynamic = dynamic;
+ mOverridden = overridden;
+ }
+
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public String getValue() {
+ return mValue;
+ }
+
+ public boolean isDynamic() {
+ return mDynamic;
+ }
+
+ public boolean isOverridden() {
+ return mOverridden;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() {
+ public SyncableFlag createFromParcel(Parcel in) {
+ return new SyncableFlag(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readBoolean(),
+ in.readBoolean());
+ }
+
+ public SyncableFlag[] newArray(int size) {
+ return new SyncableFlag[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mNamespace);
+ dest.writeString(mName);
+ dest.writeString(mValue);
+ dest.writeBoolean(mDynamic);
+ dest.writeBoolean(mOverridden);
+ }
+
+ @Override
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getValue() + "]";
+ }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 35ae11c7397cc14f0215c23e823db1d97075b315..9b9e3db1e0c7c348aa0b0af775997f1e810626ee 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -147,6 +147,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
private Context mContext;
private IAuthService mService;
+ // LINT.IfChange
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
* @param context The {@link Context} that will be used to build the prompt.
@@ -420,6 +421,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
return this;
@@ -432,6 +434,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setReceiveSystemEvents(boolean set) {
mPromptInfo.setReceiveSystemEvents(set);
return this;
@@ -445,6 +448,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
return this;
@@ -457,10 +461,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIsForLegacyFingerprintManager(int sensorId) {
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index addd622eef356e1772cc33a00e8167124e5e3a8f..17cd18cc41825963782c1096d75bc08187c80fdf 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -48,7 +48,8 @@ interface IBiometricAuthenticator {
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- long requestId, int cookie, boolean allowBackgroundAuthentication);
+ long requestId, int cookie, boolean allowBackgroundAuthentication,
+ boolean isForLegacyFingerprintManager);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 02aad1dc4b4b0f139ef6d2dded3715628183070c..e275078741678c7c4602d977a67775bf0c5c3a61 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -113,6 +113,7 @@ public class PromptInfo implements Parcelable {
dest.writeBoolean(mIsForLegacyFingerprintManager);
}
+ // LINT.IfChange
public boolean containsTestConfigurations() {
if (mIsForLegacyFingerprintManager
&& mAllowedSensorIds.size() == 1
@@ -122,6 +123,10 @@ public class PromptInfo implements Parcelable {
return true;
} else if (mAllowBackgroundAuthentication) {
return true;
+ } else if (mIsForLegacyFingerprintManager) {
+ return true;
+ } else if (mIgnoreEnrollmentState) {
+ return true;
}
return false;
}
@@ -144,6 +149,7 @@ public class PromptInfo implements Parcelable {
}
return false;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a4dd0655917e94fc8b53f698a234c1697a7e3421..d352be16ae0c8a4d1df1c7cb5a6dd09c13e6dd4d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -212,14 +212,7 @@ public final class CameraCharacteristics extends CameraMetadata
* #rb { border-right-width: thick; }
*
+ *
+ * LEGACY-level guaranteed configurations
+ *
* Legacy devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}) support at
* least the following stream combinations:
*
- *
LEGACY-level guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
@@ -644,13 +646,13 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
+ * LIMITED-level additional guaranteed configurations
+ *
* Limited-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices
* support at least the following stream combinations in addition to those for
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY} devices:
*
- *
LIMITED-level additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
@@ -663,13 +665,13 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
+ * FULL-level additional guaranteed configurations
+ *
* FULL-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_FULL FULL}) devices
* support at least the following stream combinations in addition to those for
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
*
- *
FULL-level additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
@@ -682,14 +684,14 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
+ * RAW-capability additional guaranteed configurations
+ *
* RAW-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes
* {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}) devices additionally support
* at least the following stream combinations on both
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_FULL FULL} and
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
*
- *
RAW-capability additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
@@ -704,6 +706,8 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
+ * BURST-capability additional guaranteed configurations
+ *
* BURST-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes
* {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}) devices
* support at least the below stream combinations in addition to those for
@@ -712,8 +716,6 @@ public abstract class CameraDevice implements AutoCloseable {
* list for FULL-level devices, so this table is only relevant for LIMITED-level devices that
* support the BURST_CAPTURE capability.
*
- *
BURST-capability additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Sample use case(s) |
* Type | Max size | Type | Max size |
@@ -723,6 +725,8 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
+ * LEVEL-3 additional guaranteed configurations
+ *
* LEVEL-3 ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_3 LEVEL_3})
* support at least the following stream combinations in addition to the combinations for
@@ -730,8 +734,6 @@ public abstract class CameraDevice implements AutoCloseable {
* RAW capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes
* {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}):
*
- *
LEVEL-3 additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Target 4 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size | Type | Max size |
@@ -740,14 +742,16 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
- *BACKWARD_COMPATIBLE devices capable of streaming concurrently with other devices as described by
- * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the
+ *
Concurrent stream guaranteed configurations
+ *
+ * BACKWARD_COMPATIBLE devices capable of streaming concurrently with other devices as
+ * described by {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the
* following guaranteed streams (when streaming concurrently with other devices)
+ *
* Note: The sizes mentioned for these concurrent streams are the maximum sizes guaranteed
* to be supported. Sizes smaller than these, obtained by {@link StreamConfigurationMap#getOutputSizes} for a particular format, are supported as well.
*
- * Concurrent stream guaranteed configurations
- *
+ *
*
* Target 1 | Target 2 | Sample use case(s) |
* Type | Max size | Type | Max size |
@@ -783,6 +787,8 @@ public abstract class CameraDevice implements AutoCloseable {
* level and capabilities. Calling createCaptureSession with both JPEG and HEIC outputs is not
* supported.
*
+ * LEGACY-level additional guaranteed combinations with multi-resolution outputs
+ *
* Devices capable of multi-resolution output for a particular format (
* {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}
* returns a non-empty list) support using {@link MultiResolutionImageReader} for MAXIMUM
@@ -793,8 +799,6 @@ public abstract class CameraDevice implements AutoCloseable {
* stream combinations ({@code MULTI_RES} in the Max size column refers to a {@link
* MultiResolutionImageReader} created based on the variable max resolutions supported):
*
- *
LEGACY-level additional guaranteed combinations with MultiResolutionoutputs
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
@@ -803,8 +807,12 @@ public abstract class CameraDevice implements AutoCloseable {
* {@code PRIV} | {@code PREVIEW} | {@code JPEG} | {@code MULTI_RES} | | Standard still imaging. |
* {@code PRIV} | {@code PREVIEW} | {@code YUV } | {@code PREVIEW} | {@code JPEG} | {@code MULTI_RES} | Still capture plus in-app processing. |
*
+ *
+ *
+ * LIMITED-level additional guaranteed configurations with multi-resolution outputs
+ *
+ *
*
- * LIMITED-level additional guaranteed configurations with MultiResolutionoutputs |
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
* {@code YUV } | {@code PREVIEW} | {@code YUV } | {@code PREVIEW} | {@code JPEG} | {@code MULTI_RES} | Two-input in-app processing with still capture. |
@@ -812,11 +820,11 @@ public abstract class CameraDevice implements AutoCloseable {
* The same logic applies to other hardware levels and capabilities.
*
*
- * Devices with the ULTRA_HIGH_RESOLUTION_SENSOR capability have some additional guarantees
- * which clients can take advantage of :
- *
* Additional guaranteed combinations for ULTRA_HIGH_RESOLUTION sensors
*
+ * Devices with the ULTRA_HIGH_RESOLUTION_SENSOR capability have some additional guarantees
+ * which clients can take advantage of:
+ *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | SC Map | Max size | Type | SC Map | Max size | Type | SC Map | Max size |
@@ -824,6 +832,7 @@ public abstract class CameraDevice implements AutoCloseable {
* {@code YUV / JPEG / RAW} | {@code MAX_RES} | {@code MAX} | {@code PRIV} | {@code DEFAULT} | {@code PREVIEW} | {@code PRIV / YUV} | {@code DEFAULT} | {@code RECORD} | Ultra high res still capture with preview + app based RECORD size analysis |
* {@code YUV / JPEG / RAW} | {@code MAX_RES} | {@code MAX} | {@code PRIV} | {@code DEFAULT} | {@code PREVIEW} | {@code JPEG / YUV / RAW} | {@code DEFAULT} | {@code MAX} | Ultra high res still image capture with preview + default sensor pixel mode analysis stream |
*
+ *
*
* Here, SC Map, refers to the {@link StreamConfigurationMap}, the target stream sizes must
* be chosen from. {@code DEFAULT} refers to the default sensor pixel mode {@link
@@ -833,17 +842,17 @@ public abstract class CameraDevice implements AutoCloseable {
* Note: The same capture request must not mix targets from
* {@link StreamConfigurationMap}s corresponding to different sensor pixel modes.
*
- * 10-bit output capable
- * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT}
- * devices support at least the following stream combinations:
- *
* 10-bit output additional guaranteed configurations
*
+ * 10-bit output capable
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT}
+ * devices support at least the following stream combinations:
+ *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Type | Max size | Type | Max size |
- * {@code PRIV} | {@code MAXIMUM} | } | Simple preview, GPU video processing, or no-preview video recording. |
- * {@code YUV} | {@code MAXIMUM} | } | In-application video/image processing. |
+ * {@code PRIV} | {@code MAXIMUM} | | Simple preview, GPU video processing, or no-preview video recording. |
+ * {@code YUV} | {@code MAXIMUM} | | In-application video/image processing. |
* {@code PRIV} | {@code PREVIEW} | {@code JPEG} | {@code MAXIMUM } | | Standard still imaging. |
* {@code PRIV} | {@code PREVIEW} | {@code YUV } | {@code MAXIMUM } | | Maximum-resolution in-app processing with preview. |
* {@code YUV} | {@code PREVIEW} | {@code YUV} | {@code MAXIMUM } | | Maximum-resolution two-input in-app processing. |
@@ -851,6 +860,8 @@ public abstract class CameraDevice implements AutoCloseable {
* {@code PRIV} | {@code PREVIEW} | {@code PRIV} | {@code RECORD } | {@code YUV} | {@code RECORD } | High-resolution recording with in-app snapshot. |
* {@code PRIV} | {@code PREVIEW} | {@code PRIV } | {@code RECORD } | {@code JPEG} | {@code RECORD } | High-resolution recording with video snapshot. |
*
+ *
+ *
* Here PRIV can be either 8 or 10-bit {@link android.graphics.ImageFormat#PRIVATE} pixel
* format. YUV can be either {@link android.graphics.ImageFormat#YUV_420_888} or
* {@link android.graphics.ImageFormat#YCBCR_P010}.
@@ -886,13 +897,13 @@ public abstract class CameraDevice implements AutoCloseable {
* {@link CameraDevice#isSessionConfigurationSupported} to ensure that this particular
* configuration is supported.
*
+ * STREAM_USE_CASE capability additional guaranteed configurations
+ *
* Devices with the STREAM_USE_CASE capability ({@link
* CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} includes {@link
* CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE}) support below additional
* stream combinations:
*
- *
STREAM_USE_CASE capability additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Usecase | Type | Max size | Usecase | Type | Max size | Usecase |
@@ -912,12 +923,12 @@ public abstract class CameraDevice implements AutoCloseable {
*
*
*
+ * STREAM_USE_CASE_CROPPED_RAW capability additional guaranteed configurations
+ *
* Devices that include the {@link CameraMetadata#SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW}
* stream use-case in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES},
* support the additional stream combinations below:
*
- *
STREAM_USE_CASE_CROPPED_RAW capability additional guaranteed configurations
- *
*
* Target 1 | Target 2 | Target 3 | Sample use case(s) |
* Type | Max size | Usecase | Type | Max size | Usecase | Type | Max size | Usecase |
@@ -925,15 +936,17 @@ public abstract class CameraDevice implements AutoCloseable {
* {@code PRIV / YUV} | {@code PREVIEW} | {@code PREVIEW} | {@code RAW} | {@code MAXIMUM} | {@code CROPPED_RAW} | | Preview with cropped RAW still capture |
* {@code PRIV / YUV} | {@code PREVIEW} | {@code PREVIEW} | {@code YUV / JPEG} | {@code MAXIMUM} | {@code STILL_CAPTURE} | {@code RAW} | {@code MAXIMUM} | {@code CROPPED_RAW} | Preview with YUV / JPEG and cropped RAW still capture |
* {@code PRIV / YUV} | {@code PREVIEW} | {@code PREVIEW} | {@code PRIV / YUV} | {@code PREVIEW} | {@code VIDEO_RECORD / PREVIEW} | {@code RAW} | {@code MAXIMUM} | {@code CROPPED_RAW} | Video recording with preview and cropped RAW still capture |
+ *
+ *
*
+ * Preview stabilization guaranteed stream configurations
*
- * For devices where {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES}
- * includes {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION},
+ *
For devices where
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES} includes
+ * {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION},
* the following stream combinations are guaranteed,
* for CaptureRequests where {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE} is set to
- * {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION}
- *
- *
Preview stabilization guaranteed stream configurations
+ * {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION}
*
*
* Target 1 | Target 2 | Sample use case(s) |
@@ -942,6 +955,8 @@ public abstract class CameraDevice implements AutoCloseable {
* {@code PRIV / YUV} | {@code s1440p} | {@code JPEG / YUV} | {@code MAXIMUM } | Standard still imaging with stabilized preview. |
* {@code PRIV / YUV} | {@code PREVIEW} | {@code PRIV / YUV} | {@code s1440p } | High-resolution recording with stabilized preview and recording stream. |
*
+ *
+ *
*
* For the maximum size column, PREVIEW refers to the best size match to the device's screen
* resolution, or to 1080p (1920x1080), whichever is smaller. RECORD refers to the camera
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index d48e20e128b9ff8cd2b23be0e54690b93ea2af4d..ea951a55bfca20038e1c9ad8cfa5abc02d31391f 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -34,6 +34,7 @@ import android.hardware.camera2.impl.CameraExtensionUtils;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Binder;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.RemoteException;
@@ -48,7 +49,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -236,9 +236,10 @@ public final class CameraExtensionCharacteristics {
private static final CameraExtensionManagerGlobal GLOBAL_CAMERA_MANAGER =
new CameraExtensionManagerGlobal();
private final Object mLock = new Object();
- private final int PROXY_SERVICE_DELAY_MS = 1000;
+ private final int PROXY_SERVICE_DELAY_MS = 2000;
private InitializerFuture mInitFuture = null;
private ServiceConnection mConnection = null;
+ private int mConnectionCount = 0;
private ICameraExtensionsProxyService mProxy = null;
private boolean mSupportsAdvancedExtensions = false;
@@ -249,6 +250,15 @@ public final class CameraExtensionCharacteristics {
return GLOBAL_CAMERA_MANAGER;
}
+ private void releaseProxyConnectionLocked(Context ctx) {
+ if (mConnection != null ) {
+ ctx.unbindService(mConnection);
+ mConnection = null;
+ mProxy = null;
+ mConnectionCount = 0;
+ }
+ }
+
private void connectToProxyLocked(Context ctx) {
if (mConnection == null) {
Intent intent = new Intent();
@@ -270,7 +280,6 @@ public final class CameraExtensionCharacteristics {
mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName component) {
- mInitFuture.setStatus(false);
mConnection = null;
mProxy = null;
}
@@ -346,31 +355,46 @@ public final class CameraExtensionCharacteristics {
}
}
- public long registerClient(Context ctx) {
+ public boolean registerClient(Context ctx, IBinder token) {
synchronized (mLock) {
+ boolean ret = false;
connectToProxyLocked(ctx);
- if (mProxy != null) {
- try {
- return mProxy.registerClient();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to initialize extension! Extension service does "
- + " not respond!");
- return -1;
- }
- } else {
- return -1;
+ if (mProxy == null) {
+ return false;
}
+ mConnectionCount++;
+
+ try {
+ ret = mProxy.registerClient(token);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to initialize extension! Extension service does "
+ + " not respond!");
+ }
+ if (!ret) {
+ mConnectionCount--;
+ }
+
+ if (mConnectionCount <= 0) {
+ releaseProxyConnectionLocked(ctx);
+ }
+
+ return ret;
}
}
- public void unregisterClient(long clientId) {
+ public void unregisterClient(Context ctx, IBinder token) {
synchronized (mLock) {
if (mProxy != null) {
try {
- mProxy.unregisterClient(clientId);
+ mProxy.unregisterClient(token);
} catch (RemoteException e) {
Log.e(TAG, "Failed to de-initialize extension! Extension service does"
+ " not respond!");
+ } finally {
+ mConnectionCount--;
+ if (mConnectionCount <= 0) {
+ releaseProxyConnectionLocked(ctx);
+ }
}
}
}
@@ -438,15 +462,15 @@ public final class CameraExtensionCharacteristics {
/**
* @hide
*/
- public static long registerClient(Context ctx) {
- return CameraExtensionManagerGlobal.get().registerClient(ctx);
+ public static boolean registerClient(Context ctx, IBinder token) {
+ return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
}
/**
* @hide
*/
- public static void unregisterClient(long clientId) {
- CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+ public static void unregisterClient(Context ctx, IBinder token) {
+ CameraExtensionManagerGlobal.get().unregisterClient(ctx, token);
}
/**
@@ -564,8 +588,9 @@ public final class CameraExtensionCharacteristics {
*/
public @NonNull List getSupportedExtensions() {
ArrayList ret = new ArrayList<>();
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
return Collections.unmodifiableList(ret);
}
@@ -576,7 +601,7 @@ public final class CameraExtensionCharacteristics {
}
}
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableList(ret);
@@ -599,8 +624,9 @@ public final class CameraExtensionCharacteristics {
* supported device-specific extension
*/
public boolean isPostviewAvailable(@Extension int extension) {
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -623,7 +649,7 @@ public final class CameraExtensionCharacteristics {
Log.e(TAG, "Failed to query the extension for postview availability! Extension "
+ "service does not respond!");
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
return false;
@@ -656,9 +682,9 @@ public final class CameraExtensionCharacteristics {
@NonNull
public List getPostviewSupportedSizes(@Extension int extension,
@NonNull Size captureSize, int format) {
-
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -719,7 +745,7 @@ public final class CameraExtensionCharacteristics {
+ "service does not respond!");
return Collections.emptyList();
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
}
@@ -756,8 +782,9 @@ public final class CameraExtensionCharacteristics {
// TODO: Revisit this code once the Extension preview processor output format
// ambiguity is resolved in b/169799538.
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -787,7 +814,7 @@ public final class CameraExtensionCharacteristics {
+ " not respond!");
return new ArrayList<>();
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
}
@@ -814,8 +841,9 @@ public final class CameraExtensionCharacteristics {
public @NonNull
List getExtensionSupportedSizes(@Extension int extension, int format) {
try {
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -867,7 +895,7 @@ public final class CameraExtensionCharacteristics {
}
}
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -888,7 +916,6 @@ public final class CameraExtensionCharacteristics {
* @param format device-specific extension output format
* @return the range of estimated minimal and maximal capture latency in milliseconds
* or null if no capture latency info can be provided
- *
* @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
* {@link ImageFormat#YUV_420_888}; or unsupported extension.
*/
@@ -903,8 +930,9 @@ public final class CameraExtensionCharacteristics {
throw new IllegalArgumentException("Unsupported format: " + format);
}
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -952,7 +980,7 @@ public final class CameraExtensionCharacteristics {
Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
+ " not respond!");
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
return null;
@@ -968,8 +996,9 @@ public final class CameraExtensionCharacteristics {
* @throws IllegalArgumentException in case of an unsupported extension.
*/
public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -992,7 +1021,7 @@ public final class CameraExtensionCharacteristics {
Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
+ " not respond!");
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
return false;
@@ -1013,8 +1042,9 @@ public final class CameraExtensionCharacteristics {
*/
@NonNull
public Set getAvailableCaptureRequestKeys(@Extension int extension) {
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -1033,10 +1063,11 @@ public final class CameraExtensionCharacteristics {
} else {
Pair extenders =
initializeExtension(extension);
- extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+ extenders.second.onInit(token, mCameraId,
+ mCharacteristicsMapNative.get(mCameraId));
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
- extenders.second.onDeInit();
+ extenders.second.onDeInit(token);
}
if (captureRequestMeta != null) {
@@ -1067,7 +1098,7 @@ public final class CameraExtensionCharacteristics {
} catch (RemoteException e) {
throw new IllegalStateException("Failed to query the available capture request keys!");
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableSet(ret);
@@ -1092,8 +1123,9 @@ public final class CameraExtensionCharacteristics {
*/
@NonNull
public Set getAvailableCaptureResultKeys(@Extension int extension) {
- long clientId = registerClient(mContext);
- if (clientId < 0) {
+ final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
+ boolean success = registerClient(mContext, token);
+ if (!success) {
throw new IllegalArgumentException("Unsupported extensions");
}
@@ -1111,10 +1143,11 @@ public final class CameraExtensionCharacteristics {
} else {
Pair extenders =
initializeExtension(extension);
- extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+ extenders.second.onInit(token, mCameraId,
+ mCharacteristicsMapNative.get(mCameraId));
extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
- extenders.second.onDeInit();
+ extenders.second.onDeInit(token);
}
if (captureResultMeta != null) {
@@ -1126,7 +1159,7 @@ public final class CameraExtensionCharacteristics {
}
CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
Object crKey = CaptureResult.Key.class;
- Class> crKeyTyped = (Class>)crKey;
+ Class> crKeyTyped = (Class>) crKey;
ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
resultKeys, /*includeSynthetic*/ true));
@@ -1145,7 +1178,7 @@ public final class CameraExtensionCharacteristics {
} catch (RemoteException e) {
throw new IllegalStateException("Failed to query the available capture result keys!");
} finally {
- unregisterClient(clientId);
+ unregisterClient(mContext, token);
}
return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 51501b558fba97dce6f7fd64b5e4d9a017dbb99b..c80124c4c2ec3ef4395c15a8c90993e1c6914914 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,6 +31,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.hardware.CameraExtensionSessionStats;
+import android.hardware.CameraIdRemapping;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -67,6 +68,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -129,14 +131,17 @@ public final class CameraManager {
/**
* Enable physical camera availability callbacks when the logical camera is unavailable
*
- * Previously once a logical camera becomes unavailable, no {@link
- * #onPhysicalCameraAvailable} or {@link #onPhysicalCameraUnavailable} will be called until
- * the logical camera becomes available again. The results in the app opening the logical
- * camera not able to receive physical camera availability change.
- *
- * With this change, the {@link #onPhysicalCameraAvailable} and {@link
- * #onPhysicalCameraUnavailable} can still be called while the logical camera is unavailable.
- *
+ * Previously once a logical camera becomes unavailable, no
+ * {@link AvailabilityCallback#onPhysicalCameraAvailable} or
+ * {@link AvailabilityCallback#onPhysicalCameraUnavailable} will
+ * be called until the logical camera becomes available again. The
+ * results in the app opening the logical camera not able to
+ * receive physical camera availability change.
+ *
+ * With this change, the {@link
+ * AvailabilityCallback#onPhysicalCameraAvailable} and {@link
+ * AvailabilityCallback#onPhysicalCameraUnavailable} can still be
+ * called while the logical camera is unavailable.
*/
@ChangeId
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -178,22 +183,20 @@ public final class CameraManager {
boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
mFoldedDeviceState = folded;
- ArrayList> invalidListeners = new ArrayList<>();
- for (WeakReference listener : mDeviceStateListeners) {
- DeviceStateListener callback = listener.get();
+ Iterator> it = mDeviceStateListeners.iterator();
+ while(it.hasNext()) {
+ DeviceStateListener callback = it.next().get();
if (callback != null) {
callback.onDeviceStateChanged(folded);
} else {
- invalidListeners.add(listener);
+ it.remove();
}
}
- if (!invalidListeners.isEmpty()) {
- mDeviceStateListeners.removeAll(invalidListeners);
- }
}
public synchronized void addDeviceStateListener(DeviceStateListener listener) {
listener.onDeviceStateChanged(mFoldedDeviceState);
+ mDeviceStateListeners.removeIf(l -> l.get() == null);
mDeviceStateListeners.add(new WeakReference<>(listener));
}
@@ -1727,6 +1730,17 @@ public final class CameraManager {
}
}
+ /**
+ * Remaps Camera Ids in the CameraService.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+ public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+ throws CameraAccessException, SecurityException, IllegalArgumentException {
+ CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
+ }
+
/**
* Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
* currently active session. Validation is done downstream.
@@ -1800,6 +1814,13 @@ public final class CameraManager {
private final Object mLock = new Object();
+ /**
+ * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
+ * in the CameraService every time we connect to it, including when the CameraService
+ * Binder dies and we reconnect to it.
+ */
+ @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
+
// Access only through getCameraService to deal with binder death
private ICameraService mCameraService;
private boolean mHasOpenCloseListenerPermission = false;
@@ -1836,6 +1857,7 @@ public final class CameraManager {
ctx.getSystemService(DeviceStateManager.class).registerCallback(
new HandlerExecutor(mDeviceStateHandler), mFoldStateListener);
} catch (IllegalStateException e) {
+ mFoldStateListener = null;
Log.v(TAG, "Failed to register device state listener!");
Log.v(TAG, "Device state dependent characteristics updates will not be" +
"functional!");
@@ -1941,6 +1963,41 @@ public final class CameraManager {
} catch (RemoteException e) {
// Camera service died in all probability
}
+
+ if (mActiveCameraIdRemapping != null) {
+ try {
+ cameraService.remapCameraIds(mActiveCameraIdRemapping);
+ } catch (ServiceSpecificException e) {
+ // Unexpected failure, ignore and continue.
+ Log.e(TAG, "Unable to remap camera Ids in the camera service");
+ } catch (RemoteException e) {
+ // Camera service died in all probability
+ }
+ }
+ }
+
+ /** Updates the cameraIdRemapping state in the CameraService. */
+ public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+ throws CameraAccessException, SecurityException {
+ synchronized (mLock) {
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+
+ try {
+ cameraService.remapCameraIds(cameraIdRemapping);
+ mActiveCameraIdRemapping = cameraIdRemapping;
+ } catch (ServiceSpecificException e) {
+ throwAsPublicException(e);
+ } catch (RemoteException e) {
+ throw new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable.");
+ }
+ }
}
private String[] extractCameraIdListLocked() {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3a660812fd24ab74852b40bbe74ac2128a0c8fcd..25c84e5e9c586e1c7c59a5737dc4981fe266fd03 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1468,6 +1468,19 @@ public final class CaptureRequest extends CameraMetadata>
* Only constrains auto-exposure (AE) algorithm, not
* manual control of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and
* {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}.
+ * Note that the actual achievable max framerate also depends on the minimum frame
+ * duration of the output streams. The max frame rate will be
+ * min(aeTargetFpsRange.maxFps, 1 / max(individual stream min durations)
. For example,
+ * if the application sets this key to {60, 60}
, but the maximum minFrameDuration among
+ * all configured streams is 33ms, the maximum framerate won't be 60fps, but will be
+ * 30fps.
+ * To start a CaptureSession with a target FPS range different from the
+ * capture request template's default value, the application
+ * is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the target fps range before creating the capture session. The aeTargetFpsRange is
+ * typically a session parameter. Specifying it at session creation time helps avoid
+ * session reconfiguration delays in cases like 60fps or high speed recording.
* Units: Frames per second (FPS)
* Range of valid values:
* Any of the entries in {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges}
@@ -2140,6 +2153,12 @@ public final class CaptureRequest extends CameraMetadata>
* {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.
+ * The application is strongly recommended to call
+ * {@link SessionConfiguration#setSessionParameters }
+ * with the desired video stabilization mode before creating the capture session.
+ * Video stabilization mode is a session parameter on many devices. Specifying
+ * it at session creation time helps avoid reconfiguration delay caused by difference
+ * between the default value and the first CaptureRequest.
* If a camera device supports both this mode and OIS
* ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
* produce undesirable interaction, so it is recommended not to enable
@@ -3535,7 +3554,7 @@ public final class CaptureRequest extends CameraMetadata>
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
* They can be queried through
* {@link android.hardware.camera2.CameraCharacteristics#get } with
- * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
* Unless reported by both
* {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
* {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}
and
@@ -3550,13 +3569,12 @@ public final class CaptureRequest extends CameraMetadata>
*
*
*
* The bayer pattern of {@code RAW} streams when
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
- * is selected will be the one listed in {@link android.sensor.info.binningFactor }.
+ * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.
*
*
* The following keys will always be present:
@@ -3576,9 +3594,11 @@ public final class CaptureRequest extends CameraMetadata>
*
* Optional - The value for this key may be {@code null} on some devices.
*
+ * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
* @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see #SENSOR_PIXEL_MODE_DEFAULT
@@ -4193,9 +4213,8 @@ public final class CaptureRequest extends CameraMetadata>
* This control allows Camera extension clients to configure the strength of the applied
* extension effect. Strength equal to 0 means that the extension must not apply any
* post-processing and return a regular captured frame. Strength equal to 100 is the
- * default level of post-processing applied when the control is not supported or not set
- * by the client. Values between 0 and 100 will have different effect depending on the
- * extension type as described below:
+ * maximum level of post-processing. Values between 0 and 100 will have different effect
+ * depending on the extension type as described below:
*
*
* The bayer pattern of {@code RAW} streams when
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
- * is selected will be the one listed in {@link android.sensor.info.binningFactor }.
+ * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.
*
*
* The following keys will always be present:
@@ -4501,9 +4519,11 @@ public class CaptureResult extends CameraMetadata> {
*
* Optional - The value for this key may be {@code null} on some devices.
*
+ * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
* @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see #SENSOR_PIXEL_MODE_DEFAULT
@@ -5693,9 +5713,8 @@ public class CaptureResult extends CameraMetadata> {
* This control allows Camera extension clients to configure the strength of the applied
* extension effect. Strength equal to 0 means that the extension must not apply any
* post-processing and return a regular captured frame. Strength equal to 100 is the
- * default level of post-processing applied when the control is not supported or not set
- * by the client. Values between 0 and 100 will have different effect depending on the
- * extension type as described below:
+ * maximum level of post-processing. Values between 0 and 100 will have different effect
+ * depending on the extension type as described below:
*
* These values are relative to the state from the last event, not accumulated, so developers
* should make sure to process this axis value for all batched historical events.
+ *
+ * This axis is only set on the first pointer in a motion event.
*/
public static final int AXIS_GESTURE_X_OFFSET = 48;
@@ -1304,6 +1306,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*
* These values are relative to the state from the last event, not accumulated, so developers
* should make sure to process this axis value for all batched historical events.
+ *
+ * This axis is only set on the first pointer in a motion event.
*/
public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50;
@@ -1324,14 +1328,29 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*
* These values are relative to the state from the last event, not accumulated, so developers
* should make sure to process this axis value for all batched historical events.
+ *
+ * This axis is only set on the first pointer in a motion event.
*/
public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52;
+ /**
+ * Axis constant: the number of fingers being used in a multi-finger swipe gesture.
+ *
+ *
+ * - For a touch pad, reports the number of fingers being used in a multi-finger swipe gesture
+ * (with CLASSIFICATION_MULTI_FINGER_SWIPE).
+ *
+ *
+ * Since CLASSIFICATION_MULTI_FINGER_SWIPE is a hidden API, so is this axis. It is only set on
+ * the first pointer in a motion event.
+ * @hide
+ */
+ public static final int AXIS_GESTURE_SWIPE_FINGER_COUNT = 53;
+
// NOTE: If you add a new axis here you must also add it to:
// frameworks/native/include/android/input.h
// frameworks/native/libs/input/InputEventLabels.cpp
- // platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
- // (testAxisFromToString)
+ // cts/tests/tests/view/src/android/view/cts/MotionEventTest.java (testAxisFromToString)
// Symbolic names of all axes.
private static final SparseArray AXIS_SYMBOLIC_NAMES = new SparseArray();
@@ -1387,6 +1406,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
names.append(AXIS_GESTURE_SCROLL_X_DISTANCE, "AXIS_GESTURE_SCROLL_X_DISTANCE");
names.append(AXIS_GESTURE_SCROLL_Y_DISTANCE, "AXIS_GESTURE_SCROLL_Y_DISTANCE");
names.append(AXIS_GESTURE_PINCH_SCALE_FACTOR, "AXIS_GESTURE_PINCH_SCALE_FACTOR");
+ names.append(AXIS_GESTURE_SWIPE_FINGER_COUNT, "AXIS_GESTURE_SWIPE_FINGER_COUNT");
}
/**
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index bd20f5bf82fdf03b1f068fbb5a2cf8dc4cc03ff6..a2919f5bfd7458ecc2f2f148413247b8b941dc93 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.os.Trace;
import android.util.AttributeSet;
import android.widget.RemoteViews;
@@ -106,6 +107,7 @@ public class NotificationTopLineView extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationTopLineView#onMeasure");
final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
@@ -161,6 +163,7 @@ public class NotificationTopLineView extends ViewGroup {
.finish();
}
setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
+ Trace.endSection();
}
@Override
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index e8f62fc0963f43e33bf4a50e6f271f7f3b70487a..a4cbc52416b322e13ca2ac0ba1d0d001e196095c 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@ public class PendingInsetsController implements WindowInsetsController {
private ArrayList mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private int mImeCaptionBarInsetsHeight = 0;
private WindowInsetsAnimationControlListener mLoggingListener;
private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@@ -90,6 +91,11 @@ public class PendingInsetsController implements WindowInsetsController {
mCaptionInsetsHeight = height;
}
+ @Override
+ public void setImeCaptionBarInsetsHeight(int height) {
+ mImeCaptionBarInsetsHeight = height;
+ }
+
@Override
public void setSystemBarsBehavior(int behavior) {
if (mReplayedInsetsController != null) {
@@ -168,6 +174,9 @@ public class PendingInsetsController implements WindowInsetsController {
if (mCaptionInsetsHeight != 0) {
controller.setCaptionInsetsHeight(mCaptionInsetsHeight);
}
+ if (mImeCaptionBarInsetsHeight != 0) {
+ controller.setImeCaptionBarInsetsHeight(mImeCaptionBarInsetsHeight);
+ }
if (mAnimationsDisabled) {
controller.setAnimationsDisabled(true);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c11f4975149d61bf1624b2784b1650eb0ea07c0b..e673676395144784d9b5dbad345127abd7830cf1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.gui.DropInputMode;
+import android.gui.StalledTransactionInfo;
import android.hardware.DataSpace;
import android.hardware.HardwareBuffer;
import android.hardware.OverlayProperties;
@@ -292,6 +293,7 @@ public final class SurfaceControl implements Parcelable {
long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
private static native void nativeClearTrustedPresentationCallback(long transactionObj,
long nativeObject);
+ private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
/**
* Transforms that can be applied to buffers as they are displayed to a window.
@@ -4363,4 +4365,11 @@ public final class SurfaceControl implements Parcelable {
callback.accept(fence);
}
+ /**
+ * @hide
+ */
+ public static StalledTransactionInfo getStalledTransactionInfo(int pid) {
+ return nativeGetStalledTransactionInfo(pid);
+ }
+
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index c8cf7d9a5194a1cd2d01f93d07fb5d5ecf6e83ae..effc127dabd225991e3e38d1f0572cf62163a2e7 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -54,7 +54,7 @@ public class SurfaceControlViewHost {
private final static String TAG = "SurfaceControlViewHost";
private final ViewRootImpl mViewRoot;
private final CloseGuard mCloseGuard = CloseGuard.get();
- private WindowlessWindowManager mWm;
+ private final WindowlessWindowManager mWm;
private SurfaceControl mSurfaceControl;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -67,9 +67,7 @@ public class SurfaceControlViewHost {
return;
}
mViewRoot.mHandler.post(() -> {
- if (mWm != null) {
- mWm.setConfiguration(configuration);
- }
+ mWm.setConfiguration(configuration);
if (mViewRoot != null) {
mViewRoot.forceWmRelayout();
}
@@ -116,6 +114,11 @@ public class SurfaceControlViewHost {
}
return null;
}
+
+ @Override
+ public void attachParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+ mViewRoot.mHandler.post(() -> mWm.setParentInterface(parentInterface));
+ }
}
private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
@@ -148,10 +151,11 @@ public class SurfaceControlViewHost {
private SurfaceControl mSurfaceControl;
private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
private final IBinder mInputToken;
+ @NonNull
private final ISurfaceControlViewHost mRemoteInterface;
SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
- IBinder inputToken, ISurfaceControlViewHost ri) {
+ IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
mSurfaceControl = sc;
mAccessibilityEmbeddedConnection = connection;
mInputToken = inputToken;
@@ -213,6 +217,7 @@ public class SurfaceControlViewHost {
/**
* @hide
*/
+ @NonNull
public ISurfaceControlViewHost getRemoteInterface() {
return mRemoteInterface;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0e4cf89e7772bcd02c6c34b332512db3cd50b5ad..5b69d7fdeb359f2422bd1a1feae28965f6a600e9 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
@@ -40,6 +41,7 @@ import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -54,6 +56,8 @@ import com.android.internal.view.SurfaceCallbackHelper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@@ -302,6 +306,26 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private SurfaceControl mBlastSurfaceControl;
private BLASTBufferQueue mBlastBufferQueue;
+ private final ConcurrentLinkedQueue mEmbeddedWindowParams =
+ new ConcurrentLinkedQueue<>();
+
+ private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
+ new ISurfaceControlViewHostParent.Stub() {
+ @Override
+ public void updateParams(WindowManager.LayoutParams[] childAttrs) {
+ mEmbeddedWindowParams.clear();
+ mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+ if (isAttachedToWindow()) {
+ runOnUiThread(() -> {
+ if (mParent != null) {
+ mParent.recomputeViewAttributes(SurfaceView.this);
+ }
+ });
+ }
+ }
+ };
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -801,9 +825,18 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mBlastSurfaceControl = null;
}
- if (releaseSurfacePackage && mSurfacePackage != null) {
- mSurfacePackage.release();
- mSurfacePackage = null;
+ if (mSurfacePackage != null) {
+ try {
+ mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+ mEmbeddedWindowParams.clear();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+ + "already dead");
+ }
+ if (releaseSurfacePackage) {
+ mSurfacePackage.release();
+ mSurfacePackage = null;
+ }
}
applyTransactionOnVriDraw(transaction);
@@ -1307,7 +1340,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
.setLocalOwnerView(this)
- .setParent(viewRoot.getBoundsLayer())
+ .setParent(viewRoot.updateAndGetBoundsLayer(surfaceUpdateTransaction))
.setCallsite("SurfaceView.updateSurface")
.setContainerLayer()
.build();
@@ -1854,6 +1887,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
applyTransactionOnVriDraw(transaction);
}
mSurfacePackage = p;
+ try {
+ mSurfacePackage.getRemoteInterface().attachParentInterface(
+ mSurfaceControlViewHostParent);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
+ }
if (isFocused()) {
requestEmbeddedFocus(true);
@@ -2014,4 +2053,19 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
}
}
+
+ @Override
+ void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+ super.performCollectViewAttributes(attachInfo, visibility);
+ if (mEmbeddedWindowParams.isEmpty()) {
+ return;
+ }
+
+ for (WindowManager.LayoutParams embeddedWindowAttr : mEmbeddedWindowParams) {
+ if ((embeddedWindowAttr.flags & FLAG_KEEP_SCREEN_ON) == FLAG_KEEP_SCREEN_ON) {
+ attachInfo.mKeepScreenOn = true;
+ break;
+ }
+ }
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 73d18f91f46ce75cc72d708a644c255f268ef859..1d882f2d4cf0d68a8f0a1d8ac2bce13ef4026704 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5483,7 +5483,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
(TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
(PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
(IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
- mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
@@ -6208,7 +6207,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setPreferKeepClear(a.getBoolean(attr, false));
break;
case R.styleable.View_autoHandwritingEnabled:
- setAutoHandwritingEnabled(a.getBoolean(attr, true));
+ setAutoHandwritingEnabled(a.getBoolean(attr, false));
break;
case R.styleable.View_handwritingBoundsOffsetLeft:
mHandwritingBoundsOffsetLeft = a.getDimension(attr, 0);
@@ -9232,8 +9231,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
- ignoredParentLeft += parentGroup.mLeft;
- ignoredParentTop += parentGroup.mTop;
+ ignoredParentLeft += parentGroup.mLeft - parentGroup.mScrollX;
+ ignoredParentTop += parentGroup.mTop - parentGroup.mScrollY;
viewParent = parentGroup.getParent();
if (viewParent instanceof View) {
@@ -12077,7 +12076,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (getSystemGestureExclusionRects().isEmpty()
&& collectPreferKeepClearRects().isEmpty()
&& collectUnrestrictedPreferKeepClearRects().isEmpty()
- && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
+ && (info.mHandwritingArea == null || !shouldInitiateHandwriting())) {
if (info.mPositionUpdateListener != null) {
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
info.mPositionUpdateListener = null;
@@ -12444,13 +12443,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
void updateHandwritingArea() {
// If autoHandwritingArea is not enabled, do nothing.
- if (!isAutoHandwritingEnabled()) return;
+ if (!shouldInitiateHandwriting()) return;
final AttachInfo ai = mAttachInfo;
if (ai != null) {
ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this);
}
}
+ /**
+ * Returns true if a stylus {@link MotionEvent} within this view's bounds should initiate
+ * handwriting mode, either for this view ({@link #isAutoHandwritingEnabled()} is {@code true})
+ * or for a handwriting delegate view ({@link #getHandwritingDelegatorCallback()} is not {@code
+ * null}).
+ */
+ boolean shouldInitiateHandwriting() {
+ return isAutoHandwritingEnabled() || getHandwritingDelegatorCallback() != null;
+ }
+
/**
* Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this
* view's bounds. The callback will be called from the UI thread.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index d80819f29cc61325ae8a3d0a8d3abb1c28490da9..0e7c6d12b0d9a40d1472563c98b71777905c2985 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -181,7 +181,7 @@ public class ViewConfiguration {
private static final int TOUCH_SLOP = 8;
/** Distance a stylus touch can wander before we think the user is handwriting in dips. */
- private static final int HANDWRITING_SLOP = 4;
+ private static final int HANDWRITING_SLOP = 2;
/**
* Defines the minimum size of the touch target for a scrollbar in dips
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d70296353ff63c72c46c1766f61653351f95d9bb..02da266e2171ba2f6929c5600017bf4384cc333b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2043,7 +2043,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float x = event.getXDispatchLocation(pointerIndex);
final float y = event.getYDispatchLocation(pointerIndex);
if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
- return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+ // Return null here so that it fallbacks to the default PointerIcon for the source
+ // device. For mouse, the default PointerIcon is PointerIcon.TYPE_ARROW.
+ // For stylus, the default PointerIcon is PointerIcon.TYPE_NULL.
+ return null;
}
// Check what the child under the pointer says about the pointer.
final int childrenCount = mChildrenCount;
@@ -7358,6 +7361,126 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ if (!child.hasIdentityMatrix()) {
+ matrix.preConcat(child.getInverseMatrix());
+ }
+
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
+ matrix.preTranslate(-dx, -dy);
+
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ // Map the bounds of this view into the region's coordinates and clip the region.
+ final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
+ rect.set(0, 0, width, height);
+ matrix.mapRect(rect);
+
+ boolean notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom), Region.Op.INTERSECT);
+
+ if (isHover) {
+ HoverTarget target = mFirstHoverTarget;
+ boolean childIsHit = false;
+ while (target != null) {
+ final HoverTarget next = target.next;
+ if (target.child == child) {
+ childIsHit = true;
+ break;
+ }
+ target = next;
+ }
+ if (!childIsHit && mFirstHoverTarget != null) {
+ target = mFirstHoverTarget;
+ final ArrayList preorderedList = buildTouchDispatchChildList();
+ while (notEmpty && target != null) {
+ final HoverTarget next = target.next;
+ final View hoveredView = target.child;
+
+ if (!isOnTop(child, hoveredView, preorderedList)) {
+ rect.set(hoveredView.mLeft, hoveredView.mTop, hoveredView.mRight,
+ hoveredView.mBottom);
+ matrix.mapRect(rect);
+ notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom),
+ Region.Op.DIFFERENCE);
+ }
+ target = next;
+ }
+ if (preorderedList != null) {
+ preorderedList.clear();
+ }
+ }
+ } else {
+ TouchTarget target = mFirstTouchTarget;
+ boolean childIsHit = false;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (target.child == child) {
+ childIsHit = true;
+ break;
+ }
+ target = next;
+ }
+ if (!childIsHit && mFirstTouchTarget != null) {
+ target = mFirstTouchTarget;
+ final ArrayList preorderedList = buildOrderedChildList();
+ while (notEmpty && target != null) {
+ final TouchTarget next = target.next;
+ final View touchedView = target.child;
+
+ if (!isOnTop(child, touchedView, preorderedList)) {
+ rect.set(touchedView.mLeft, touchedView.mTop, touchedView.mRight,
+ touchedView.mBottom);
+ matrix.mapRect(rect);
+ notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
+ Math.round(rect.right), Math.round(rect.bottom),
+ Region.Op.DIFFERENCE);
+ }
+ target = next;
+ }
+ if (preorderedList != null) {
+ preorderedList.clear();
+ }
+ }
+ }
+
+ if (notEmpty && mParent != null) {
+ notEmpty = mParent.getChildLocalHitRegion(this, region, matrix, isHover);
+ }
+ return notEmpty;
+ }
+
+ /**
+ * Return true if the given {@code view} is drawn on top of the {@code otherView}.
+ * Both the {@code view} and {@code otherView} must be children of this ViewGroup.
+ * Otherwise, the returned value is meaningless.
+ */
+ private boolean isOnTop(View view, View otherView, ArrayList preorderedList) {
+ final int childrenCount = mChildrenCount;
+ final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ if (child == view) {
+ return true;
+ }
+ if (child == otherView) {
+ return false;
+ }
+ }
+ // Can't find the view and otherView in the children list. Return value is meaningless.
+ return false;
+ }
+
private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) {
final int[] locationInWindow = new int[2];
view.getLocationInWindow(locationInWindow);
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 1020d2ef02be38e859cb340025a2245c2677e480..54bc3484d2954cd9b26cc8f6ff9eb407bac958ff 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -685,6 +686,36 @@ public interface ViewParent {
default void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
}
+ /**
+ * Compute the region where the child can receive the {@link MotionEvent}s from the root view.
+ *
+ * Given region where the child will accept {@link MotionEvent}s.
+ * Modify the region to the unblocked region where the child can receive the
+ * {@link MotionEvent}s from the view root.
+ *
+ *
+ * The given region is always clipped by the bounds of the parent views. When there are
+ * on-going {@link MotionEvent}s, this method also makes use of the event dispatching results to
+ * determine whether a sibling view will also block the child's hit region.
+ *
+ *
+ * @param child a child View, whose hit region we want to compute.
+ * @param region the initial hit region where the child view will handle {@link MotionEvent}s,
+ * defined in the child coordinates. Will be overwritten to the result hit region.
+ * @param matrix the matrix that maps the given child view's coordinates to the region
+ * coordinates. It will be modified to a matrix that maps window coordinates to
+ * the result region's coordinates.
+ * @param isHover if true it will return the hover events' hit region, otherwise it will
+ * return the touch events' hit region.
+ * @return true if the returned region is not empty.
+ * @hide
+ */
+ default boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ region.setEmpty();
+ return false;
+ }
+
/**
* Unbuffered dispatch has been requested by a child of this view parent.
* This method is called by the View hierarchy to signal ancestors that a View needs to
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a28be4be24ad802566b1ffa033de420e6e24bcd1..cd2d36c60ade318b869b35b9387ef5c7211d7663 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -50,6 +50,8 @@ import static android.view.ViewRootImplProto.VISIBLE_RECT;
import static android.view.ViewRootImplProto.WIDTH;
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
+import static android.view.ViewRootRefreshRateController.RefreshRatePref.LOWER;
+import static android.view.ViewRootRefreshRateController.RefreshRatePref.RESTORE;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -96,6 +98,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UiContext;
+import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
@@ -127,6 +130,7 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
@@ -239,6 +243,7 @@ import java.util.OptionalInt;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
@@ -310,6 +315,16 @@ public final class ViewRootImpl implements ViewParent,
public static final boolean CLIENT_TRANSIENT =
SystemProperties.getBoolean("persist.wm.debug.client_transient", false);
+ /**
+ * Whether the client (system UI) is handling the immersive confirmation window. If
+ * {@link CLIENT_TRANSIENT} is set to true, the immersive confirmation window will always be the
+ * client instance and this flag will be ignored. Otherwise, the immersive confirmation window
+ * can be switched freely by this flag.
+ * @hide
+ */
+ public static final boolean CLIENT_IMMERSIVE_CONFIRMATION =
+ SystemProperties.getBoolean("persist.wm.debug.client_immersive_confirmation", false);
+
/**
* Whether the client should compute the window frame on its own.
* @hide
@@ -411,6 +426,95 @@ public final class ViewRootImpl implements ViewParent,
ICompatCameraControlCallback callback);
}
+ /**
+ * Used to notify if the user is typing or not.
+ * @hide
+ */
+ public interface TypingHintNotifier {
+ /**
+ * Called when the typing hint is changed. This would be invoked by the
+ * {@link android.view.inputmethod.RemoteInputConnectionImpl}
+ * to hint if the user is typing when it is {@link #isActive() active}.
+ *
+ * The operation in this method should be dispatched to the UI thread to
+ * keep the sequence.
+ *
+ * @param isTyping {@code true} if the user is typing.
+ * @param deactivate {code true} if the input connection deactivate
+ */
+ void onTypingHintChanged(boolean isTyping, boolean deactivate);
+
+ /**
+ * Indicates whether the notifier is currently in active state or not.
+ *
+ * @see #deactivate()
+ */
+ boolean isActive();
+
+ /**
+ * Deactivate the notifier when no longer in use. Mostly invoked when finishing the typing.
+ */
+ void deactivate();
+ }
+
+ /**
+ * The {@link TypingHintNotifier} implementation used to handle
+ * the refresh rate preference when the typing state is changed.
+ */
+ private static class TypingHintNotifierImpl implements TypingHintNotifier {
+
+ private final AtomicReference mActiveNotifier;
+
+ @NonNull
+ private final ViewRootRefreshRateController mController;
+
+ @NonNull
+ private final Handler mHandler;
+
+ @NonNull
+ private final Thread mThread;
+
+ TypingHintNotifierImpl(@NonNull AtomicReference notifier,
+ @NonNull ViewRootRefreshRateController controller, @NonNull Handler handler,
+ @NonNull Thread thread) {
+ mController = controller;
+ mActiveNotifier = notifier;
+ mHandler = handler;
+ mThread = thread;
+ }
+
+ @Override
+ public void onTypingHintChanged(boolean isTyping, boolean deactivate) {
+ final Runnable runnable = () -> {
+ if (!isActive()) {
+ // No-op when the listener was deactivated.
+ return;
+ }
+ mController.updateRefreshRatePreference(isTyping ? LOWER : RESTORE);
+ if (deactivate) {
+ deactivate();
+ }
+ };
+
+ if (Thread.currentThread() == mThread) {
+ // Run directly if it's on the UiThread.
+ runnable.run();
+ } else {
+ mHandler.post(runnable);
+ }
+ }
+
+ @Override
+ public boolean isActive() {
+ return mActiveNotifier.get() == this;
+ }
+
+ @Override
+ public void deactivate() {
+ mActiveNotifier.compareAndSet(this, null);
+ }
+ }
+
/**
* Callback used to notify corresponding activity about camera compat control changes, override
* configuration change and make sure that all resources are set correctly before updating the
@@ -418,6 +522,32 @@ public final class ViewRootImpl implements ViewParent,
*/
private ActivityConfigCallback mActivityConfigCallback;
+ /**
+ * The current active {@link TypingHintNotifier} to handle
+ * typing hint change operations.
+ */
+ private final AtomicReference mActiveTypingHintNotifier =
+ new AtomicReference<>(null);
+
+ /**
+ * Create a {@link TypingHintNotifier} if the client support variable
+ * refresh rate for typing. The {@link TypingHintNotifier} is created
+ * and mapped to a new active input connection each time.
+ *
+ * @hide
+ */
+ @Nullable
+ public TypingHintNotifier createTypingHintNotifierIfSupported() {
+ if (mRefreshRateController == null) {
+ return null;
+ }
+ final TypingHintNotifier newNotifier = new TypingHintNotifierImpl(mActiveTypingHintNotifier,
+ mRefreshRateController, mHandler, mThread);
+ mActiveTypingHintNotifier.set(newNotifier);
+
+ return newNotifier;
+ }
+
/**
* Used when configuration change first updates the config of corresponding activity.
* In that case we receive a call back from {@link ActivityThread} and this flag is used to
@@ -457,6 +587,9 @@ public final class ViewRootImpl implements ViewParent,
@NonNull Display mDisplay;
final String mBasePackageName;
+ // If we would like to keep a particular eye on the corresponding package.
+ final boolean mExtraDisplayListenerLogging;
+
final int[] mTmpLocation = new int[2];
final TypedValue mTmpValue = new TypedValue();
@@ -716,9 +849,9 @@ public final class ViewRootImpl implements ViewParent,
/**
* Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
- * the surface insets. This surface is created only if a client requests it via {@link
- * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do
- * not draw into the surface inset region set by the parent window.
+ * the surface insets. This surface is created only if a client requests it via
+ * {@link #updateAndGetBoundsLayer(Transaction)}. By parenting to this bounds surface, child
+ * surfaces can ensure they do not draw into the surface inset region set by the parent window.
*/
private SurfaceControl mBoundsLayer;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -847,6 +980,8 @@ public final class ViewRootImpl implements ViewParent,
private final InsetsController mInsetsController;
private final ImeFocusController mImeFocusController;
+ private ViewRootRefreshRateController mRefreshRateController;
+
private boolean mIsSurfaceOpaque;
private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
@@ -1004,6 +1139,8 @@ public final class ViewRootImpl implements ViewParent,
mWindowLayout = windowLayout;
mDisplay = display;
mBasePackageName = context.getBasePackageName();
+ final String name = DisplayProperties.debug_vri_package().orElse(null);
+ mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -1037,6 +1174,13 @@ public final class ViewRootImpl implements ViewParent,
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ // Whether the variable refresh rate for typing is supported.
+ boolean useVariableRefreshRateWhenTyping = context.getResources().getBoolean(
+ R.bool.config_variableRefreshRateTypingSupported);
+ if (useVariableRefreshRateWhenTyping) {
+ mRefreshRateController = new ViewRootRefreshRateController(this);
+ }
+
mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
mIsStylusPointerIconEnabled =
InputSettings.isStylusPointerIconEnabled(mContext);
@@ -1438,6 +1582,10 @@ public final class ViewRootImpl implements ViewParent,
// We should update mAttachInfo.mDisplayState after registerDisplayListener
// because displayState might be changed before registerDisplayListener.
mAttachInfo.mDisplayState = mDisplay.getState();
+ if (mExtraDisplayListenerLogging) {
+ Slog.i(mTag, "(" + mBasePackageName + ") Initial DisplayState: "
+ + mAttachInfo.mDisplayState, new Throwable());
+ }
if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
mUseBLASTAdapter = true;
}
@@ -1522,6 +1670,9 @@ public final class ViewRootImpl implements ViewParent,
* Register any kind of listeners if setView was success.
*/
private void registerListeners() {
+ if (mExtraDisplayListenerLogging) {
+ Slog.i(mTag, "Register listeners: " + mBasePackageName);
+ }
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager, mHandler);
mAccessibilityManager.addHighTextContrastStateChangeListener(
@@ -1547,6 +1698,9 @@ public final class ViewRootImpl implements ViewParent,
DisplayManagerGlobal
.getInstance()
.unregisterDisplayListener(mDisplayListener);
+ if (mExtraDisplayListenerLogging) {
+ Slog.w(mTag, "Unregister listeners: " + mBasePackageName, new Throwable());
+ }
}
private void setTag() {
@@ -1798,6 +1952,9 @@ public final class ViewRootImpl implements ViewParent,
// Request to update light center.
mAttachInfo.mNeedsUpdateLightCenter = true;
}
+ if ((changes & WindowManager.LayoutParams.COLOR_MODE_CHANGED) != 0) {
+ invalidate();
+ }
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
@@ -1900,9 +2057,10 @@ public final class ViewRootImpl implements ViewParent,
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
+ final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
&& !displayChanged && !forceNextWindowRelayout
- && !compatScaleChanged) {
+ && !compatScaleChanged && !dragResizingChanged) {
return;
}
@@ -1950,9 +2108,16 @@ public final class ViewRootImpl implements ViewParent,
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayChanged(int displayId) {
+ if (mExtraDisplayListenerLogging) {
+ Slog.i(mTag, "Received onDisplayChanged - " + mView);
+ }
if (mView != null && mDisplay.getDisplayId() == displayId) {
final int oldDisplayState = mAttachInfo.mDisplayState;
final int newDisplayState = mDisplay.getState();
+ if (mExtraDisplayListenerLogging) {
+ Slog.i(mTag, "DisplayState - old: " + oldDisplayState
+ + ", new: " + newDisplayState);
+ }
if (oldDisplayState != newDisplayState) {
mAttachInfo.mDisplayState = newDisplayState;
pokeDrawLockIfNeeded();
@@ -2074,6 +2239,10 @@ public final class ViewRootImpl implements ViewParent,
if (!mIsInTraversal) {
scheduleTraversals();
}
+
+ if (!mInsetsController.getState().isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+ notifyLeaveTypingEvent();
+ }
}
@Override
@@ -2261,7 +2430,7 @@ public final class ViewRootImpl implements ViewParent,
* Parenting to this layer will ensure that its children are cropped by the view's surface
* insets.
*/
- public SurfaceControl getBoundsLayer() {
+ public SurfaceControl updateAndGetBoundsLayer(Transaction t) {
if (mBoundsLayer == null) {
mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
.setContainerLayer()
@@ -2269,8 +2438,8 @@ public final class ViewRootImpl implements ViewParent,
.setParent(getSurfaceControl())
.setCallsite("ViewRootImpl.getBoundsLayer")
.build();
- setBoundsLayerCrop(mTransaction);
- mTransaction.show(mBoundsLayer).apply();
+ setBoundsLayerCrop(t);
+ t.show(mBoundsLayer);
}
return mBoundsLayer;
}
@@ -2378,6 +2547,22 @@ public final class ViewRootImpl implements ViewParent,
return r.intersect(0, 0, mWidth, mHeight);
}
+ @Override
+ public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
+ @NonNull Matrix matrix, boolean isHover) {
+ if (child != mView) {
+ throw new IllegalArgumentException("child " + child + " is not the root view "
+ + mView + " managed by this ViewRootImpl");
+ }
+
+ RectF rectF = new RectF(0, 0, mWidth, mHeight);
+ matrix.mapRect(rectF);
+ // Note: don't apply scroll offset, because we want to know its
+ // visibility in the virtual canvas being given to the view hierarchy.
+ return region.op(Math.round(rectF.left), Math.round(rectF.top),
+ Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
+ }
+
@Override
public void bringChildToFront(View child) {
}
@@ -2820,16 +3005,15 @@ public final class ViewRootImpl implements ViewParent,
if (mLastWindowInsets == null || forceConstruct) {
final Configuration config = getConfiguration();
mLastWindowInsets = mInsetsController.calculateInsets(
- config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
- mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
- mWindowAttributes.softInputMode, mWindowAttributes.flags,
- (mWindowAttributes.systemUiVisibility
+ config.isScreenRound(), mWindowAttributes.type,
+ config.windowConfiguration.getActivityType(), mWindowAttributes.softInputMode,
+ mWindowAttributes.flags, (mWindowAttributes.systemUiVisibility
| mWindowAttributes.subtreeSystemUiVisibility));
mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect());
mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect());
mAttachInfo.mVisibleInsets.set(mInsetsController.calculateVisibleInsets(
- mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
+ mWindowAttributes.type, config.windowConfiguration.getActivityType(),
mWindowAttributes.softInputMode, mWindowAttributes.flags).toRect());
}
return mLastWindowInsets;
@@ -3772,6 +3956,11 @@ public final class ViewRootImpl implements ViewParent,
boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
boolean cancelAndRedraw = cancelDueToPreDrawListener
|| (cancelDraw && mDrewOnceForSync);
+ if (cancelAndRedraw) {
+ Log.d(mTag, "Cancelling draw."
+ + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+ + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
+ }
if (!cancelAndRedraw) {
// A sync was already requested before the WMS requested sync. This means we need to
// sync the buffer, regardless if WMS wants to sync the buffer.
@@ -3819,7 +4008,7 @@ public final class ViewRootImpl implements ViewParent,
}
mPendingTransitions.clear();
}
- if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+ if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) {
mActiveSurfaceSyncGroup.markSyncReady();
}
}
@@ -3854,7 +4043,15 @@ public final class ViewRootImpl implements ViewParent,
mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {
mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
- reportDrawFinished(t, seqId);
+ // See b/286355097. If the current process is not system, then invoking finishDraw on
+ // any thread is fine since once it calls into system process, finishDrawing will run
+ // on a different thread. However, when the current process is system, the finishDraw in
+ // system server will be run on the current thread, which could result in a deadlock.
+ if (mWindowSession instanceof Binder) {
+ reportDrawFinished(t, seqId);
+ } else {
+ mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId));
+ }
});
if (DEBUG_BLAST) {
Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
@@ -4582,6 +4779,10 @@ public final class ViewRootImpl implements ViewParent,
});
}
+ /**
+ * These callbacks check if the draw failed for any reason and apply
+ * those transactions directly so they don't get stuck forever.
+ */
private void registerCallbackForPendingTransactions() {
Transaction t = new Transaction();
t.merge(mPendingTransaction);
@@ -4610,7 +4811,7 @@ public final class ViewRootImpl implements ViewParent,
});
}
- private boolean performDraw() {
+ private boolean performDraw(@Nullable SurfaceSyncGroup surfaceSyncGroup) {
mLastPerformDrawSkippedReason = null;
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
mLastPerformDrawSkippedReason = "screen_off";
@@ -4620,7 +4821,7 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
- final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
@@ -4628,22 +4829,12 @@ public final class ViewRootImpl implements ViewParent,
addFrameCommitCallbackIfNeeded();
- boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
- if (usingAsyncReport) {
- registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
- } else if (mHasPendingTransactions) {
- // These callbacks are only needed if there's no sync involved and there were calls to
- // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
- // apply those transactions directly so they don't get stuck forever.
- registerCallbackForPendingTransactions();
- }
- mHasPendingTransactions = false;
+ boolean usingAsyncReport;
try {
- boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
- if (usingAsyncReport && !canUseAsync) {
+ usingAsyncReport = draw(fullRedrawNeeded, surfaceSyncGroup, mSyncBuffer);
+ if (mAttachInfo.mThreadedRenderer != null && !usingAsyncReport) {
mAttachInfo.mThreadedRenderer.setFrameCallback(null);
- usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
@@ -4681,10 +4872,12 @@ public final class ViewRootImpl implements ViewParent,
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
- SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
- mHandler.post(() -> surfaceSyncGroup.markSyncReady()));
- mActiveSurfaceSyncGroup = null;
+ usingAsyncReport = true;
+ SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
+ if (surfaceSyncGroup != null) {
+ surfaceSyncGroup.markSyncReady();
+ }
+ });
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4695,8 +4888,9 @@ public final class ViewRootImpl implements ViewParent,
}
}
}
- if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
- mActiveSurfaceSyncGroup.markSyncReady();
+
+ if (surfaceSyncGroup != null && !usingAsyncReport) {
+ surfaceSyncGroup.markSyncReady();
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
@@ -4789,7 +4983,8 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
+ private boolean draw(boolean fullRedrawNeeded,
+ @Nullable SurfaceSyncGroup activeSyncGroup, boolean syncBuffer) {
Surface surface = mSurface;
if (!surface.isValid()) {
return false;
@@ -4933,9 +5128,19 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(mRenderHdrSdrRatio);
}
- if (forceDraw) {
- mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+ if (activeSyncGroup != null) {
+ registerCallbacksForSync(syncBuffer, activeSyncGroup);
+ if (syncBuffer) {
+ mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+ }
+ } else if (mHasPendingTransactions) {
+ // Register a calback if there's no sync involved but there were calls to
+ // applyTransactionOnDraw. If there is a sync involved, the sync callback will
+ // handle merging the pending transaction.
+ registerCallbackForPendingTransactions();
}
+ mHasPendingTransactions = false;
+
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
@@ -5481,6 +5686,7 @@ public final class ViewRootImpl implements ViewParent,
if (desiredRatio != mDesiredHdrSdrRatio) {
mDesiredHdrSdrRatio = desiredRatio;
updateRenderHdrSdrRatio();
+ invalidate();
if (mDesiredHdrSdrRatio < 1.01f) {
mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
@@ -6797,6 +7003,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Restores the refresh rate after leaving typing, the leaving typing cases like
+ * the IME insets is invisible or the user interacts the screen outside keyboard.
+ */
+ @UiThread
+ private void notifyLeaveTypingEvent() {
+ if (mRefreshRateController != null && mActiveTypingHintNotifier.get() != null) {
+ mRefreshRateController.updateRefreshRatePreference(RESTORE);
+ }
+ }
+
/**
* Delivers post-ime input events to the view hierarchy.
*/
@@ -7014,6 +7231,10 @@ public final class ViewRootImpl implements ViewParent,
mLastClickToolType = event.getToolType(event.getActionIndex());
}
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ notifyLeaveTypingEvent();
+ }
+
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
// If the event was fully handled by the handwriting initiator, then don't dispatch it
@@ -11171,7 +11392,8 @@ public final class ViewRootImpl implements ViewParent,
@Nullable public SurfaceControl.Transaction buildReparentTransaction(
@NonNull SurfaceControl child) {
if (mSurfaceControl.isValid()) {
- return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
+ Transaction t = new Transaction();
+ return t.reparent(child, updateAndGetBoundsLayer(t));
}
return null;
}
@@ -11553,4 +11775,17 @@ public final class ViewRootImpl implements ViewParent,
mChildBoundingInsetsChanged = true;
scheduleTraversals();
}
+
+ @Override
+ public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+ @NonNull Executor executor, @NonNull Consumer listener) {
+ t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
+ }
+
+ @Override
+ public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+ @NonNull Consumer listener) {
+ t.clearTrustedPresentationCallback(getSurfaceControl());
+ }
}
diff --git a/core/java/android/view/ViewRootRefreshRateController.java b/core/java/android/view/ViewRootRefreshRateController.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb9a81c03479b6c05ffcf27fd78a2417a4ae40da
--- /dev/null
+++ b/core/java/android/view/ViewRootRefreshRateController.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.os.Trace.TRACE_TAG_VIEW;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Trace;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Controller to request refresh rate preference operations to the {@link ViewRootImpl}.
+ *
+ * @hide
+ */
+public class ViewRootRefreshRateController {
+
+ private static final String TAG = "VRRefreshRateController";
+
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final float TARGET_REFRESH_RATE_UPPER_BOUND = 60f;
+
+ @NonNull
+ private final ViewRootImpl mViewRootImpl;
+
+ private final RefreshRateParams mRateParams;
+
+ private final boolean mHasPreferredRefreshRate;
+
+ private int mRefreshRatePref = RefreshRatePref.NONE;
+
+ private boolean mMaxRefreshRateOverride = false;
+
+ @IntDef(value = {
+ RefreshRatePref.NONE,
+ RefreshRatePref.LOWER,
+ RefreshRatePref.RESTORE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RefreshRatePref {
+ /**
+ * Indicates that no refresh rate preference.
+ */
+ int NONE = 0;
+
+ /**
+ * Indicates that apply the lower refresh rate.
+ */
+ int LOWER = 1;
+
+ /**
+ * Indicates that restore to previous refresh rate.
+ */
+ int RESTORE = 2;
+ }
+
+ public ViewRootRefreshRateController(@NonNull ViewRootImpl viewRoot) {
+ mViewRootImpl = viewRoot;
+ mRateParams = new RefreshRateParams(getLowerSupportedRefreshRate());
+ mHasPreferredRefreshRate = hasPreferredRefreshRate();
+ if (mHasPreferredRefreshRate && DEBUG) {
+ Log.d(TAG, "App has preferred refresh rate. name:" + viewRoot);
+ }
+ }
+
+ /**
+ * Updates the preference to {@link ViewRootRefreshRateController#mRefreshRatePref},
+ * and check if it's needed to update the preferred refresh rate on demand. Like if the
+ * user is typing, try to apply the {@link RefreshRateParams#mTargetRefreshRate}.
+ *
+ * @param refreshRatePref to indicate the refresh rate preference
+ */
+ public void updateRefreshRatePreference(@RefreshRatePref int refreshRatePref) {
+ mRefreshRatePref = refreshRatePref;
+ doRefreshRateCheck();
+ }
+
+ private void doRefreshRateCheck() {
+ if (mRefreshRatePref == RefreshRatePref.NONE) {
+ return;
+ }
+ if (mHasPreferredRefreshRate) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "mMaxRefreshRateOverride:" + mMaxRefreshRateOverride
+ + ", mRefreshRatePref:" + refreshRatePrefToString(mRefreshRatePref));
+ }
+
+ switch (mRefreshRatePref) {
+ case RefreshRatePref.LOWER :
+ if (!mMaxRefreshRateOverride) {
+ // Save previous preferred rate before update
+ mRateParams.savePreviousRefreshRateParams(mViewRootImpl.mWindowAttributes);
+ updateMaxRefreshRate();
+ } else if (mViewRootImpl.mDisplay.getRefreshRate()
+ > mRateParams.mTargetRefreshRate) {
+ // Boosted, try to update again.
+ updateMaxRefreshRate();
+ }
+ break;
+ case RefreshRatePref.RESTORE :
+ resetRefreshRate();
+ break;
+ default :
+ throw new RuntimeException("Unexpected value: " + mRefreshRatePref);
+ }
+ }
+
+ private void updateMaxRefreshRate() {
+ Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.updateMaxRefreshRate");
+ WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
+ params.preferredMaxDisplayRefreshRate = mRateParams.mTargetRefreshRate;
+ mViewRootImpl.setLayoutParams(params, false);
+ mMaxRefreshRateOverride = true;
+ Trace.instant(TRACE_TAG_VIEW, "VRRC update preferredMax="
+ + mRateParams.mTargetRefreshRate);
+ Trace.traceEnd(TRACE_TAG_VIEW);
+ if (DEBUG) {
+ Log.d(TAG, "update max refresh rate to: " + params.preferredMaxDisplayRefreshRate);
+ }
+ }
+
+ private void resetRefreshRate() {
+ if (!mMaxRefreshRateOverride) {
+ return;
+ }
+ Trace.traceBegin(TRACE_TAG_VIEW, "VRRC.resetRefreshRate");
+ WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
+ params.preferredMaxDisplayRefreshRate = mRateParams.mPreviousPreferredMaxRefreshRate;
+ mViewRootImpl.setLayoutParams(params, false);
+ mMaxRefreshRateOverride = false;
+ Trace.instant(TRACE_TAG_VIEW, "VRRC restore previous="
+ + mRateParams.mPreviousPreferredMaxRefreshRate);
+ Trace.traceEnd(TRACE_TAG_VIEW);
+ if (DEBUG) {
+ Log.d(TAG, "reset max refresh rate to: " + params.preferredMaxDisplayRefreshRate);
+ }
+ }
+
+ private boolean hasPreferredRefreshRate() {
+ WindowManager.LayoutParams params = mViewRootImpl.mWindowAttributes;
+ return params.preferredRefreshRate > 0
+ || params.preferredMaxDisplayRefreshRate > 0
+ || params.preferredMinDisplayRefreshRate > 0
+ || params.preferredDisplayModeId > 0;
+ }
+
+ private float getLowerSupportedRefreshRate() {
+ final Display display = mViewRootImpl.mDisplay;
+ final Display.Mode defaultMode = display.getDefaultMode();
+ float targetRefreshRate = defaultMode.getRefreshRate();
+ for (Display.Mode mode : display.getSupportedModes()) {
+ if (mode.getRefreshRate() < targetRefreshRate) {
+ targetRefreshRate = mode.getRefreshRate();
+ }
+ }
+ if (targetRefreshRate < TARGET_REFRESH_RATE_UPPER_BOUND) {
+ targetRefreshRate = TARGET_REFRESH_RATE_UPPER_BOUND;
+ }
+ return targetRefreshRate;
+ }
+
+ private static String refreshRatePrefToString(@RefreshRatePref int pref) {
+ switch (pref) {
+ case RefreshRatePref.NONE:
+ return "NONE";
+ case RefreshRatePref.LOWER:
+ return "LOWER";
+ case RefreshRatePref.RESTORE:
+ return "RESTORE";
+ default:
+ return "Unknown pref=" + pref;
+ }
+ }
+
+ /**
+ * A class for recording refresh rate parameters of the target view, including the target
+ * refresh rate we want to apply when entering particular states, and the original preferred
+ * refresh rate for restoring when leaving the state.
+ */
+ private static class RefreshRateParams {
+ float mTargetRefreshRate;
+
+ float mPreviousPreferredMaxRefreshRate = 0;
+
+ RefreshRateParams(float targetRefreshRate) {
+ mTargetRefreshRate = targetRefreshRate;
+ if (DEBUG) {
+ Log.d(TAG, "The target rate: " + targetRefreshRate);
+ }
+ }
+ void savePreviousRefreshRateParams(WindowManager.LayoutParams param) {
+ mPreviousPreferredMaxRefreshRate = param.preferredMaxDisplayRefreshRate;
+ if (DEBUG) {
+ Log.d(TAG, "Save previous params, preferred: " + param.preferredRefreshRate
+ + ", Max: " + param.preferredMaxDisplayRefreshRate);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 21fe87f42e1bf565f555f6eb934510e7adba24e9..2f04b0c695daca7805304b01570e56e461418002 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -657,6 +657,12 @@ public abstract class Window {
*/
void updateStatusBarColor(int color);
+ /**
+ * Update the status bar appearance.
+ */
+
+ void updateStatusBarAppearance(int appearance);
+
/**
* Update the navigation bar color to a forced one.
*/
@@ -1039,6 +1045,9 @@ public abstract class Window {
if (mDecorCallback != null) {
mDecorCallback.onSystemBarAppearanceChanged(appearance);
}
+ if (mWindowControllerCallback != null) {
+ mWindowControllerCallback.updateStatusBarAppearance(appearance);
+ }
}
/** @hide */
@@ -1481,6 +1490,11 @@ public abstract class Window {
}
}
+ /** @hide */
+ public boolean shouldCloseOnTouchOutside() {
+ return mCloseOnTouchOutside;
+ }
+
/** @hide */
@SuppressWarnings("HiddenAbstractMethod")
@UnsupportedAppUsage
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 4acaea8495868a98404c904c104939a8bda5d3dc..57a41619ff8ddd89ec9632011724f0261c2d565d 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -84,13 +84,7 @@ public final class WindowInsets {
@Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
@Nullable private final DisplayShape mDisplayShape;
- /**
- * In multi-window we force show the navigation bar. Because we don't want that the surface size
- * changes in this mode, we instead have a flag whether the navigation bar size should always
- * be consumed, so the app is treated like there is no virtual navigation bar at all.
- */
- private final boolean mAlwaysConsumeSystemBars;
-
+ private final @InsetsType int mForceConsumingTypes;
private final @InsetsType int mSuppressScrimTypes;
private final boolean mSystemWindowInsetsConsumed;
private final boolean mStableInsetsConsumed;
@@ -117,7 +111,7 @@ public final class WindowInsets {
static {
CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
- createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null,
+ createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
null, null, null, systemBars(), false);
}
@@ -137,7 +131,8 @@ public final class WindowInsets {
@Nullable Insets[] typeMaxInsetsMap,
boolean[] typeVisibilityMap,
boolean isRound,
- boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes,
+ @InsetsType int forceConsumingTypes,
+ @InsetsType int suppressScrimTypes,
DisplayCutout displayCutout,
RoundedCorners roundedCorners,
PrivacyIndicatorBounds privacyIndicatorBounds,
@@ -155,7 +150,7 @@ public final class WindowInsets {
mTypeVisibilityMap = typeVisibilityMap;
mIsRound = isRound;
- mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+ mForceConsumingTypes = forceConsumingTypes;
mSuppressScrimTypes = suppressScrimTypes;
mCompatInsetsTypes = compatInsetsTypes;
mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -178,7 +173,7 @@ public final class WindowInsets {
this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
src.mTypeVisibilityMap, src.mIsRound,
- src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes,
+ src.mForceConsumingTypes, src.mSuppressScrimTypes,
displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
@@ -235,7 +230,7 @@ public final class WindowInsets {
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0,
+ this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
null, null, null, null, systemBars(), false /* compatIgnoreVisibility */);
}
@@ -556,7 +551,7 @@ public final class WindowInsets {
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
- mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -607,7 +602,7 @@ public final class WindowInsets {
public WindowInsets consumeSystemWindowInsets() {
return new WindowInsets(null, null,
mTypeVisibilityMap,
- mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
// If the system window insets types contain displayCutout, we should also consume
// it.
(mCompatInsetsTypes & displayCutout()) != 0
@@ -895,8 +890,8 @@ public final class WindowInsets {
/**
* @hide
*/
- public boolean shouldAlwaysConsumeSystemBars() {
- return mAlwaysConsumeSystemBars;
+ public @InsetsType int getForceConsumingTypes() {
+ return mForceConsumingTypes;
}
/**
@@ -930,6 +925,8 @@ public final class WindowInsets {
result.append("\n ");
result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
result.append("\n ");
+ result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
+ result.append("\n ");
result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
result.append("\n ");
result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1027,7 +1024,7 @@ public final class WindowInsets {
? null
: insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
mTypeVisibilityMap,
- mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
mDisplayCutoutConsumed
? null
: mDisplayCutout == null
@@ -1050,7 +1047,7 @@ public final class WindowInsets {
WindowInsets that = (WindowInsets) o;
return mIsRound == that.mIsRound
- && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars
+ && mForceConsumingTypes == that.mForceConsumingTypes
&& mSuppressScrimTypes == that.mSuppressScrimTypes
&& mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
&& mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1068,7 +1065,7 @@ public final class WindowInsets {
public int hashCode() {
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
- mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
+ mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
mDisplayShape);
}
@@ -1134,7 +1131,7 @@ public final class WindowInsets {
private DisplayShape mDisplayShape = DisplayShape.NONE;
private boolean mIsRound;
- private boolean mAlwaysConsumeSystemBars;
+ private @InsetsType int mForceConsumingTypes;
private @InsetsType int mSuppressScrimTypes;
private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1162,7 +1159,7 @@ public final class WindowInsets {
mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
mRoundedCorners = insets.mRoundedCorners;
mIsRound = insets.mIsRound;
- mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
+ mForceConsumingTypes = insets.mForceConsumingTypes;
mSuppressScrimTypes = insets.mSuppressScrimTypes;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
mDisplayShape = insets.mDisplayShape;
@@ -1433,7 +1430,15 @@ public final class WindowInsets {
/** @hide */
@NonNull
public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) {
- mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+ // TODO (b/277891341): Remove this and related usages. This has been replaced by
+ // #setForceConsumingTypes.
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) {
+ mForceConsumingTypes = forceConsumingTypes;
return this;
}
@@ -1453,7 +1458,7 @@ public final class WindowInsets {
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
- mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout,
+ mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
false /* compatIgnoreVisibility */);
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index bc0bab7b5e9533fe2d41b287459497759aa06a5d..cc2cd7982841489b090a81b3964bd359cd243c53 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -249,6 +249,16 @@ public interface WindowInsetsController {
*/
void setCaptionInsetsHeight(int height);
+ /**
+ * Sets the insets height for the IME caption bar, which corresponds to the
+ * "fake" IME navigation bar.
+ *
+ * @param height the insets height of the IME caption bar.
+ * @hide
+ */
+ default void setImeCaptionBarInsetsHeight(int height) {
+ }
+
/**
* Controls the behavior of system bars.
*
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 3b8298ed3627d56f32699e1a09ec7f07f841bded..dda399357d8c8840e9dcfb1d57a2f690b6e6663b 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -124,16 +124,16 @@ public class WindowLayout {
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
displayFrame, systemBars(), requestedVisibleTypes);
- if (systemBarsInsets.left > 0) {
+ if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
- if (systemBarsInsets.top > 0) {
+ if (systemBarsInsets.top >= cutout.getSafeInsetTop()) {
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
}
- if (systemBarsInsets.right > 0) {
+ if (systemBarsInsets.right >= cutout.getSafeInsetRight()) {
displayCutoutSafeExceptMaybeBars.right = MAX_X;
}
- if (systemBarsInsets.bottom > 0) {
+ if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) {
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a91781580ac4f63345797652f6b2a0aa663e2497..c00924b61a345248c775bb5f764e4f718428f9fd 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -880,22 +880,23 @@ public interface WindowManager extends ViewManager {
int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600;
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app can be opted-in or opted-out
- * from the compatibility treatment that avoids {@link
- * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
- * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
- * orientation of the device.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app can be opted-in or opted-out from the
+ * compatibility treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
* The treatment is disabled by default but device manufacturers can enable the treatment
* using their discretion to improve display compatibility.
*
- *
With this property set to {@code true}, the system could ignore {@link
- * android.app.Activity#setRequestedOrientation} call from an app if one of the following
- * conditions are true:
+ *
With this property set to {@code true}, the system could ignore
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()} call
+ * from an app if one of the following conditions are true:
*
- * - Activity is relaunching due to the previous {@link
- * android.app.Activity#setRequestedOrientation} call.
+ *
- Activity is relaunching due to the previous
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}
+ * call.
*
- Camera compatibility force rotation treatment is active for the package.
*
*
@@ -912,21 +913,22 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
"android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* for an app to inform the system that the app can be opted-out from the compatibility
- * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop
- * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or
- * by the landscape natural orientation of the device.
+ * treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
- * The system could ignore {@link android.app.Activity#setRequestedOrientation}
- * call from an app if both of the following conditions are true:
+ *
The system could ignore {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} call from an app if both of the following conditions are
+ * true:
*
- * - Activity has requested orientation more than 2 times within 1-second timer
+ *
- Activity has requested orientation more than two times within one-second timer
*
- Activity is not letterboxed for fixed orientation
*
*
@@ -953,23 +955,21 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that it needs to be opted-out from the
- * compatibility treatment that sandboxes {@link android.view.View} API.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that it needs to be opted-out from the compatibility
+ * treatment that sandboxes the {@link android.view.View View} API.
*
* The treatment can be enabled by device manufacturers for applications which misuse
- * {@link android.view.View} APIs by expecting that
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View#getWindowDisplayFrame}
+ * {@link android.view.View View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen View#getLocationOnScreen()} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame View#getWindowVisibleDisplayFrame()}
* return coordinates as if an activity is positioned in the top-left corner of the screen, with
- * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and
* letterbox modes.
*
*
Setting this property to {@code false} informs the system that the application must be
- * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
- * if the device manufacturer has opted the app into the treatment.
+ * opted-out from the "Sandbox View API to Activity bounds" treatment even if the device
+ * manufacturer has opted the app into the treatment.
*
*
Not setting this property at all, or setting this property to {@code true} has no effect.
*
@@ -982,17 +982,15 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Make this public API.
String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the application can be opted-in or opted-out
- * from the compatibility treatment that enables sending a fake focus event for unfocused
- * resumed split screen activities. This is needed because some game engines wait to get
- * focus before drawing the content of the app which isn't guaranteed by default in multi-window
- * modes.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the application can be opted-in or opted-out from the
+ * compatibility treatment that enables sending a fake focus event for unfocused resumed
+ * split-screen activities. This is needed because some game engines wait to get focus before
+ * drawing the content of the app which isn't guaranteed by default in multi-window mode.
*
*
Device manufacturers can enable this treatment using their discretion on a per-device
* basis to improve display compatibility. The treatment also needs to be specifically enabled
@@ -1018,13 +1016,12 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the camera compatibility
+ * force rotation treatment.
*
*
The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1034,10 +1031,11 @@ public interface WindowManager extends ViewManager {
* rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
* camera and is removed once camera is closed.
*
- *
The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see Enhanced letterboxing
- * for more details).
+ *
The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * Enhanced
+ * letterboxing for more details).
*
*
With this property set to {@code true} or unset, the system may apply the force rotation
* treatment to fixed orientation activities. Device manufacturers can exclude packages from the
@@ -1055,14 +1053,13 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded
- * from the activity "refresh" after the camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the activity "refresh"
+ * after the camera compatibility force rotation treatment.
*
*
The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1079,10 +1076,11 @@ public interface WindowManager extends ViewManager {
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*
- *
The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see Enhanced letterboxing
- * for more details).
+ *
The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * Enhanced
+ * letterboxing for more details).
*
*
With this property set to {@code true} or unset, the system may "refresh" activity after
* the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
@@ -1100,15 +1098,14 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be or shouldn't be
- * "refreshed" after the camera compatibility force rotation treatment using "paused ->
- * resumed" cycle rather than "stopped -> resumed".
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the activity should be or shouldn't be "refreshed" after
+ * the camera compatibility force rotation treatment using "paused -> resumed" cycle rather than
+ * "stopped -> resumed".
*
*
The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1124,10 +1121,11 @@ public interface WindowManager extends ViewManager {
* values in apps (e.g., display or camera rotation) that influence camera preview and can lead
* to sideways or stretching issues persisting even after force rotation.
*
- *
The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see Enhanced letterboxing
- * for more details).
+ *
The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * Enhanced
+ * letterboxing for more details).
*
*
Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
* cycle using their discretion to improve display compatibility.
@@ -1148,27 +1146,27 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * compatibility override for orientation set by the device manufacturer. When the orientation
- * override is applied it can:
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the compatibility
+ * override for orientation set by the device manufacturer. When the orientation override is
+ * applied it can:
*
* - Replace the specific orientation requested by the app with another selected by the
- device manufacturer, e.g. replace undefined requested by the app with portrait.
+ device manufacturer; for example, replace undefined requested by the app with portrait.
*
- Always use an orientation selected by the device manufacturer.
*
- Do one of the above but only when camera connection is open.
*
*
- * This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+ *
This property is different from {@link #PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
* (which is used to avoid orientation loops caused by the incorrect use of {@link
- * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
- * orientation selected by the device manufacturer rather than ignoring one of orientation
- * requests coming from the app while respecting the previous one.
+ * android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}) because
+ * this property overrides the app to an orientation selected by the device manufacturer rather
+ * than ignoring one of orientation requests coming from the app while respecting the previous
+ * one.
*
*
With this property set to {@code true} or unset, device manufacturers can override
* orientation for the app using their discretion to improve display compatibility.
@@ -1185,15 +1183,14 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that fixes display orientation to landscape natural orientation when
- * an activity is fullscreen.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that fixes display orientation to landscape natural orientation when an activity is
+ * fullscreen.
*
*
When this compat override is enabled and while display is fixed to the landscape natural
* orientation, the orientation requested by the activity will be still respected by bounds
@@ -1202,16 +1199,17 @@ public interface WindowManager extends ViewManager {
* lanscape natural orientation.
*
*
The treatment is disabled by default but device manufacturers can enable the treatment
- * using their discretion to improve display compatibility on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see Enhanced letterboxing
- * for more details).
+ * using their discretion to improve display compatibility on displays that have the ignore
+ * orientation request display setting enabled by OEMs on the device (enables compatibility mode
+ * for fixed orientation on Android 12 (API level 31) or higher; see
+ * Enhanced
+ * letterboxing for more details).
*
*
With this property set to {@code true} or unset, the system wiil use landscape display
* orientation when the following conditions are met:
*
* - Natural orientation of the display is landscape
- *
- ignoreOrientationRequest display setting is enabled
+ *
- ignore requested orientation display setting is enabled
*
- Activity is fullscreen.
*
- Device manufacturer enabled the treatment.
*
@@ -1228,14 +1226,13 @@ public interface WindowManager extends ViewManager {
* </application>
*
*/
- // TODO(b/263984287): Add CTS tests.
String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that changes the min aspect ratio.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that changes the min aspect ratio.
*
* When this compat override is enabled the min aspect ratio given in the app's manifest can
* be overridden by the device manufacturer using their discretion to improve display
@@ -1264,14 +1261,14 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility overrides that change the resizability of the app.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * overrides that change the resizability of the app.
*
*
When these compat overrides are enabled they force the packages they are applied to to be
- * resizable / unresizable. If the app is forced to be resizable this won't change whether
- * the app can be put into multi-windowing mode, but allow the app to resize without going into
- * size-compat mode when the window container resizes, such as display size change or screen
+ * resizable/unresizable. If the app is forced to be resizable this won't change whether the app
+ * can be put into multi-windowing mode, but allow the app to resize without going into size
+ * compatibility mode when the window container resizes, such as display size change or screen
* rotation.
*
*
Setting this property to {@code false} informs the system that the app must be
@@ -1294,6 +1291,102 @@ public interface WindowManager extends ViewManager {
String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES =
"android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
+ /**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * user-facing aspect ratio compatibility override.
+ *
+ *
The compatibility override enables device users to set the app's aspect
+ * ratio or force the app to fill the display regardless of the aspect
+ * ratio or orientation specified in the app manifest.
+ *
+ *
The aspect ratio compatibility override is exposed to users in device
+ * settings. A menu in device settings lists all apps that have not opted out of
+ * the compatibility override. Users select apps from the menu and set the
+ * app aspect ratio on a per-app basis. Typically, the menu is available
+ * only on large screen devices.
+ *
+ *
When users apply the aspect ratio override, the minimum aspect ratio
+ * specified in the app manifest is overridden. If users choose a
+ * full-screen aspect ratio, the orientation of the activity is forced to
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+ * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+ * disable the full-screen option only.
+ *
+ *
The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ *
+ * Large screen compatibility mode
+ * for more details).
+ *
+ *
To opt out of the user aspect ratio compatibility override, add this property
+ * to your app manifest and set the value to {@code false}. Your app will be excluded
+ * from the list of apps in device settings, and users will not be able to override
+ * the app's aspect ratio.
+ *
+ *
Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ *
Syntax:
+ *
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ *
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+ /**
+ * Application level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that (when set to false) informs the system the app has opted out of the
+ * full-screen option of the user aspect ratio compatibility override settings. (For
+ * background information about the user aspect ratio compatibility override, see
+ * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+ *
+ * When users apply the full-screen compatibility override, the orientation
+ * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+ *
+ *
The user override is intended to improve the app experience on devices
+ * that have the ignore orientation request display setting enabled by OEMs
+ * (enables compatibility mode for fixed orientation on Android 12 (API
+ * level 31) or higher; see
+ *
+ * Large screen compatibility mode
+ * for more details).
+ *
+ *
To opt out of the full-screen option of the user aspect ratio compatibility
+ * override, add this property to your app manifest and set the value to {@code false}.
+ * Your app will have full-screen option removed from the list of user aspect ratio
+ * override options in device settings, and users will not be able to apply
+ * full-screen override to your app.
+ *
+ *
Note: If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+ * {@code false}, this property has no effect.
+ *
+ *
Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ *
Syntax:
+ *
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+ * android:value="false"/>
+ * </application>
+ *
+ * @hide
+ */
+ // TODO(b/294227289): Make this public API
+ String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+ "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
/**
* @hide
*/
@@ -1320,34 +1413,29 @@ public interface WindowManager extends ViewManager {
}
/**
- * Application-level
- * {@link android.content.pm.PackageManager.Property PackageManager.Property}
- * tag that specifies whether OEMs are permitted to provide activity
- * embedding split-rule configurations on behalf of the app.
+ * Application-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that specifies whether OEMs are permitted to provide activity embedding split-rule
+ * configurations on behalf of the app.
*
- * If {@code true}, the system is permitted to override the app's
- * windowing behavior and implement activity embedding split rules, such as
- * displaying activities side by side. A system override informs the app
- * that the activity embedding APIs are disabled so the app will not provide
- * its own activity embedding rules, which would conflict with the system's
+ *
If {@code true}, the system is permitted to override the app's windowing behavior and
+ * implement activity embedding split rules, such as displaying activities side by side. A
+ * system override informs the app that the activity embedding APIs are disabled so the app
+ * doesn't provide its own activity embedding rules, which would conflict with the system's
* rules.
*
- *
If {@code false}, the system is not permitted to override the
- * windowing behavior of the app. Set the property to {@code false} if the
- * app provides its own activity embedding split rules, or if you want to
- * prevent the system override for any other reason.
+ *
If {@code false}, the system is not permitted to override the windowing behavior of the
+ * app. Set the property to {@code false} if the app provides its own activity embedding split
+ * rules, or if you want to prevent the system override for any other reason.
*
*
The default value is {@code false}.
*
- *
Note: Refusal to permit the system override is not
- * enforceable. OEMs can override the app's activity embedding
- * implementation whether or not this property is specified and set to
- * false
. The property is, in effect, a hint to OEMs.
+ *
Note: Refusal to permit the system override is not enforceable. OEMs
+ * can override the app's activity embedding implementation whether or not this property is
+ * specified and set to {@code false}. The property is, in effect, a hint to OEMs.
*
- *
OEMs can implement activity embedding on any API level. The best
- * practice for apps is to always explicitly set this property in the app
- * manifest file regardless of targeted API level rather than rely on the
- * default value.
+ *
OEMs can implement activity embedding on any API level. The best practice for apps is to
+ * always explicitly set this property in the app manifest file regardless of targeted API level
+ * rather than rely on the default value.
*
*
Syntax:
*
@@ -1362,14 +1450,15 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} that an app can specify to inform the system that the app is ActivityEmbedding
- * split feature enabled.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that an app can specify to inform the system that the app is activity embedding split feature
+ * enabled.
*
* With this property, the system could provide custom behaviors for the apps that are
- * ActivityEmbedding split feature enabled. For example, the fixed-portrait orientation
+ * activity embedding split feature enabled. For example, the fixed-portrait orientation
* requests of the activities could be ignored by the system in order to provide seamless
- * ActivityEmbedding split experiences while holding the large-screen devices in landscape mode.
+ * activity embedding split experiences while holding large screen devices in landscape
+ * orientation.
*
*
Syntax:
*
@@ -1384,14 +1473,27 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
/**
- * Request for keyboard shortcuts to be retrieved asynchronously.
+ * Request for app's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
+ * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+ * not triggered by a KeyEvent.
*
* @hide
*/
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
+ /**
+ * Request for ime's keyboard shortcuts to be retrieved asynchronously.
+ *
+ * @param receiver The callback to be triggered when the result is ready.
+ * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+ * not triggered by a KeyEvent.
+ *
+ * @hide
+ */
+ default void requestImeKeyboardShortcuts(KeyboardShortcutsReceiver receiver, int deviceId) {};
+
/**
* Return the touch region for the current IME window, or an empty region if there is none.
*
@@ -3036,15 +3138,6 @@ public interface WindowManager extends ViewManager {
*/
public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
- /**
- * Flag to force the status bar window to be visible all the time. If the bar is hidden when
- * this flag is set it will be shown again.
- * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
- *
- * {@hide}
- */
- public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
-
/**
* Flag to indicate that the window frame should be the requested frame adding the display
* cutout frame. This will only be applied if a specific size smaller than the parent frame
@@ -3084,6 +3177,16 @@ public interface WindowManager extends ViewManager {
*/
public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16;
+ /**
+ * Flag to indicate that this window is a immersive mode confirmation window. The window
+ * should be ignored when calculating insets control. This is used for prompt window
+ * triggered by insets visibility changes. If it can take over the insets control, the
+ * visibility will change unexpectedly and the window may dismiss itself. Power button panic
+ * handling will be disabled when this window exists.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW = 1 << 17;
+
/**
* Flag to indicate that any window added by an application process that is of type
* {@link #TYPE_TOAST} or that requires
@@ -3125,15 +3228,6 @@ public interface WindowManager extends ViewManager {
*/
public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
- /**
- * Flag to indicate that the status bar window is in a state such that it forces showing
- * the navigation bar unless the navigation bar window is explicitly set to
- * {@link View#GONE}.
- * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
- * @hide
- */
- public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
-
/**
* Flag to indicate that the window is color space agnostic, and the color can be
* interpreted to any color space.
@@ -3222,17 +3316,16 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
- PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+ PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
PRIVATE_FLAG_NOT_MAGNIFIABLE,
- PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
PRIVATE_FLAG_USE_BLAST,
PRIVATE_FLAG_APPEARANCE_CONTROLLED,
@@ -3287,10 +3380,6 @@ public interface WindowManager extends ViewManager {
mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
- @ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
- equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
- name = "FORCE_STATUS_BAR_VISIBLE"),
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
@@ -3311,6 +3400,10 @@ public interface WindowManager extends ViewManager {
mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
name = "SUSTAINED_PERFORMANCE_MODE"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW,
+ equals = PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW,
+ name = "IMMERSIVE_CONFIRMATION_WINDOW"),
@ViewDebug.FlagToString(
mask = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
equals = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
@@ -3327,10 +3420,6 @@ public interface WindowManager extends ViewManager {
mask = PRIVATE_FLAG_NOT_MAGNIFIABLE,
equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
name = "NOT_MAGNIFIABLE"),
- @ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
- equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
- name = "STATUS_FORCE_SHOW_NAVIGATION"),
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
@@ -3722,6 +3811,7 @@ public interface WindowManager extends ViewManager {
* @see #ROTATION_ANIMATION_ROTATE
* @see #ROTATION_ANIMATION_CROSSFADE
* @see #ROTATION_ANIMATION_JUMPCUT
+ * @see #ROTATION_ANIMATION_SEAMLESS
*/
public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
@@ -4294,15 +4384,14 @@ public interface WindowManager extends ViewManager {
public InsetsFrameProvider[] providedInsets;
/**
- * If specified, the frame that used to calculate relative {@link RoundedCorner} will be
- * the window frame of this window minus the insets that this window provides.
- *
- * Task bar will draw fake rounded corners above itself, so we need this insets to calculate
- * correct rounded corners for this window.
+ * Specifies which {@link InsetsType}s should be forcibly shown. The types shown by this
+ * method won't affect the app's layout. This field only takes effects if the caller has
+ * {@link android.Manifest.permission#STATUS_BAR_SERVICE} or the caller has the same uid as
+ * the recents component.
*
* @hide
*/
- public boolean insetsRoundedCornerFrame = false;
+ public @InsetsType int forciblyShownTypes;
/**
* {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
@@ -4760,9 +4849,9 @@ public interface WindowManager extends ViewManager {
out.writeBoolean(mFitInsetsIgnoringVisibility);
out.writeBoolean(preferMinimalPostProcessing);
out.writeInt(mBlurBehindRadius);
- out.writeBoolean(insetsRoundedCornerFrame);
out.writeBoolean(mWallpaperTouchEventsEnabled);
out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
+ out.writeInt(forciblyShownTypes);
checkNonRecursiveParams();
out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
out.writeInt(mDisplayFlags);
@@ -4832,9 +4921,9 @@ public interface WindowManager extends ViewManager {
mFitInsetsIgnoringVisibility = in.readBoolean();
preferMinimalPostProcessing = in.readBoolean();
mBlurBehindRadius = in.readInt();
- insetsRoundedCornerFrame = in.readBoolean();
mWallpaperTouchEventsEnabled = in.readBoolean();
providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
+ forciblyShownTypes = in.readInt();
paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
mDisplayFlags = in.readInt();
}
@@ -5140,9 +5229,9 @@ public interface WindowManager extends ViewManager {
changes |= LAYOUT_CHANGED;
}
- if (insetsRoundedCornerFrame != o.insetsRoundedCornerFrame) {
- insetsRoundedCornerFrame = o.insetsRoundedCornerFrame;
- changes |= LAYOUT_CHANGED;
+ if (forciblyShownTypes != o.forciblyShownTypes) {
+ forciblyShownTypes = o.forciblyShownTypes;
+ changes |= PRIVATE_FLAGS_CHANGED;
}
if (paramsForRotation != o.paramsForRotation) {
@@ -5382,9 +5471,10 @@ public interface WindowManager extends ViewManager {
sb.append(prefix).append(" ").append(providedInsets[i]);
}
}
- if (insetsRoundedCornerFrame) {
- sb.append(" insetsRoundedCornerFrame=");
- sb.append(insetsRoundedCornerFrame);
+ if (forciblyShownTypes != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" forciblyShownTypes=").append(
+ WindowInsets.Type.toString(forciblyShownTypes));
}
if (paramsForRotation != null && paramsForRotation.length != 0) {
sb.append(System.lineSeparator());
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index df3e0bb7429285a4822f68e56dd55fa4372af377..b57163c4e4356fc9dcd775cdf47119c1623e49f0 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -215,14 +215,36 @@ public final class WindowManagerImpl implements WindowManager {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
List result =
- resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, android.view.KeyboardShortcutGroup.class);
+ resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+ android.view.KeyboardShortcutGroup.class);
receiver.onKeyboardShortcutsReceived(result);
}
};
try {
WindowManagerGlobal.getWindowManagerService()
- .requestAppKeyboardShortcuts(resultReceiver, deviceId);
+ .requestAppKeyboardShortcuts(resultReceiver, deviceId);
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void requestImeKeyboardShortcuts(
+ final KeyboardShortcutsReceiver receiver, int deviceId) {
+ IResultReceiver resultReceiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) throws RemoteException {
+ List result =
+ resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+ android.view.KeyboardShortcutGroup.class);
+ receiver.onKeyboardShortcutsReceived(result);
+ }
+ };
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .requestImeKeyboardShortcuts(resultReceiver, deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index afc567e5de5c7a59e6aa3195cf2b3ca890bf19b6..c88048c17160c6ae6a077afa7b7d40899ffc7f71 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -97,6 +97,12 @@ public interface WindowManagerPolicyConstants {
*/
String EXTRA_START_REASON = "android.intent.extra.EXTRA_START_REASON";
+ /**
+ * Set to {@code true} when intent was invoked from pressing one of the brightness keys.
+ * @hide
+ */
+ String EXTRA_FROM_BRIGHTNESS_KEY = "android.intent.extra.FROM_BRIGHTNESS_KEY";
+
// TODO: move this to a more appropriate place.
interface PointerEventListener {
/**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 96bfb2d9e1e6eeb5ece22fafd34b548ff6090c8c..7d3d283a45f2e34aa3f89033c6531c35ab43b638 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -49,7 +49,8 @@ public class WindowlessWindowManager implements IWindowSession {
private class State {
SurfaceControl mSurfaceControl;
- WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ final WindowManager.LayoutParams mLastReportedParams = new WindowManager.LayoutParams();
int mDisplayId;
IBinder mInputChannelToken;
Region mInputRegion;
@@ -94,6 +95,8 @@ public class WindowlessWindowManager implements IWindowSession {
private final MergedConfiguration mTmpConfig = new MergedConfiguration();
private final WindowlessWindowLayout mLayout = new WindowlessWindowLayout();
+ private ISurfaceControlViewHostParent mParentInterface;
+
public WindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
IBinder hostInputToken) {
mRootSurface = rootSurface;
@@ -244,6 +247,7 @@ public class WindowlessWindowManager implements IWindowSession {
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
+ sendLayoutParamsToParent();
// Include whether the window is in touch mode.
return isInTouchModeInternal(displayId) ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE
: res;
@@ -425,6 +429,7 @@ public class WindowlessWindowManager implements IWindowSession {
outInsetsState.set(mInsetsState);
}
+ sendLayoutParamsToParent();
return 0;
}
@@ -645,4 +650,45 @@ public class WindowlessWindowManager implements IWindowSession {
" we shouldn't get here!");
return false;
}
+
+ void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+ IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
+ IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
+ // If the parent interface has changed, it needs to clear the last reported params so it
+ // will update the new interface with the params.
+ if (oldInterface != newInterface) {
+ clearLastReportedParams();
+ }
+ mParentInterface = parentInterface;
+ sendLayoutParamsToParent();
+ }
+
+ private void clearLastReportedParams() {
+ WindowManager.LayoutParams emptyParam = new WindowManager.LayoutParams();
+ for (State windowInfo : mStateForWindow.values()) {
+ windowInfo.mLastReportedParams.copyFrom(emptyParam);
+ }
+ }
+
+ private void sendLayoutParamsToParent() {
+ if (mParentInterface == null) {
+ return;
+ }
+ WindowManager.LayoutParams[] params =
+ new WindowManager.LayoutParams[mStateForWindow.size()];
+ int index = 0;
+ boolean hasChanges = false;
+ for (State windowInfo : mStateForWindow.values()) {
+ int changes = windowInfo.mLastReportedParams.copyFrom(windowInfo.mParams);
+ hasChanges |= (changes != 0);
+ params[index++] = windowInfo.mParams;
+ }
+
+ if (hasChanges) {
+ try {
+ mParentInterface.updateParams(params);
+ } catch (RemoteException e) {
+ }
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 8a30f8cebc3d02f94a05732caf9d629260be817b..a11c6d0ce956228b8c93cd6a795eec9c7439e3db 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -115,4 +115,13 @@ oneway interface IWindowMagnificationConnection {
* @param callback the interface to be called.
*/
void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+
+ /**
+ * Notify System UI the magnification scale on the specified display for userId is changed.
+ *
+ * @param userId the user id.
+ * @param displayId the logical display id.
+ * @param scale magnification scale.
+ */
+ void onUserMagnificationScaleChanged(int userId, int displayId, float scale);
}
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
index adfeb6d110083de8a698d6bbd1ef3786409ab878..21b433465a3a711ef65d610c2019d30ddaed065e 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -57,8 +57,9 @@ import android.graphics.Rect;
*
* @param displayId The logical display id.
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
+ * @param updatePersistence whether the new scale should be persisted in Settings
*/
- void onPerformScaleAction(int displayId, float scale);
+ void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
/**
* Called when the accessibility action is performed.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 739c1bfccd3bf151d641a708d235ef498897a91a..a7fbaf63eaede3c5bc8d256b0646e85767e16450 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1436,7 +1436,7 @@ public final class AutofillManager {
}
for (int i = 0; i < infos.size(); i++) {
final VirtualViewFillInfo info = infos.valueAt(i);
- final int virtualId = infos.indexOfKey(i);
+ final int virtualId = infos.keyAt(i);
notifyViewReadyInner(getAutofillId(view, virtualId),
(info == null) ? null : info.getAutofillHints());
}
@@ -1450,9 +1450,6 @@ public final class AutofillManager {
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
- if (sDebug) {
- Log.d(TAG, "notifyViewEnteredForFillDialog:" + v.getAutofillId());
- }
if (v.isCredential()
&& mIsFillAndSaveDialogDisabledForCredentialManager) {
if (sDebug) {
@@ -1465,11 +1462,14 @@ public final class AutofillManager {
notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
}
- private void notifyViewReadyInner(AutofillId id, String[] autofillHints) {
+ private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints) {
+ if (sDebug) {
+ Log.d(TAG, "notifyViewReadyInner:" + id);
+ }
+
if (!hasAutofillFeature()) {
return;
}
-
synchronized (mLock) {
if (mAllTrackedViews.contains(id)) {
// The id is tracked and will not trigger pre-fill request again.
@@ -1505,26 +1505,38 @@ public final class AutofillManager {
final boolean clientAdded = tryAddServiceClientIfNeededLocked();
if (clientAdded) {
startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null,
- /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
+ /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
} else {
if (sVerbose) {
Log.v(TAG, "not starting session: no service client");
}
}
-
}
}
}
- if (mIsFillDialogEnabled
- || ArrayUtils.containsAny(autofillHints, mFillDialogEnabledHints)) {
+ // Check if framework should send pre-fill request for fill dialog
+ boolean shouldSendPreFillRequestForFillDialog = false;
+ if (mIsFillDialogEnabled) {
+ shouldSendPreFillRequestForFillDialog = true;
+ } else if (autofillHints != null) {
+ // check if supported autofill hint is present
+ for (String autofillHint : autofillHints) {
+ for (String filldialogEnabledHint : mFillDialogEnabledHints) {
+ if (filldialogEnabledHint.equalsIgnoreCase(autofillHint)) {
+ shouldSendPreFillRequestForFillDialog = true;
+ break;
+ }
+ }
+ if (shouldSendPreFillRequestForFillDialog) break;
+ }
+ }
+ if (shouldSendPreFillRequestForFillDialog) {
if (sDebug) {
Log.d(TAG, "Triggering pre-emptive request for fill dialog.");
}
-
int flags = FLAG_SUPPORTS_FILL_DIALOG;
flags |= FLAG_VIEW_NOT_FOCUSED;
-
synchronized (mLock) {
// To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill
// request, because IME will reset the id of IME served view to 0 when activity
@@ -1532,9 +1544,10 @@ public final class AutofillManager {
// not match the IME served view's, Autofill will be blocking to wait inline
// request from the IME.
notifyViewEnteredLocked(/* view= */ null, AutofillId.NO_AUTOFILL_ID,
- /* bounds= */ null, /* value= */ null, flags);
+ /* bounds= */ null, /* value= */ null, flags);
}
}
+ return;
}
private boolean hasFillDialogUiFeature() {
@@ -4213,6 +4226,14 @@ public final class AutofillManager {
}
}
+ @Override
+ public void requestHideFillUiWhenDestroyed(int sessionId, AutofillId id) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.requestHideFillUi(id, true));
+ }
+ }
+
@Override
public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
final AutofillManager afm = mAfm.get();
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 51afe4cf784d0de3f80a3b7c6158d175608a8096..917a974f992df8d25d40a6d68a3482e94f03187c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -78,6 +78,11 @@ oneway interface IAutoFillManagerClient {
*/
void requestHideFillUi(int sessionId, in AutofillId id);
+ /**
+ * Requests hiding the fill UI when it's destroyed
+ */
+ void requestHideFillUiWhenDestroyed(int sessionId, in AutofillId id);
+
/**
* Notifies no fill UI will be shown, and also mark the state as finished if necessary (if
* sessionFinishedState != 0).
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 668351b949c1b5def308c1287f7fcf00f0eebf4e..2c7d326587c7f0bf5795ca40cb5cea43baf897db 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
@@ -352,6 +353,30 @@ public final class ContentCaptureManager {
public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
"disable_flush_for_view_tree_appearing";
+ /**
+ * Enables the content protection receiver.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
+ "enable_content_protection_receiver";
+
+ /**
+ * Sets the size of the app blocklist for the content protection flow.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
+ "content_protection_apps_blocklist_size";
+
+ /**
+ * Sets the size of the in-memory ring buffer for the content protection flow.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
+ "content_protection_buffer_size";
+
/** @hide */
@TestApi
public static final int LOGGING_LEVEL_OFF = 0;
@@ -384,6 +409,14 @@ public final class ContentCaptureManager {
public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
/** @hide */
public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
+ /** @hide */
+ public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
+ /** @hide */
+ public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
+ /** @hide */
+ public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
+ /** @hide */
+ public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
private final Object mLock = new Object();
@@ -414,6 +447,9 @@ public final class ContentCaptureManager {
@Nullable // set on-demand by addDumpable()
private Dumper mDumpable;
+ // Created here in order to live across activity and session changes
+ @Nullable private final RingBuffer mContentProtectionEventBuffer;
+
/** @hide */
public interface ContentCaptureClient {
/**
@@ -424,12 +460,15 @@ public final class ContentCaptureManager {
}
/** @hide */
- static class StrippedContext {
- final String mPackageName;
- final String mContext;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static class StrippedContext {
+ @NonNull final String mPackageName;
+ @NonNull final String mContext;
final @UserIdInt int mUserId;
- private StrippedContext(Context context) {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public StrippedContext(@NonNull Context context) {
mPackageName = context.getPackageName();
mContext = context.toString();
mUserId = context.getUserId();
@@ -440,6 +479,7 @@ public final class ContentCaptureManager {
return mContext;
}
+ @NonNull
public String getPackageName() {
return mPackageName;
}
@@ -469,6 +509,16 @@ public final class ContentCaptureManager {
mHandler = Handler.createAsync(Looper.getMainLooper());
mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+ if (mOptions.contentProtectionOptions.enableReceiver
+ && mOptions.contentProtectionOptions.bufferSize > 0) {
+ mContentProtectionEventBuffer =
+ new RingBuffer(
+ ContentCaptureEvent.class,
+ mOptions.contentProtectionOptions.bufferSize);
+ } else {
+ mContentProtectionEventBuffer = null;
+ }
}
/**
@@ -837,6 +887,13 @@ public final class ContentCaptureManager {
activity.addDumpable(mDumpable);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Nullable
+ public RingBuffer getContentProtectionEventBuffer() {
+ return mContentProtectionEventBuffer;
+ }
+
// NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
private final class Dumper implements Dumpable {
@Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 62044aa78213d3d364f7b1612c5929858191ddf2..dc3d32317ded3362040eb0cc0486bd3441e9c559 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -191,20 +191,22 @@ public abstract class ContentCaptureSession implements AutoCloseable {
static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L;
/** @hide */
- @IntDef(prefix = { "FLUSH_REASON_" }, value = {
- FLUSH_REASON_FULL,
- FLUSH_REASON_VIEW_ROOT_ENTERED,
- FLUSH_REASON_SESSION_STARTED,
- FLUSH_REASON_SESSION_FINISHED,
- FLUSH_REASON_IDLE_TIMEOUT,
- FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
- FLUSH_REASON_SESSION_CONNECTED,
- FLUSH_REASON_FORCE_FLUSH,
- FLUSH_REASON_VIEW_TREE_APPEARING,
- FLUSH_REASON_VIEW_TREE_APPEARED
- })
+ @IntDef(
+ prefix = {"FLUSH_REASON_"},
+ value = {
+ FLUSH_REASON_FULL,
+ FLUSH_REASON_VIEW_ROOT_ENTERED,
+ FLUSH_REASON_SESSION_STARTED,
+ FLUSH_REASON_SESSION_FINISHED,
+ FLUSH_REASON_IDLE_TIMEOUT,
+ FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+ FLUSH_REASON_SESSION_CONNECTED,
+ FLUSH_REASON_FORCE_FLUSH,
+ FLUSH_REASON_VIEW_TREE_APPEARING,
+ FLUSH_REASON_VIEW_TREE_APPEARED
+ })
@Retention(RetentionPolicy.SOURCE)
- public @interface FlushReason{}
+ public @interface FlushReason {}
private final Object mLock = new Object();
@@ -686,7 +688,7 @@ public abstract class ContentCaptureSession implements AutoCloseable {
case FLUSH_REASON_VIEW_TREE_APPEARED:
return "VIEW_TREE_APPEARED";
default:
- return "UNKOWN-" + reason;
+ return "UNKNOWN-" + reason;
}
}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index a64111069c9b9ee0fced6b4a5e5ac68a94e43d28..14879977d2a5006ef179bfb9812b32443bf7997f 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -17,6 +17,7 @@
package android.view.contentcapture;
import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.DataRemovalRequest;
@@ -108,4 +109,9 @@ oneway interface IContentCaptureManager {
*/
void registerContentCaptureOptionsCallback(String packageName,
in IContentCaptureOptionsCallback callback);
+
+ /**
+ * Notifies the system server that a login was detected.
+ */
+ void onLoginDetected(in ParceledListSlice events);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index efd50e7d2343fcc1164dc87a8d42f2ec6c14ee29..2241fd5dc37abe5754454bcb952e7e4fa5b650a1 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -52,8 +52,10 @@ import android.util.Log;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentprotection.ContentProtectionEventProcessor;
import android.view.inputmethod.BaseInputConnection;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import java.io.PrintWriter;
@@ -118,9 +120,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
/**
* Direct interface to the service binder object - it's used to send the events, including the
* last ones (when the session is finished)
+ *
+ * @hide
*/
- @NonNull
- private IContentCaptureDirectManager mDirectServiceInterface;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public IContentCaptureDirectManager mDirectServiceInterface;
+
@Nullable
private DeathRecipient mDirectServiceVulture;
@@ -131,14 +137,19 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@Nullable
private IBinder mShareableActivityToken;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
- private ComponentName mComponentName;
+ public ComponentName mComponentName;
/**
* List of events held to be sent as a batch.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
- private ArrayList mEvents;
+ public ArrayList mEvents;
// Used just for debugging purposes (on dump)
private long mNextFlush;
@@ -157,6 +168,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@NonNull
private final SessionStateReceiver mSessionStateReceiver;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
private static class SessionStateReceiver extends IResultReceiver.Stub {
private final WeakReference mMainSession;
@@ -194,8 +210,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
- protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
- @NonNull ContentCaptureManager manager, @NonNull Handler handler,
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public MainContentCaptureSession(
+ @NonNull ContentCaptureManager.StrippedContext context,
+ @NonNull ContentCaptureManager manager,
+ @NonNull Handler handler,
@NonNull IContentCaptureManager systemServerInterface) {
mContext = context;
mManager = manager;
@@ -273,15 +293,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
/**
- * Callback from {@code system_server} after call to
- * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
- * IResultReceiver)}.
+ * Callback from {@code system_server} after call to {@link
+ * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
*
* @param resultCode session state
* @param binder handle to {@code IContentCaptureDirectManager}
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+ public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
if (binder != null) {
mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
mDirectServiceVulture = () -> {
@@ -296,6 +317,20 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
+ // Should not be possible for mComponentName to be null here but check anyway
+ if (mManager.mOptions.contentProtectionOptions.enableReceiver
+ && mManager.getContentProtectionEventBuffer() != null
+ && mComponentName != null) {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mManager.getContentProtectionEventBuffer(),
+ mHandler,
+ mSystemServerInterface,
+ mComponentName.getPackageName());
+ } else {
+ mContentProtectionEventProcessor = null;
+ }
+
if ((resultCode & STATE_DISABLED) != 0) {
resetSession(resultCode);
} else {
@@ -311,8 +346,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void sendEvent(@NonNull ContentCaptureEvent event) {
+ public void sendEvent(@NonNull ContentCaptureEvent event) {
sendEvent(event, /* forceFlush= */ false);
}
@@ -337,6 +374,25 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
return;
}
+
+ if (isContentProtectionReceiverEnabled()) {
+ sendContentProtectionEvent(event);
+ }
+ if (isContentCaptureReceiverEnabled()) {
+ sendContentCaptureEvent(event, forceFlush);
+ }
+ }
+
+ @UiThread
+ private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+ if (mContentProtectionEventProcessor != null) {
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+ }
+
+ @UiThread
+ private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ final int eventType = event.getType();
final int maxBufferSize = mManager.mOptions.maxBufferSize;
if (mEvents == null) {
if (sVerbose) {
@@ -528,9 +584,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
flush(reason);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
@UiThread
- void flush(@FlushReason int reason) {
+ public void flush(@FlushReason int reason) {
if (mEvents == null || mEvents.size() == 0) {
if (sVerbose) {
Log.v(TAG, "Don't flush for empty event buffer.");
@@ -544,6 +602,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
return;
}
+ if (!isContentCaptureReceiverEnabled()) {
+ return;
+ }
+
if (mDirectServiceInterface == null) {
if (sVerbose) {
Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
@@ -607,8 +669,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
return new ParceledListSlice<>(events);
}
+ /** hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void destroySession() {
+ public void destroySession() {
if (sDebug) {
Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -626,12 +690,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
}
mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
}
// TODO(b/122454205): once we support multiple sessions, we might need to move some of these
// clearings out.
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void resetSession(int newState) {
+ public void resetSession(int newState) {
if (sVerbose) {
Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+ getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -651,6 +718,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
mHandler.removeMessages(MSG_FLUSH);
}
@@ -878,4 +946,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
private String getDebugState(@FlushReason int reason) {
return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
}
+
+ @UiThread
+ private boolean isContentProtectionReceiverEnabled() {
+ return mManager.mOptions.contentProtectionOptions.enableReceiver;
+ }
+
+ @UiThread
+ private boolean isContentCaptureReceiverEnabled() {
+ return mManager.mOptions.enableReceiver;
+ }
}
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 044a31f3b297ce76e34efdcf57199708f7c0063c..f218995e55ad1f5a7ce30ed5acbcc8000238fe0f 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -480,6 +480,11 @@ public final class ViewNode extends AssistStructure.ViewNode {
return mLocaleList;
}
+ /** @hide */
+ public void setTextIdEntry(@NonNull String textIdEntry) {
+ mTextIdEntry = textIdEntry;
+ }
+
private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
long nodeFlags = mFlags;
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..b44abf3eb04d20eee5e086bf1091f7d5b9562844
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+ private static final String TAG = "ContentProtectionEventProcessor";
+
+ private static final List PASSWORD_FIELD_INPUT_TYPES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+ private static final List PASSWORD_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+ private static final List ADDITIONAL_SUSPICIOUS_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+ private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+ private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+ private static final Set EVENT_TYPES_TO_STORE =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ ContentCaptureEvent.TYPE_VIEW_APPEARED,
+ ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+ private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
+ @NonNull private final RingBuffer mEventBuffer;
+
+ @NonNull private final Handler mHandler;
+
+ @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+ @NonNull private final String mPackageName;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mPasswordFieldDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mSuspiciousTextDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public Instant mLastFlushTime;
+
+ private int mResetLoginRemainingEventsToProcess;
+
+ public ContentProtectionEventProcessor(
+ @NonNull RingBuffer eventBuffer,
+ @NonNull Handler handler,
+ @NonNull IContentCaptureManager contentCaptureManager,
+ @NonNull String packageName) {
+ mEventBuffer = eventBuffer;
+ mHandler = handler;
+ mContentCaptureManager = contentCaptureManager;
+ mPackageName = packageName;
+ }
+
+ /** Main entry point for {@link ContentCaptureEvent} processing. */
+ @UiThread
+ public void processEvent(@NonNull ContentCaptureEvent event) {
+ if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+ storeEvent(event);
+ }
+ if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+ processViewAppearedEvent(event);
+ }
+ }
+
+ @UiThread
+ private void storeEvent(@NonNull ContentCaptureEvent event) {
+ // Ensure receiver gets the package name which might not be set
+ ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
+ viewNode.setTextIdEntry(mPackageName);
+ event.setViewNode(viewNode);
+ mEventBuffer.append(event);
+ }
+
+ @UiThread
+ private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+ mPasswordFieldDetected |= isPasswordField(event);
+ mSuspiciousTextDetected |= isSuspiciousText(event);
+ if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+ loginDetected();
+ } else {
+ maybeResetLoginFlags();
+ }
+ }
+
+ @UiThread
+ private void loginDetected() {
+ if (mLastFlushTime == null
+ || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+ flush();
+ }
+ resetLoginFlags();
+ }
+
+ @UiThread
+ private void resetLoginFlags() {
+ mPasswordFieldDetected = false;
+ mSuspiciousTextDetected = false;
+ mResetLoginRemainingEventsToProcess = 0;
+ }
+
+ @UiThread
+ private void maybeResetLoginFlags() {
+ if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+ if (mResetLoginRemainingEventsToProcess <= 0) {
+ mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
+ } else {
+ mResetLoginRemainingEventsToProcess--;
+ if (mResetLoginRemainingEventsToProcess <= 0) {
+ resetLoginFlags();
+ }
+ }
+ }
+ }
+
+ @UiThread
+ private void flush() {
+ mLastFlushTime = Instant.now();
+
+ // Note the thread annotations, do not move clearEvents to mHandler
+ ParceledListSlice events = clearEvents();
+ mHandler.post(() -> handlerOnLoginDetected(events));
+ }
+
+ @UiThread
+ @NonNull
+ private ParceledListSlice clearEvents() {
+ List events = Arrays.asList(mEventBuffer.toArray());
+ mEventBuffer.clear();
+ return new ParceledListSlice<>(events);
+ }
+
+ private void handlerOnLoginDetected(@NonNull ParceledListSlice events) {
+ try {
+ mContentCaptureManager.onLoginDetected(events);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+ }
+ }
+
+ private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+ return isPasswordField(event.getViewNode());
+ }
+
+ private boolean isPasswordField(@Nullable ViewNode viewNode) {
+ if (viewNode == null) {
+ return false;
+ }
+ return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+ }
+
+ private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+ if (!isAndroidViewNode(viewNode)) {
+ return false;
+ }
+ int inputType = viewNode.getInputType();
+ return PASSWORD_FIELD_INPUT_TYPES.stream()
+ .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+ }
+
+ private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+ if (viewNode.getClassName() != null) {
+ return false;
+ }
+ return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+ }
+
+ private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+ String className = viewNode.getClassName();
+ return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+ }
+
+ private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+ return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+ || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+ }
+
+ private boolean isSuspiciousText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ if (isPasswordText(text)) {
+ return true;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+ .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+ }
+
+ private boolean isPasswordText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return PASSWORD_TEXTS.stream()
+ .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+ }
+}
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..9abf6f10d05daee64e6fe79818cbaeef55b13e6e
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+
+/**
+ * Utilities for reading data from {@link ContentCaptureEvent} and {@link ViewNode}.
+ *
+ * @hide
+ */
+public final class ContentProtectionUtils {
+
+ /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+ @Nullable
+ public static String getEventText(@NonNull ContentCaptureEvent event) {
+ CharSequence text = event.getText();
+ if (text == null) {
+ return null;
+ }
+ return text.toString();
+ }
+
+ /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+ @Nullable
+ public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
+ ViewNode viewNode = event.getViewNode();
+ if (viewNode == null) {
+ return null;
+ }
+ return getViewNodeText(viewNode);
+ }
+
+ /** Returns the text extracted directly from the {@link ViewNode}, if set. */
+ @Nullable
+ public static String getViewNodeText(@NonNull ViewNode viewNode) {
+ CharSequence text = viewNode.getText();
+ if (text == null) {
+ return null;
+ }
+ return text.toString();
+ }
+}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index ce2c18080b910a5e01630da0003040ba67f8c654..467daa028afdff6afcc089c33283bec22fb7b850 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -295,8 +295,8 @@ final class IInputMethodManagerGlobalInvoker {
@AnyThread
static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
- @Nullable ResultReceiver resultReceiver,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ int lastClickToolType, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -312,7 +312,7 @@ final class IInputMethodManagerGlobalInvoker {
@AnyThread
static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- @Nullable ImeTracker.Token statsToken, int flags,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
@Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final IInputMethodManager service = getService();
if (service == null) {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f0d1019ffb067c4c5b14d3bfae0a53ab62722f9c..03d1cd8f89e355a7b7ff2460c6d2d41854aff3ce 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,8 +16,12 @@
package android.view.inputmethod;
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
+
import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
@@ -696,20 +700,22 @@ public interface ImeTracker {
/**
* Called when the animation, which is going to be monitored, starts.
*
- * @param jankContext context which is needed by {@link InteractionJankMonitor}
- * @param animType {@link AnimationType}
+ * @param jankContext context which is needed by {@link InteractionJankMonitor}.
+ * @param animType the animation type.
* @param useSeparatedThread {@code true} if the animation is handled by the app,
* {@code false} if the animation will be scheduled on the
- * {@link android.view.InsetsAnimationThread}
+ * {@link android.view.InsetsAnimationThread}.
*/
public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
@AnimationType int animType, boolean useSeparatedThread) {
+ final int cujType = getImeInsetsCujFromAnimation(animType);
if (jankContext.getDisplayContext() == null
- || jankContext.getTargetSurfaceControl() == null) {
+ || jankContext.getTargetSurfaceControl() == null
+ || cujType == -1) {
return;
}
final Configuration.Builder builder = Configuration.Builder.withSurface(
- CUJ_IME_INSETS_ANIMATION,
+ cujType,
jankContext.getDisplayContext(),
jankContext.getTargetSurfaceControl())
.setTag(String.format(Locale.US, "%d@%d@%s", animType,
@@ -719,16 +725,44 @@ public interface ImeTracker {
/**
* Called when the animation, which is going to be monitored, cancels.
+ *
+ * @param animType the animation type.
*/
- public void onCancelAnimation() {
- InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
+ public void onCancelAnimation(@AnimationType int animType) {
+ final int cujType = getImeInsetsCujFromAnimation(animType);
+ if (cujType == -1) {
+ InteractionJankMonitor.getInstance().cancel(cujType);
+ }
}
/**
* Called when the animation, which is going to be monitored, ends.
+ *
+ * @param animType the animation type.
*/
- public void onFinishAnimation() {
- InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
+ public void onFinishAnimation(@AnimationType int animType) {
+ final int cujType = getImeInsetsCujFromAnimation(animType);
+ if (cujType != -1) {
+ InteractionJankMonitor.getInstance().end(cujType);
+ }
+ }
+
+ /**
+ * A helper method to translate animation type to CUJ type for IME animations.
+ *
+ * @param animType the animation type.
+ * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+ * or {@code -1} if the animation type is not supported for tracking yet.
+ */
+ private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
+ switch (animType) {
+ case ANIMATION_TYPE_SHOW:
+ return CUJ_IME_INSETS_SHOW_ANIMATION;
+ case ANIMATION_TYPE_HIDE:
+ return CUJ_IME_INSETS_HIDE_ANIMATION;
+ default:
+ return -1;
+ }
}
}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 92380ed7a7bcaee092c470abc831feab7fcda980..9340f46b257ff67d58127da1c45b31626aea5c68 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -17,6 +17,7 @@
package android.view.inputmethod;
import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,6 +37,8 @@ import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -269,6 +272,14 @@ public interface InputMethod {
*/
@MainThread
public void revokeSession(InputMethodSession session);
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+ SHOW_EXPLICIT,
+ SHOW_FORCED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ShowFlags {}
/**
* Flag for {@link #showSoftInput}: this show has been explicitly
@@ -288,8 +299,6 @@ public interface InputMethod {
/**
* Request that any soft input part of the input method be shown to the user.
*
- * @param flags Provides additional information about the show request.
- * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
* @param resultReceiver The client requesting the show may wish to
* be told the impact of their request, which should be supplied here.
* The result code should be
@@ -304,7 +313,7 @@ public interface InputMethod {
* @hide
*/
@MainThread
- public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+ public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver,
IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
showSoftInput(flags, resultReceiver);
}
@@ -312,8 +321,6 @@ public interface InputMethod {
/**
* Request that any soft input part of the input method be shown to the user.
*
- * @param flags Provides additional information about the show request.
- * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
* @param resultReceiver The client requesting the show may wish to
* be told the impact of their request, which should be supplied here.
* The result code should be
@@ -323,11 +330,12 @@ public interface InputMethod {
* {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
@MainThread
- public void showSoftInput(int flags, ResultReceiver resultReceiver);
+ public void showSoftInput(@ShowFlags int flags, ResultReceiver resultReceiver);
/**
* Request that any soft input part of the input method be hidden from the user.
- * @param flags Provides additional information about the show request.
+ *
+ * @param flags Provides additional information about the hide request.
* Currently always 0.
* @param resultReceiver The client requesting the show may wish to
* be told the impact of their request, which should be supplied here.
@@ -350,7 +358,8 @@ public interface InputMethod {
/**
* Request that any soft input part of the input method be hidden from the user.
- * @param flags Provides additional information about the show request.
+ *
+ * @param flags Provides additional information about the hide request.
* Currently always 0.
* @param resultReceiver The client requesting the show may wish to
* be told the impact of their request, which should be supplied here.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3cac1e5f7d6eb8c51bf9d72dd3033fc3f9df3356..31abac8550909059351cf0fcb9a50deb477966f5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@ import android.Manifest;
import android.annotation.DisplayContext;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
@@ -50,6 +51,7 @@ import android.annotation.TestApi;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
+import android.app.PropertyInvalidatedCache;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -121,6 +123,8 @@ import com.android.internal.view.IInputMethodManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
@@ -536,6 +540,13 @@ public final class InputMethodManager {
@UnsupportedAppUsage
Rect mCursorRect = new Rect();
+ /** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
+ @GuardedBy("mH")
+ private PropertyInvalidatedCache mStylusHandwritingAvailableCache;
+
+ private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
+ "cache_key.system_server.stylus_handwriting";
+
@GuardedBy("mH")
private int mCursorSelStart;
@GuardedBy("mH")
@@ -662,6 +673,15 @@ public final class InputMethodManager {
private static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
private static final int MSG_ON_SHOW_REQUESTED = 31;
+ /**
+ * Calling this will invalidate Local stylus handwriting availability Cache which
+ * forces the next query in any process to recompute the cache.
+ * @hide
+ */
+ public static void invalidateLocalStylusHandwritingAvailabilityCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
+ }
+
private static boolean isAutofillUIShowing(View servedView) {
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
return afm != null && afm.isAutofillUiShowing();
@@ -1577,8 +1597,21 @@ public final class InputMethodManager {
if (fallbackContext == null) {
return false;
}
-
- return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
+ boolean isAvailable;
+ synchronized (mH) {
+ if (mStylusHandwritingAvailableCache == null) {
+ mStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
+ 4 /* maxEntries */, CACHE_KEY_STYLUS_HANDWRITING_PROPERTY) {
+ @Override
+ public Boolean recompute(Integer userId) {
+ return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
+ userId);
+ }
+ };
+ }
+ isAvailable = mStylusHandwritingAvailableCache.query(userId);
+ }
+ return isAvailable;
}
/**
@@ -2004,6 +2037,14 @@ public final class InputMethodManager {
}
}
+ /** @hide */
+ @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+ SHOW_IMPLICIT,
+ SHOW_FORCED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShowFlags {}
+
/**
* Flag for {@link #showSoftInput} to indicate that this is an implicit
* request to show the input window, not as the result of a direct request
@@ -2035,10 +2076,8 @@ public final class InputMethodManager {
* {@link View#isFocused view focus}, and its containing window has
* {@link View#hasWindowFocus window focus}. Otherwise the call fails and
* returns {@code false}.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #SHOW_IMPLICIT} bit set.
*/
- public boolean showSoftInput(View view, int flags) {
+ public boolean showSoftInput(View view, @ShowFlags int flags) {
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
@@ -2101,21 +2140,20 @@ public final class InputMethodManager {
* {@link View#isFocused view focus}, and its containing window has
* {@link View#hasWindowFocus window focus}. Otherwise the call fails and
* returns {@code false}.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #SHOW_IMPLICIT} bit set.
* @param resultReceiver If non-null, this will be called by the IME when
* it has processed your request to tell you what it has done. The result
* code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
* {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
* {@link #RESULT_HIDDEN}.
*/
- public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+ public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
}
- private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken,
+ @ShowFlags int flags, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
if (statsToken == null) {
statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
@@ -2127,7 +2165,7 @@ public final class InputMethodManager {
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
- return fallbackImm.showSoftInput(view, flags, resultReceiver);
+ return fallbackImm.showSoftInput(view, statsToken, flags, resultReceiver, reason);
}
checkFocus();
@@ -2169,7 +2207,7 @@ public final class InputMethodManager {
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
- public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+ public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
@@ -2200,6 +2238,14 @@ public final class InputMethodManager {
}
}
+ /** @hide */
+ @IntDef(flag = true, prefix = { "HIDE_" }, value = {
+ HIDE_IMPLICIT_ONLY,
+ HIDE_NOT_ALWAYS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HideFlags {}
+
/**
* Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
* to indicate that the soft input window should only be hidden if it was not explicitly shown
@@ -2221,10 +2267,8 @@ public final class InputMethodManager {
*
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
*/
- public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+ public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
return hideSoftInputFromWindow(windowToken, flags, null);
}
@@ -2246,21 +2290,19 @@ public final class InputMethodManager {
*
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
* @param resultReceiver If non-null, this will be called by the IME when
* it has processed your request to tell you what it has done. The result
* code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
* {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
* {@link #RESULT_HIDDEN}.
*/
- public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+ public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
ResultReceiver resultReceiver) {
return hideSoftInputFromWindow(windowToken, flags, resultReceiver,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
}
- private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+ private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
null /* component */, Process.myUid(),
@@ -2463,12 +2505,6 @@ public final class InputMethodManager {
* If not the input window will be displayed.
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
- * @param showFlags Provides additional operating flags. May be
- * 0 or have the {@link #SHOW_IMPLICIT},
- * {@link #SHOW_FORCED} bit set.
- * @param hideFlags Provides additional operating flags. May be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
- * {@link #HIDE_NOT_ALWAYS} bit set.
*
* @deprecated Use {@link #showSoftInput(View, int)} or
* {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2477,7 +2513,8 @@ public final class InputMethodManager {
* has an effect if the calling app is the current IME focus.
*/
@Deprecated
- public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+ public void toggleSoftInputFromWindow(IBinder windowToken, @ShowFlags int showFlags,
+ @HideFlags int hideFlags) {
ImeTracing.getInstance().triggerClientDump(
"InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this,
null /* icProto */);
@@ -2495,12 +2532,6 @@ public final class InputMethodManager {
*
* If the input window is already displayed, it gets hidden.
* If not the input window will be displayed.
- * @param showFlags Provides additional operating flags. May be
- * 0 or have the {@link #SHOW_IMPLICIT},
- * {@link #SHOW_FORCED} bit set.
- * @param hideFlags Provides additional operating flags. May be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
- * {@link #HIDE_NOT_ALWAYS} bit set.
*
* @deprecated Use {@link #showSoftInput(View, int)} or
* {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2509,7 +2540,7 @@ public final class InputMethodManager {
* has an effect if the calling app is the current IME focus.
*/
@Deprecated
- public void toggleSoftInput(int showFlags, int hideFlags) {
+ public void toggleSoftInput(@ShowFlags int showFlags, @HideFlags int hideFlags) {
ImeTracing.getInstance().triggerClientDump(
"InputMethodManager#toggleSoftInput", InputMethodManager.this,
null /* icProto */);
@@ -3522,15 +3553,12 @@ public final class InputMethodManager {
* @param token Supplies the identifying token given to an input method
* when it was started, which allows it to perform this operation on
* itself.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
- * {@link #HIDE_NOT_ALWAYS} bit set.
* @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
* intended for IME developers who should be accessing APIs through the service. APIs in this
* class are intended for app developers interacting with the IME.
*/
@Deprecated
- public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+ public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) {
InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(
flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION);
}
@@ -3544,15 +3572,12 @@ public final class InputMethodManager {
* @param token Supplies the identifying token given to an input method
* when it was started, which allows it to perform this operation on
* itself.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #SHOW_IMPLICIT} or
- * {@link #SHOW_FORCED} bit set.
* @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
* intended for IME developers who should be accessing APIs through the service. APIs in this
* class are intended for app developers interacting with the IME.
*/
@Deprecated
- public void showSoftInputFromInputMethod(IBinder token, int flags) {
+ public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) {
InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags);
}
@@ -4361,15 +4386,14 @@ public final class InputMethodManager {
* @param icProto {@link InputConnection} call data in proto format.
* @hide
*/
- @GuardedBy("mH")
public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
- if (!isImeSessionAvailableLocked()) {
- return;
- }
-
- proto.write(DISPLAY_ID, mDisplayId);
- final long token = proto.start(INPUT_METHOD_MANAGER);
synchronized (mH) {
+ if (!isImeSessionAvailableLocked()) {
+ return;
+ }
+
+ proto.write(DISPLAY_ID, mDisplayId);
+ final long token = proto.start(INPUT_METHOD_MANAGER);
proto.write(CUR_ID, mCurBindState.mImeId);
proto.write(FULLSCREEN_MODE, mFullscreenMode);
proto.write(ACTIVE, mActive);
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index af6af14472db9cb072d1efe66bcc5eac07b9db29..4f48cb684e8cccefbd0346d843de6dab6d33f7f0 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -169,12 +169,6 @@ public interface InputMethodSession {
/**
* Toggle the soft input window.
* Applications can toggle the state of the soft input window.
- * @param showFlags Provides additional operating flags. May be
- * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
- * {@link InputMethodManager#SHOW_FORCED} bit set.
- * @param hideFlags Provides additional operating flags. May be
- * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
- * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
*
* @deprecated Starting in {@link android.os.Build.VERSION_CODES#S} the system no longer invokes
* this method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
@@ -182,7 +176,8 @@ public interface InputMethodSession {
* InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
*/
@Deprecated
- public void toggleSoftInput(int showFlags, int hideFlags);
+ public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+ @InputMethodManager.HideFlags int hideFlags);
/**
* This method is called when the cursor and/or the character position relevant to text input
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index aa9225b79e415d2f7db5cdbb16b2b88b00e16580..7694754de6ff1eab73769b96689431612ab3701c 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,7 +28,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.UriGrantsManager;
+import android.content.ContentProvider;
+import android.content.Intent;
import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignalBeamer;
@@ -37,6 +42,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.KeyEvent;
@@ -182,6 +188,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
private CancellationSignalBeamer.Receiver mBeamer;
+ private ViewRootImpl.TypingHintNotifier mTypingHintNotifier;
+
RemoteInputConnectionImpl(@NonNull Looper looper,
@NonNull InputConnection inputConnection,
@NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
@@ -190,6 +198,12 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
mH = new Handler(mLooper);
mParentInputMethodManager = inputMethodManager;
mServedView = new WeakReference<>(servedView);
+ if (servedView != null) {
+ final ViewRootImpl viewRoot = servedView.getViewRootImpl();
+ if (viewRoot != null) {
+ mTypingHintNotifier = viewRoot.createTypingHintNotifierIfSupported();
+ }
+ }
}
/**
@@ -364,6 +378,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
return;
}
dispatch(() -> {
+ // Deactivate the notifier when finishing typing.
+ notifyTypingHint(false /* isTyping */, true /* deactivate */);
+
// Note that we do not need to worry about race condition here, because 1) mFinished is
// updated only inside this block, and 2) the code here is running on a Handler hence we
// assume multiple closeConnection() tasks will not be handled at the same time.
@@ -434,7 +451,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
if (mBeamer == null) {
return;
}
- mBeamer.forget(token);
+ dispatch(() -> {
+ mBeamer.forget(token);
+ });
}
@Override
@@ -628,6 +647,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
return;
}
ic.commitText(text, newCursorPosition);
+ notifyTypingHint(true /* isTyping */, false /* deactivate */);
});
}
@@ -783,6 +803,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
return;
}
ic.setComposingText(text, newCursorPosition);
+ notifyTypingHint(true /* isTyping */, false /* deactivate */);
});
}
@@ -910,6 +931,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
return;
}
ic.deleteSurroundingText(beforeLength, afterLength);
+ notifyTypingHint(true /* isTyping */, false /* deactivate */);
});
}
@@ -1118,7 +1140,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
final InputConnection ic = getInputConnection();
if (ic == null || !isActive()) {
- Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+ Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
return false;
}
if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
@@ -1179,7 +1201,22 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
public void commitContent(InputConnectionCommandHeader header,
InputContentInfo inputContentInfo, int flags, Bundle opts,
AndroidFuture future /* T=Boolean */) {
+ final int imeUid = Binder.getCallingUid();
dispatchWithTracing("commitContent", future, () -> {
+ // Check if the originator IME has the right permissions
+ try {
+ final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(
+ inputContentInfo.getContentUri(), UserHandle.getUserId(imeUid));
+ final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(
+ inputContentInfo.getContentUri());
+ UriGrantsManager.getService().checkGrantUriPermission_ignoreNonSystem(imeUid, null,
+ contentUriWithoutUserId, Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ contentUriOwnerUserId);
+ } catch (Exception e) {
+ Log.w(TAG, "commitContent with invalid Uri permission from IME:", e);
+ return false;
+ }
+
if (header.mSessionId != mCurrentSessionId.get()) {
return false; // cancelled
}
@@ -1473,4 +1510,15 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
private static boolean useImeTracing() {
return ImeTracing.getInstance().isEnabled();
}
+
+ /**
+ * Dispatch the typing hint to {@link ViewRootImpl.TypingHintNotifier}.
+ * The input connection indicates that the user is typing when {@link #commitText} or
+ * {@link #setComposingText)} and the user finish typing when {@link #deactivate()}.
+ */
+ private void notifyTypingHint(boolean isTyping, boolean deactivate) {
+ if (mTypingHintNotifier != null) {
+ mTypingHintNotifier.onTypingHintChanged(isTyping, deactivate);
+ }
+ }
}
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 05717dd5d9307948da9706f4ee22ccae25706922..7eee33f7f6176eba12a5a66db7116ab880ccac84 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -238,7 +238,10 @@ public final class TextAppearanceInfo implements Parcelable {
.setFontFeatureSettings(textPaint.getFontFeatureSettings())
.setFontVariationSettings(textPaint.getFontVariationSettings())
.setTextScaleX(textPaint.getTextScaleX())
- .setTextColor(textPaint.getColor())
+ // When there is a hint text (text length is 0), the text color should be the normal
+ // text color rather than hint text color.
+ .setTextColor(text.length() == 0
+ ? textView.getCurrentTextColor() : textPaint.getColor())
.setLinkTextColor(textPaint.linkColor)
.setAllCaps(textView.isAllCaps())
.setFallbackLineSpacing(textView.isFallbackLineSpacing())
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
deleted file mode 100644
index aaeb12012f68d602f183490f58c936357cc52455..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * Binder interface to notify the selection toolbar events from one process to the other.
- * @hide
- */
-oneway interface ISelectionToolbarCallback {
- void onShown(in WidgetInfo info);
- void onWidgetUpdated(in WidgetInfo info);
- void onToolbarShowTimeout();
- void onMenuItemClicked(in ToolbarMenuItem item);
- void onError(int errorCode);
-}
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
deleted file mode 100644
index 4a647ada1d6cd11b7267bb89428a41081fe7eaf7..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-/**
- * Mediator between apps and selection toolbar service implementation.
- *
- * @hide
- */
-oneway interface ISelectionToolbarManager {
- void showToolbar(in ShowInfo showInfo, in ISelectionToolbarCallback callback, int userId);
- void hideToolbar(long widgetToken, int userId);
- void dismissToolbar(long widgetToken, int userId);
-}
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/OWNERS b/core/java/android/view/selectiontoolbar/OWNERS
deleted file mode 100644
index 5500b92868dd21cfcac9806c0caeb73a1ea8bd6d..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# Bug component: 709498
-
-augale@google.com
-joannechung@google.com
-licha@google.com
-lpeter@google.com
-svetoslavganov@google.com
-toki@google.com
-tonymak@google.com
-tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
deleted file mode 100644
index 6de031628768ee1916c2cd12028fd33acc36ae27..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.os.RemoteException;
-import android.provider.DeviceConfig;
-
-import java.util.Objects;
-
-/**
- * The {@link SelectionToolbarManager} class provides ways for apps to control the
- * selection toolbar.
- *
- * @hide
- */
-@SystemService(Context.SELECTION_TOOLBAR_SERVICE)
-public final class SelectionToolbarManager {
-
- private static final String TAG = "SelectionToolbar";
-
- /**
- * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell
- * setprop log.tag.UiTranslation DEBUG".
- */
- public static final String LOG_TAG = "SelectionToolbar";
-
- /**
- * Whether system selection toolbar is enabled.
- */
- private static final String REMOTE_SELECTION_TOOLBAR_ENABLED =
- "remote_selection_toolbar_enabled";
-
- /**
- * Used to mark a toolbar that has no toolbar token id.
- */
- public static final long NO_TOOLBAR_ID = 0;
-
- /**
- * The error code that do not allow to create multiple toolbar.
- */
- public static final int ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR = 1;
-
- @NonNull
- private final Context mContext;
- private final ISelectionToolbarManager mService;
-
- public SelectionToolbarManager(@NonNull Context context,
- @NonNull ISelectionToolbarManager service) {
- mContext = Objects.requireNonNull(context);
- mService = service;
- }
-
- /**
- * Request to show selection toolbar for a given View.
- */
- public void showToolbar(@NonNull ShowInfo showInfo,
- @NonNull ISelectionToolbarCallback callback) {
- try {
- Objects.requireNonNull(showInfo);
- Objects.requireNonNull(callback);
- mService.showToolbar(showInfo, callback, mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Request to hide selection toolbar.
- */
- public void hideToolbar(long widgetToken) {
- try {
- mService.hideToolbar(widgetToken, mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Dismiss to dismiss selection toolbar.
- */
- public void dismissToolbar(long widgetToken) {
- try {
- mService.dismissToolbar(widgetToken, mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- private boolean isRemoteSelectionToolbarEnabled() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR,
- REMOTE_SELECTION_TOOLBAR_ENABLED, false);
- }
-
- /**
- * Returns {@code true} if remote render selection toolbar enabled, otherwise
- * returns {@code false}.
- */
- public static boolean isRemoteSelectionToolbarEnabled(Context context) {
- SelectionToolbarManager manager = context.getSystemService(SelectionToolbarManager.class);
- if (manager != null) {
- return manager.isRemoteSelectionToolbarEnabled();
- }
- return false;
- }
-}
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
deleted file mode 100644
index 28b4480d4967baee2a66cc8095abf9e65657b68a..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-import java.util.List;
-
-
-/**
- * The class holds menu information for render service to render the selection toolbar.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public final class ShowInfo implements Parcelable {
-
- /**
- * The token that is used to identify the selection toolbar. This is initially set to 0
- * until a selection toolbar has been created for the showToolbar request.
- */
- private final long mWidgetToken;
-
- /**
- * If the toolbar menu items need to be re-layout.
- */
- private final boolean mLayoutRequired;
-
- /**
- * The menu items to be rendered in the selection toolbar.
- */
- @NonNull
- private final List mMenuItems;
-
- /**
- * A rect specifying where the selection toolbar on the screen.
- */
- @NonNull
- private final Rect mContentRect;
-
- /**
- * A recommended maximum suggested width of the selection toolbar.
- */
- private final int mSuggestedWidth;
-
- /**
- * The portion of the screen that is available to the selection toolbar.
- */
- @NonNull
- private final Rect mViewPortOnScreen;
-
- /**
- * The host application's input token, this allows the remote render service to transfer
- * the touch focus to the host application.
- */
- @NonNull
- private final IBinder mHostInputToken;
-
- /**
- * If the host application uses light theme.
- */
- private final boolean mIsLightTheme;
-
-
-
- // 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/view/selectiontoolbar/ShowInfo.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- /**
- * Creates a new ShowInfo.
- *
- * @param widgetToken
- * The token that is used to identify the selection toolbar. This is initially set to 0
- * until a selection toolbar has been created for the showToolbar request.
- * @param layoutRequired
- * If the toolbar menu items need to be re-layout.
- * @param menuItems
- * The menu items to be rendered in the selection toolbar.
- * @param contentRect
- * A rect specifying where the selection toolbar on the screen.
- * @param suggestedWidth
- * A recommended maximum suggested width of the selection toolbar.
- * @param viewPortOnScreen
- * The portion of the screen that is available to the selection toolbar.
- * @param hostInputToken
- * The host application's input token, this allows the remote render service to transfer
- * the touch focus to the host application.
- * @param isLightTheme
- * If the host application uses light theme.
- */
- @DataClass.Generated.Member
- public ShowInfo(
- long widgetToken,
- boolean layoutRequired,
- @NonNull List menuItems,
- @NonNull Rect contentRect,
- int suggestedWidth,
- @NonNull Rect viewPortOnScreen,
- @NonNull IBinder hostInputToken,
- boolean isLightTheme) {
- this.mWidgetToken = widgetToken;
- this.mLayoutRequired = layoutRequired;
- this.mMenuItems = menuItems;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mMenuItems);
- this.mContentRect = contentRect;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mContentRect);
- this.mSuggestedWidth = suggestedWidth;
- this.mViewPortOnScreen = viewPortOnScreen;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mViewPortOnScreen);
- this.mHostInputToken = hostInputToken;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mHostInputToken);
- this.mIsLightTheme = isLightTheme;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * The token that is used to identify the selection toolbar. This is initially set to 0
- * until a selection toolbar has been created for the showToolbar request.
- */
- @DataClass.Generated.Member
- public long getWidgetToken() {
- return mWidgetToken;
- }
-
- /**
- * If the toolbar menu items need to be re-layout.
- */
- @DataClass.Generated.Member
- public boolean isLayoutRequired() {
- return mLayoutRequired;
- }
-
- /**
- * The menu items to be rendered in the selection toolbar.
- */
- @DataClass.Generated.Member
- public @NonNull List getMenuItems() {
- return mMenuItems;
- }
-
- /**
- * A rect specifying where the selection toolbar on the screen.
- */
- @DataClass.Generated.Member
- public @NonNull Rect getContentRect() {
- return mContentRect;
- }
-
- /**
- * A recommended maximum suggested width of the selection toolbar.
- */
- @DataClass.Generated.Member
- public int getSuggestedWidth() {
- return mSuggestedWidth;
- }
-
- /**
- * The portion of the screen that is available to the selection toolbar.
- */
- @DataClass.Generated.Member
- public @NonNull Rect getViewPortOnScreen() {
- return mViewPortOnScreen;
- }
-
- /**
- * The host application's input token, this allows the remote render service to transfer
- * the touch focus to the host application.
- */
- @DataClass.Generated.Member
- public @NonNull IBinder getHostInputToken() {
- return mHostInputToken;
- }
-
- /**
- * If the host application uses light theme.
- */
- @DataClass.Generated.Member
- public boolean isIsLightTheme() {
- return mIsLightTheme;
- }
-
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "ShowInfo { " +
- "widgetToken = " + mWidgetToken + ", " +
- "layoutRequired = " + mLayoutRequired + ", " +
- "menuItems = " + mMenuItems + ", " +
- "contentRect = " + mContentRect + ", " +
- "suggestedWidth = " + mSuggestedWidth + ", " +
- "viewPortOnScreen = " + mViewPortOnScreen + ", " +
- "hostInputToken = " + mHostInputToken + ", " +
- "isLightTheme = " + mIsLightTheme +
- " }";
- }
-
- @Override
- @DataClass.Generated.Member
- public boolean equals(@android.annotation.Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(ShowInfo other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- ShowInfo that = (ShowInfo) o;
- //noinspection PointlessBooleanExpression
- return true
- && mWidgetToken == that.mWidgetToken
- && mLayoutRequired == that.mLayoutRequired
- && java.util.Objects.equals(mMenuItems, that.mMenuItems)
- && java.util.Objects.equals(mContentRect, that.mContentRect)
- && mSuggestedWidth == that.mSuggestedWidth
- && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
- && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
- && mIsLightTheme == that.mIsLightTheme;
- }
-
- @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 + Long.hashCode(mWidgetToken);
- _hash = 31 * _hash + Boolean.hashCode(mLayoutRequired);
- _hash = 31 * _hash + java.util.Objects.hashCode(mMenuItems);
- _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
- _hash = 31 * _hash + mSuggestedWidth;
- _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
- _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
- _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
- 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 (mLayoutRequired) flg |= 0x2;
- if (mIsLightTheme) flg |= 0x80;
- dest.writeInt(flg);
- dest.writeLong(mWidgetToken);
- dest.writeParcelableList(mMenuItems, flags);
- dest.writeTypedObject(mContentRect, flags);
- dest.writeInt(mSuggestedWidth);
- dest.writeTypedObject(mViewPortOnScreen, flags);
- dest.writeStrongBinder(mHostInputToken);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ ShowInfo(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- int flg = in.readInt();
- boolean layoutRequired = (flg & 0x2) != 0;
- boolean isLightTheme = (flg & 0x80) != 0;
- long widgetToken = in.readLong();
- List menuItems = new java.util.ArrayList<>();
- in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader(), android.view.selectiontoolbar.ToolbarMenuItem.class);
- Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
- int suggestedWidth = in.readInt();
- Rect viewPortOnScreen = (Rect) in.readTypedObject(Rect.CREATOR);
- IBinder hostInputToken = (IBinder) in.readStrongBinder();
-
- this.mWidgetToken = widgetToken;
- this.mLayoutRequired = layoutRequired;
- this.mMenuItems = menuItems;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mMenuItems);
- this.mContentRect = contentRect;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mContentRect);
- this.mSuggestedWidth = suggestedWidth;
- this.mViewPortOnScreen = viewPortOnScreen;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mViewPortOnScreen);
- this.mHostInputToken = hostInputToken;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mHostInputToken);
- this.mIsLightTheme = isLightTheme;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
- @Override
- public ShowInfo[] newArray(int size) {
- return new ShowInfo[size];
- }
-
- @Override
- public ShowInfo createFromParcel(@NonNull android.os.Parcel in) {
- return new ShowInfo(in);
- }
- };
-
- @DataClass.Generated(
- time = 1645108384245L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
- inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
deleted file mode 100644
index 89347c6133101e4af36e4369688d1e6b347e172a..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.drawable.Icon;
-import android.os.Parcelable;
-import android.view.MenuItem;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The menu item that is used to show the selection toolbar.
- *
- * @hide
- */
-@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true)
-public final class ToolbarMenuItem implements Parcelable {
-
- /**
- * The priority of menu item is unknown.
- */
- public static final int PRIORITY_UNKNOWN = 0;
-
- /**
- * The priority of menu item is shown in primary selection toolbar.
- */
- public static final int PRIORITY_PRIMARY = 1;
-
- /**
- * The priority of menu item is shown in overflow selection toolbar.
- */
- public static final int PRIORITY_OVERFLOW = 2;
-
- /**
- * The id of the menu item.
- *
- * @see MenuItem#getItemId()
- */
- private final int mItemId;
-
- /**
- * The title of the menu item.
- *
- * @see MenuItem#getTitle()
- */
- @NonNull
- private final CharSequence mTitle;
-
- /**
- * The content description of the menu item.
- *
- * @see MenuItem#getContentDescription()
- */
- @Nullable
- private final CharSequence mContentDescription;
-
- /**
- * The group id of the menu item.
- *
- * @see MenuItem#getGroupId()
- */
- private final int mGroupId;
-
- /**
- * The icon id of the menu item.
- *
- * @see MenuItem#getIcon()
- */
- @Nullable
- private final Icon mIcon;
-
- /**
- * The tooltip text of the menu item.
- *
- * @see MenuItem#getTooltipText()
- */
- @Nullable
- private final CharSequence mTooltipText;
-
- /**
- * The priority of the menu item used to display the order of the menu item.
- */
- private final int mPriority;
-
- /**
- * Returns the priority from a given {@link MenuItem}.
- */
- public static int getPriorityFromMenuItem(MenuItem menuItem) {
- if (menuItem.requiresActionButton()) {
- return PRIORITY_PRIMARY;
- } else if (menuItem.requiresOverflow()) {
- return PRIORITY_OVERFLOW;
- }
- return PRIORITY_UNKNOWN;
- }
-
-
-
-
- // 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/view/selectiontoolbar/ToolbarMenuItem.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @android.annotation.IntDef(prefix = "PRIORITY_", value = {
- PRIORITY_UNKNOWN,
- PRIORITY_PRIMARY,
- PRIORITY_OVERFLOW
- })
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
- @DataClass.Generated.Member
- public @interface Priority {}
-
- @DataClass.Generated.Member
- public static String priorityToString(@Priority int value) {
- switch (value) {
- case PRIORITY_UNKNOWN:
- return "PRIORITY_UNKNOWN";
- case PRIORITY_PRIMARY:
- return "PRIORITY_PRIMARY";
- case PRIORITY_OVERFLOW:
- return "PRIORITY_OVERFLOW";
- default: return Integer.toHexString(value);
- }
- }
-
- @DataClass.Generated.Member
- /* package-private */ ToolbarMenuItem(
- int itemId,
- @NonNull CharSequence title,
- @Nullable CharSequence contentDescription,
- int groupId,
- @Nullable Icon icon,
- @Nullable CharSequence tooltipText,
- int priority) {
- this.mItemId = itemId;
- this.mTitle = title;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTitle);
- this.mContentDescription = contentDescription;
- this.mGroupId = groupId;
- this.mIcon = icon;
- this.mTooltipText = tooltipText;
- this.mPriority = priority;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * The id of the menu item.
- *
- * @see MenuItem#getItemId()
- */
- @DataClass.Generated.Member
- public int getItemId() {
- return mItemId;
- }
-
- /**
- * The title of the menu item.
- *
- * @see MenuItem#getTitle()
- */
- @DataClass.Generated.Member
- public @NonNull CharSequence getTitle() {
- return mTitle;
- }
-
- /**
- * The content description of the menu item.
- *
- * @see MenuItem#getContentDescription()
- */
- @DataClass.Generated.Member
- public @Nullable CharSequence getContentDescription() {
- return mContentDescription;
- }
-
- /**
- * The group id of the menu item.
- *
- * @see MenuItem#getGroupId()
- */
- @DataClass.Generated.Member
- public int getGroupId() {
- return mGroupId;
- }
-
- /**
- * The icon id of the menu item.
- *
- * @see MenuItem#getIcon()
- */
- @DataClass.Generated.Member
- public @Nullable Icon getIcon() {
- return mIcon;
- }
-
- /**
- * The tooltip text of the menu item.
- *
- * @see MenuItem#getTooltipText()
- */
- @DataClass.Generated.Member
- public @Nullable CharSequence getTooltipText() {
- return mTooltipText;
- }
-
- /**
- * The priority of the menu item used to display the order of the menu item.
- */
- @DataClass.Generated.Member
- public int getPriority() {
- return mPriority;
- }
-
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "ToolbarMenuItem { " +
- "itemId = " + mItemId + ", " +
- "title = " + mTitle + ", " +
- "contentDescription = " + mContentDescription + ", " +
- "groupId = " + mGroupId + ", " +
- "icon = " + mIcon + ", " +
- "tooltipText = " + mTooltipText + ", " +
- "priority = " + mPriority +
- " }";
- }
-
- @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(ToolbarMenuItem other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- ToolbarMenuItem that = (ToolbarMenuItem) o;
- //noinspection PointlessBooleanExpression
- return true
- && mItemId == that.mItemId
- && java.util.Objects.equals(mTitle, that.mTitle)
- && java.util.Objects.equals(mContentDescription, that.mContentDescription)
- && mGroupId == that.mGroupId
- && java.util.Objects.equals(mIcon, that.mIcon)
- && java.util.Objects.equals(mTooltipText, that.mTooltipText)
- && mPriority == that.mPriority;
- }
-
- @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 + mItemId;
- _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
- _hash = 31 * _hash + java.util.Objects.hashCode(mContentDescription);
- _hash = 31 * _hash + mGroupId;
- _hash = 31 * _hash + java.util.Objects.hashCode(mIcon);
- _hash = 31 * _hash + java.util.Objects.hashCode(mTooltipText);
- _hash = 31 * _hash + mPriority;
- 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) { ... }
-
- byte flg = 0;
- if (mContentDescription != null) flg |= 0x4;
- if (mIcon != null) flg |= 0x10;
- if (mTooltipText != null) flg |= 0x20;
- dest.writeByte(flg);
- dest.writeInt(mItemId);
- dest.writeCharSequence(mTitle);
- if (mContentDescription != null) dest.writeCharSequence(mContentDescription);
- dest.writeInt(mGroupId);
- if (mIcon != null) dest.writeTypedObject(mIcon, flags);
- if (mTooltipText != null) dest.writeCharSequence(mTooltipText);
- dest.writeInt(mPriority);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ ToolbarMenuItem(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int itemId = in.readInt();
- CharSequence title = (CharSequence) in.readCharSequence();
- CharSequence contentDescription = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence();
- int groupId = in.readInt();
- Icon icon = (flg & 0x10) == 0 ? null : (Icon) in.readTypedObject(Icon.CREATOR);
- CharSequence tooltipText = (flg & 0x20) == 0 ? null : (CharSequence) in.readCharSequence();
- int priority = in.readInt();
-
- this.mItemId = itemId;
- this.mTitle = title;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTitle);
- this.mContentDescription = contentDescription;
- this.mGroupId = groupId;
- this.mIcon = icon;
- this.mTooltipText = tooltipText;
- this.mPriority = priority;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
- @Override
- public ToolbarMenuItem[] newArray(int size) {
- return new ToolbarMenuItem[size];
- }
-
- @Override
- public ToolbarMenuItem createFromParcel(@NonNull android.os.Parcel in) {
- return new ToolbarMenuItem(in);
- }
- };
-
- /**
- * A builder for {@link ToolbarMenuItem}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static final class Builder {
-
- private int mItemId;
- private @NonNull CharSequence mTitle;
- private @Nullable CharSequence mContentDescription;
- private int mGroupId;
- private @Nullable Icon mIcon;
- private @Nullable CharSequence mTooltipText;
- private int mPriority;
-
- private long mBuilderFieldsSet = 0L;
-
- /**
- * Creates a new Builder.
- *
- * @param itemId
- * The id of the menu item.
- * @param title
- * The title of the menu item.
- * @param contentDescription
- * The content description of the menu item.
- * @param groupId
- * The group id of the menu item.
- * @param icon
- * The icon id of the menu item.
- * @param tooltipText
- * The tooltip text of the menu item.
- * @param priority
- * The priority of the menu item used to display the order of the menu item.
- */
- public Builder(
- int itemId,
- @NonNull CharSequence title,
- @Nullable CharSequence contentDescription,
- int groupId,
- @Nullable Icon icon,
- @Nullable CharSequence tooltipText,
- int priority) {
- mItemId = itemId;
- mTitle = title;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTitle);
- mContentDescription = contentDescription;
- mGroupId = groupId;
- mIcon = icon;
- mTooltipText = tooltipText;
- mPriority = priority;
- }
-
- /**
- * The id of the menu item.
- *
- * @see MenuItem#getItemId()
- */
- @DataClass.Generated.Member
- public @NonNull Builder setItemId(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mItemId = value;
- return this;
- }
-
- /**
- * The title of the menu item.
- *
- * @see MenuItem#getTitle()
- */
- @DataClass.Generated.Member
- public @NonNull Builder setTitle(@NonNull CharSequence value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2;
- mTitle = value;
- return this;
- }
-
- /**
- * The content description of the menu item.
- *
- * @see MenuItem#getContentDescription()
- */
- @DataClass.Generated.Member
- public @NonNull Builder setContentDescription(@NonNull CharSequence value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4;
- mContentDescription = value;
- return this;
- }
-
- /**
- * The group id of the menu item.
- *
- * @see MenuItem#getGroupId()
- */
- @DataClass.Generated.Member
- public @NonNull Builder setGroupId(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x8;
- mGroupId = value;
- return this;
- }
-
- /**
- * The icon id of the menu item.
- *
- * @see MenuItem#getIcon()
- */
- @DataClass.Generated.Member
- public @NonNull Builder setIcon(@NonNull Icon value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x10;
- mIcon = value;
- return this;
- }
-
- /**
- * The tooltip text of the menu item.
- *
- * @see MenuItem#getTooltipText()
- */
- @DataClass.Generated.Member
- public @NonNull Builder setTooltipText(@NonNull CharSequence value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x20;
- mTooltipText = value;
- return this;
- }
-
- /**
- * The priority of the menu item used to display the order of the menu item.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setPriority(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x40;
- mPriority = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull ToolbarMenuItem build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
-
- ToolbarMenuItem o = new ToolbarMenuItem(
- mItemId,
- mTitle,
- mContentDescription,
- mGroupId,
- mIcon,
- mTooltipText,
- mPriority);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
- }
-
- @DataClass.Generated(
- time = 1643200806234L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java",
- inputSignatures = "public static final int PRIORITY_UNKNOWN\npublic static final int PRIORITY_PRIMARY\npublic static final int PRIORITY_OVERFLOW\nprivate final int mItemId\nprivate final @android.annotation.NonNull java.lang.CharSequence mTitle\nprivate final @android.annotation.Nullable java.lang.CharSequence mContentDescription\nprivate final int mGroupId\nprivate final @android.annotation.Nullable android.graphics.drawable.Icon mIcon\nprivate final @android.annotation.Nullable java.lang.CharSequence mTooltipText\nprivate final int mPriority\npublic static int getPriorityFromMenuItem(android.view.MenuItem)\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java
deleted file mode 100644
index 5d0fd473c9144355ee4ba4f2b2f4409910dc57ee..0000000000000000000000000000000000000000
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControlViewHost;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The class holds the rendered content and the related information from the render service to
- * be used to show on the selection toolbar.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public final class WidgetInfo implements Parcelable {
-
- /**
- * The token that is used to identify the selection toolbar.
- */
- private final long mWidgetToken;
-
- /**
- * A Rect that defines the size and positioning of the remote view with respect to
- * its host window.
- */
- @NonNull
- private final Rect mContentRect;
-
- /**
- * The SurfacePackage pointing to the remote view.
- */
- @NonNull
- private final SurfaceControlViewHost.SurfacePackage mSurfacePackage;
-
-
-
- // 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/view/selectiontoolbar/WidgetInfo.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- /**
- * Creates a new WidgetInfo.
- *
- * @param widgetToken
- * The token that is used to identify the selection toolbar.
- * @param contentRect
- * A Rect that defines the size and positioning of the remote view with respect to
- * its host window.
- * @param surfacePackage
- * The SurfacePackage pointing to the remote view.
- */
- @DataClass.Generated.Member
- public WidgetInfo(
- long widgetToken,
- @NonNull Rect contentRect,
- @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
- this.mWidgetToken = widgetToken;
- this.mContentRect = contentRect;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mContentRect);
- this.mSurfacePackage = surfacePackage;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mSurfacePackage);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- /**
- * The token that is used to identify the selection toolbar.
- */
- @DataClass.Generated.Member
- public long getWidgetToken() {
- return mWidgetToken;
- }
-
- /**
- * A Rect that defines the size and positioning of the remote view with respect to
- * its host window.
- */
- @DataClass.Generated.Member
- public @NonNull Rect getContentRect() {
- return mContentRect;
- }
-
- /**
- * The SurfacePackage pointing to the remote view.
- */
- @DataClass.Generated.Member
- public @NonNull SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
- return mSurfacePackage;
- }
-
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "WidgetInfo { " +
- "widgetToken = " + mWidgetToken + ", " +
- "contentRect = " + mContentRect + ", " +
- "surfacePackage = " + mSurfacePackage +
- " }";
- }
-
- @Override
- @DataClass.Generated.Member
- public boolean equals(@android.annotation.Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(WidgetInfo other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- WidgetInfo that = (WidgetInfo) o;
- //noinspection PointlessBooleanExpression
- return true
- && mWidgetToken == that.mWidgetToken
- && java.util.Objects.equals(mContentRect, that.mContentRect)
- && java.util.Objects.equals(mSurfacePackage, that.mSurfacePackage);
- }
-
- @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 + Long.hashCode(mWidgetToken);
- _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
- _hash = 31 * _hash + java.util.Objects.hashCode(mSurfacePackage);
- return _hash;
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- dest.writeLong(mWidgetToken);
- dest.writeTypedObject(mContentRect, flags);
- dest.writeTypedObject(mSurfacePackage, flags);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ WidgetInfo(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- long widgetToken = in.readLong();
- Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
- SurfaceControlViewHost.SurfacePackage surfacePackage = (SurfaceControlViewHost.SurfacePackage) in.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
-
- this.mWidgetToken = widgetToken;
- this.mContentRect = contentRect;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mContentRect);
- this.mSurfacePackage = surfacePackage;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mSurfacePackage);
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator CREATOR
- = new Parcelable.Creator() {
- @Override
- public WidgetInfo[] newArray(int size) {
- return new WidgetInfo[size];
- }
-
- @Override
- public WidgetInfo createFromParcel(@NonNull Parcel in) {
- return new WidgetInfo(in);
- }
- };
-
- @DataClass.Generated(
- time = 1643281495056L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java",
- inputSignatures = "private final long mWidgetToken\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final @android.annotation.NonNull android.view.SurfaceControlViewHost.SurfacePackage mSurfacePackage\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
-}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6523fffc4b91a2acf41f3629f65d311e47500d6f..f5b81b027134d78496c69dd2de48be76269a12ec 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -50,6 +50,7 @@ import android.util.SparseArray;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -3139,4 +3140,15 @@ public class WebView extends AbsoluteLayout
if (result == null) return super.onApplyWindowInsets(insets);
return result;
}
+
+ @Override
+ @Nullable
+ public PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerIndex) {
+ PointerIcon icon =
+ mProvider.getViewDelegate().onResolvePointerIcon(event, pointerIndex);
+ if (icon != null) {
+ return icon;
+ }
+ return super.onResolvePointerIcon(event, pointerIndex);
+ }
}
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 26579c5dec6896e5fd322c4ff467c4135b99794f..ca423e03004428f340af3f23381966337b0ab8b2 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -39,6 +39,7 @@ import android.util.SparseArray;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.PointerIcon;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowInsets;
@@ -496,6 +497,15 @@ public interface WebViewProvider {
default WindowInsets onApplyWindowInsets(@Nullable WindowInsets insets) {
return null;
}
+
+ /**
+ * @hide Only used by WebView.
+ */
+ @SuppressWarnings("unused")
+ @Nullable
+ default PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerIndex) {
+ return null;
+ }
}
interface ScrollDelegate {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index adeb88909d38409d0ec71166e0c8954ac6017358..6ad1960cbda96bab1189a256f49528ebf0869c83 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -4703,7 +4703,7 @@ public abstract class AbsListView extends AdapterView implements Te
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
- if (mFastScroll != null) {
+ if (mFastScroll != null && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
if (pointerIcon != null) {
return pointerIcon;
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 065089f5363304274d30f1281db01263f51c863c..53c73c6bf161e0c105f33e9447cb161f6b74b780 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -16,10 +16,7 @@
package android.widget;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Message;
import android.util.AttributeSet;
@@ -48,7 +45,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mVisible = false;
- private boolean mUserPresent = true;
private boolean mAdvancedByHost = false;
public AdapterViewFlipper(Context context) {
@@ -82,40 +78,10 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
a.recycle();
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mUserPresent = false;
- updateRunning();
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- mUserPresent = true;
- updateRunning(false);
- }
- }
- };
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- // Listen for broadcasts related to user-presence
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views machanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For exmaple, when adding widgets from a user profile to the
- // home screen. Therefore, we register the receiver as the current
- // user not the one the context is for.
- getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
- filter, null, getHandler());
-
-
if (mAutoStart) {
// Automatically start when requested
startFlipping();
@@ -126,8 +92,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
-
- getContext().unregisterReceiver(mReceiver);
updateRunning();
}
@@ -235,8 +199,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
* true.
*/
private void updateRunning(boolean flipNow) {
- boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
- && mAdapter != null;
+ boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
@@ -248,7 +211,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
- + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+ + ", mRunning=" + mRunning);
}
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 1f0e95ea305ae8c92e931d5c3de2bbc4147f426b..e01583322979e81b68c44c6ee82bb48ec9d4d169 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -23,7 +23,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
@@ -37,6 +36,9 @@ import android.view.RemotableViewMethod;
import android.view.View;
import android.view.inspector.InspectableProperty;
import android.widget.RemoteViews.RemoteView;
+import android.widget.TextClock.ClockEventDelegate;
+
+import com.android.internal.util.Preconditions;
import java.time.Clock;
import java.time.DateTimeException;
@@ -112,6 +114,7 @@ public class AnalogClock extends View {
public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mClockEventDelegate = new ClockEventDelegate(context);
mSecondsHandFps = AppGlobals.getIntCoreSetting(
WidgetFlags.KEY_ANALOG_CLOCK_SECONDS_HAND_FPS,
context.getResources()
@@ -584,21 +587,9 @@ public class AnalogClock extends View {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- IntentFilter filter = new IntentFilter();
if (!mReceiverAttached) {
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views mechanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For example, when adding widgets from a user profile to the
- // home screen. Therefore, we register the receiver as the current
- // user not the one the context is for.
- getContext().registerReceiverAsUser(mIntentReceiver,
- android.os.Process.myUserHandle(), filter, null, getHandler());
+ mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
mReceiverAttached = true;
}
@@ -615,12 +606,23 @@ public class AnalogClock extends View {
@Override
protected void onDetachedFromWindow() {
if (mReceiverAttached) {
- getContext().unregisterReceiver(mIntentReceiver);
+ mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
mReceiverAttached = false;
}
super.onDetachedFromWindow();
}
+ /**
+ * Sets a delegate to handle clock event registration. This must be called before the view is
+ * attached to the window
+ *
+ * @hide
+ */
+ public void setClockEventDelegate(ClockEventDelegate delegate) {
+ Preconditions.checkState(!mReceiverAttached, "Clock events already registered");
+ mClockEventDelegate = delegate;
+ }
+
private void onVisible() {
if (!mVisible) {
mVisible = true;
@@ -797,6 +799,7 @@ public class AnalogClock extends View {
}
};
private boolean mReceiverAttached;
+ private ClockEventDelegate mClockEventDelegate;
private final Runnable mTick = new Runnable() {
@Override
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 634cbe323d862c974d81f007cabb5e880ab64fb0..405099d6a260bba0d5929ed53dd7896214393ad6 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -18,6 +18,7 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.PointerIcon;
@@ -173,7 +174,8 @@ public class Button extends TextView {
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
- if (getPointerIcon() == null && isClickable() && isEnabled()) {
+ if (getPointerIcon() == null && isClickable() && isEnabled()
+ && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
}
return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2dbff581fe84bae322709292669a5e744cf539b7..f39122a0a5c1ee48eed8bdc2806e7cc6dc70a7b9 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8105,6 +8105,16 @@ public class Editor {
private final Paint mHighlightPaint;
private final Path mHighlightPath;
+ /**
+ * Whether it is in the progress of updating transformation method. It's needed because
+ * {@link TextView#setTransformationMethod(TransformationMethod)} will eventually call
+ * {@link TextView#setText(CharSequence)}.
+ * Because it normally should exit insert mode when {@link TextView#setText(CharSequence)}
+ * is called externally, we need this boolean to distinguish whether setText is triggered
+ * by setTransformation or not.
+ */
+ private boolean mUpdatingTransformationMethod;
+
InsertModeController(@NonNull TextView textView) {
mTextView = Objects.requireNonNull(textView);
mIsInsertModeActive = false;
@@ -8112,16 +8122,10 @@ public class Editor {
mHighlightPaint = new Paint();
mHighlightPath = new Path();
- // The highlight color is supposed to be 12% of the color primary40. We can't
- // directly access Material 3 theme. But because Material 3 sets the colorPrimary to
- // be primary40, here we hardcoded it to be 12% of colorPrimary.
- final TypedValue typedValue = new TypedValue();
- mTextView.getContext().getTheme()
- .resolveAttribute(R.attr.colorPrimary, typedValue, true);
- final int colorPrimary = typedValue.data;
- final int highlightColor = ColorUtils.setAlphaComponent(colorPrimary,
- (int) (0.12f * Color.alpha(colorPrimary)));
- mHighlightPaint.setColor(highlightColor);
+ // Insert mode highlight color is 20% opacity of the default text color.
+ int color = mTextView.getTextColors().getDefaultColor();
+ color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
+ mHighlightPaint.setColor(color);
}
/**
@@ -8143,7 +8147,7 @@ public class Editor {
final boolean isSingleLine = mTextView.isSingleLine();
mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
isSingleLine, oldTransformationMethod);
- mTextView.setTransformationMethodInternal(mInsertModeTransformationMethod);
+ setTransformationMethod(mInsertModeTransformationMethod, true);
Selection.setSelection((Spannable) mTextView.getText(), offset);
mIsInsertModeActive = true;
@@ -8151,6 +8155,10 @@ public class Editor {
}
void exitInsertMode() {
+ exitInsertMode(true);
+ }
+
+ void exitInsertMode(boolean updateText) {
if (!mIsInsertModeActive) return;
if (mInsertModeTransformationMethod == null
|| mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
@@ -8163,7 +8171,7 @@ public class Editor {
final int selectionEnd = mTextView.getSelectionEnd();
final TransformationMethod oldTransformationMethod =
mInsertModeTransformationMethod.getOldTransformationMethod();
- mTextView.setTransformationMethodInternal(oldTransformationMethod);
+ setTransformationMethod(oldTransformationMethod, updateText);
Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
mIsInsertModeActive = false;
}
@@ -8184,22 +8192,55 @@ public class Editor {
}
/**
- * Notify the {@link InsertModeController} before the TextView's
- * {@link TransformationMethod} is updated. If it's not in the insert mode,
- * the given method is directly returned. Otherwise, it will wrap the given transformation
- * method with an {@link InsertModeTransformationMethod} and then return.
+ * Update the TransformationMethod on the {@link TextView}.
+ * @param method the new method to be set on the {@link TextView}/
+ * @param updateText whether to update the text during setTransformationMethod call.
+ */
+ private void setTransformationMethod(TransformationMethod method, boolean updateText) {
+ mUpdatingTransformationMethod = true;
+ mTextView.setTransformationMethodInternal(method, updateText);
+ mUpdatingTransformationMethod = false;
+ }
+
+ /**
+ * Notify the InsertMode controller that the {@link TextView} is about to set its text.
+ */
+ void beforeSetText() {
+ // TextView#setText is called because our call to
+ // TextView#setTransformationMethodInternal in enterInsertMode(), exitInsertMode() or
+ // updateTransformationMethod().
+ // Do nothing in this case.
+ if (mUpdatingTransformationMethod) {
+ return;
+ }
+ // TextView#setText is called externally. Exit InsertMode but don't update text again
+ // when calling setTransformationMethod.
+ exitInsertMode(/* updateText */ false);
+ }
+
+ /**
+ * Notify the {@link InsertModeController} that TextView#setTransformationMethod is called.
+ * If it's not in the insert mode, the given transformation method is directly set to the
+ * TextView. Otherwise, it will wrap the given transformation method with an
+ * {@link InsertModeTransformationMethod} and then set it on the TextView.
*
- * @param oldTransformationMethod the new {@link TransformationMethod} to be set on the
+ * @param transformationMethod the new {@link TransformationMethod} to be set on the
* TextView.
- * @return the updated {@link TransformationMethod} to be set on the Textview.
*/
- TransformationMethod updateTransformationMethod(
- TransformationMethod oldTransformationMethod) {
- if (!mIsInsertModeActive) return oldTransformationMethod;
+ void updateTransformationMethod(TransformationMethod transformationMethod) {
+ if (!mIsInsertModeActive) {
+ setTransformationMethod(transformationMethod, /* updateText */ true);
+ return;
+ }
+ // Changing TransformationMethod will reset selection range to [0, 0), we need to
+ // manually restore the old selection range.
+ final int selectionStart = mTextView.getSelectionStart();
+ final int selectionEnd = mTextView.getSelectionEnd();
mInsertModeTransformationMethod = mInsertModeTransformationMethod.update(
- oldTransformationMethod, mTextView.isSingleLine());
- return mInsertModeTransformationMethod;
+ transformationMethod, mTextView.isSingleLine());
+ setTransformationMethod(mInsertModeTransformationMethod, /* updateText */ true);
+ Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
}
}
@@ -8211,6 +8252,9 @@ public class Editor {
return mInsertModeController.enterInsertMode(offset);
}
+ /**
+ * Exit insert mode if this editor is in insert mode.
+ */
void exitInsertMode() {
if (mInsertModeController == null) return;
mInsertModeController.exitInsertMode();
@@ -8222,18 +8266,19 @@ public class Editor {
* @param method the {@link TransformationMethod} to be set on the TextView.
*/
void setTransformationMethod(TransformationMethod method) {
- if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
- mTextView.setTransformationMethodInternal(method);
+ if (mInsertModeController == null) {
+ mTextView.setTransformationMethodInternal(method, /* updateText */ true);
return;
}
+ mInsertModeController.updateTransformationMethod(method);
+ }
- // Changing TransformationMethod will reset selection range to [0, 0), we need to
- // manually restore the old selection range.
- final int selectionStart = mTextView.getSelectionStart();
- final int selectionEnd = mTextView.getSelectionEnd();
- method = mInsertModeController.updateTransformationMethod(method);
- mTextView.setTransformationMethodInternal(method);
- Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+ /**
+ * Notify that the Editor that the associated {@link TextView} is about to set its text.
+ */
+ void beforeSetText() {
+ if (mInsertModeController == null) return;
+ mInsertModeController.beforeSetText();
}
/**
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1d6778b8a4a96e90d8a13b0ae34aded347ce6f92..55b2251ac19618241f39d55b05a0bc8cfed67cb6 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1498,6 +1498,11 @@ public class HorizontalScrollView extends FrameLayout {
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
+ int scrollX = getScrollX();
+ if (scrollX < 0 || scrollX > getScrollRange()) {
+ // We've overscrolled, so don't stretch
+ return unconsumed;
+ }
if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) {
int size = getWidth();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index e1b0c915c6847e85b5b4b3b3b30e999176232179..b6c5396ca1769657cce838cfaebe3dbf819846ae 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -18,6 +18,7 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.widget.RemoteViews.RemoteView;
@@ -99,7 +100,8 @@ public class ImageButton extends ImageView {
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
- if (getPointerIcon() == null && isClickable() && isEnabled()) {
+ if (getPointerIcon() == null && isClickable() && isEnabled()
+ && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
}
return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index f3600b0de22bf6d0c54c17c86e0a963da717e370..edf0f48c357766fcb83d26802a6c7434af2d8b48 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -38,6 +38,7 @@ import android.util.MathUtils;
import android.util.StateSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.View;
@@ -1060,9 +1061,11 @@ public class RadialTimePickerView extends View {
if (!isEnabled()) {
return null;
}
- final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
- if (degrees != -1) {
- return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
+ if (degrees != -1) {
+ return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+ }
}
return super.onResolvePointerIcon(event, pointerIndex);
}
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index ec86410cf89c12fc841055d5262330ecc6462221..6a5fc03fcc6e0c5e87ae92a95d0076da67357654 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -17,3 +17,4 @@
package android.widget;
parcelable RemoteViews;
+parcelable RemoteViews.RemoteCollectionItems;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f96266a1f69149c5e97d0a8cfc5e0e5abb29786..a740b651142d92f4aba9238a62e47aa08e046d43 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -33,16 +33,19 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.app.Application;
import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
@@ -65,10 +68,12 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.system.Os;
@@ -98,8 +103,10 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IRemoteViewsFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
@@ -124,7 +131,9 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -232,6 +241,7 @@ public class RemoteViews implements Parcelable, Filter {
private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
+ private static final int SET_REMOTE_ADAPTER_TAG = 33;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -322,6 +332,13 @@ public class RemoteViews implements Parcelable, Filter {
private static final LayoutInflater.Filter INFLATER_FILTER =
(clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
+ /**
+ * The maximum waiting time for remote adapter conversion in milliseconds
+ *
+ * @hide
+ */
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
+
/**
* Application that hosts the remote views.
*
@@ -412,6 +429,13 @@ public class RemoteViews implements Parcelable, Filter {
/** Class cookies of the Parcel this instance was read from. */
private Map mClassCookies;
+ /**
+ * {@link LayoutInflater.Factory2} which will be passed into a {@link LayoutInflater} instance
+ * used by this class.
+ */
+ @Nullable
+ private LayoutInflater.Factory2 mLayoutInflaterFactory2;
+
private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
(view, pendingIntent, response) ->
startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -430,6 +454,29 @@ public class RemoteViews implements Parcelable, Filter {
mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
}
+ /**
+ * Sets {@link LayoutInflater.Factory2} to be passed into {@link LayoutInflater} used
+ * by this class instance. It has to be set before the views are inflated to have any effect.
+ *
+ * The factory callbacks will be called on the background thread so the implementation needs
+ * to be thread safe.
+ *
+ * @hide
+ */
+ public void setLayoutInflaterFactory(@Nullable LayoutInflater.Factory2 factory) {
+ mLayoutInflaterFactory2 = factory;
+ }
+
+ /**
+ * Returns currently set {@link LayoutInflater.Factory2}.
+ *
+ * @hide
+ */
+ @Nullable
+ public LayoutInflater.Factory2 getLayoutInflaterFactory() {
+ return mLayoutInflaterFactory2;
+ }
+
/**
* Reduces all images and ensures that they are all below the given sizes.
*
@@ -739,6 +786,83 @@ public class RemoteViews implements Parcelable, Filter {
}
}
+ /**
+ * @hide
+ * @return True if there is a change
+ */
+ public boolean replaceRemoteCollections(int viewId) {
+ boolean isActionReplaced = false;
+ if (mActions != null) {
+ for (int i = 0; i < mActions.size(); i++) {
+ Action action = mActions.get(i);
+ if (action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
+ && itemsAction.viewId == viewId
+ && itemsAction.mServiceIntent != null) {
+ mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId,
+ itemsAction.mServiceIntent));
+ isActionReplaced = true;
+ } else if (action instanceof SetRemoteViewsAdapterIntent intentAction
+ && intentAction.viewId == viewId) {
+ mActions.set(i, new SetRemoteCollectionItemListAdapterAction(
+ intentAction.viewId, intentAction.intent));
+ isActionReplaced = true;
+ } else if (action instanceof ViewGroupActionAdd groupAction
+ && groupAction.mNestedViews != null) {
+ isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId);
+ }
+ }
+ }
+ if (mSizedRemoteViews != null) {
+ for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+ isActionReplaced |= mSizedRemoteViews.get(i).replaceRemoteCollections(viewId);
+ }
+ }
+ if (mLandscape != null) {
+ isActionReplaced |= mLandscape.replaceRemoteCollections(viewId);
+ }
+ if (mPortrait != null) {
+ isActionReplaced |= mPortrait.replaceRemoteCollections(viewId);
+ }
+
+ return isActionReplaced;
+ }
+
+ /**
+ * @return True if has set remote adapter using service intent
+ * @hide
+ */
+ public boolean hasLegacyLists() {
+ if (mActions != null) {
+ for (int i = 0; i < mActions.size(); i++) {
+ Action action = mActions.get(i);
+ if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
+ && itemsAction.mServiceIntent != null)
+ || (action instanceof SetRemoteViewsAdapterIntent intentAction
+ && intentAction.intent != null)
+ || (action instanceof ViewGroupActionAdd groupAction
+ && groupAction.mNestedViews != null
+ && groupAction.mNestedViews.hasLegacyLists())) {
+ return true;
+ }
+ }
+ }
+ if (mSizedRemoteViews != null) {
+ for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+ if (mSizedRemoteViews.get(i).hasLegacyLists()) {
+ return true;
+ }
+ }
+ }
+ if (mLandscape != null && mLandscape.hasLegacyLists()) {
+ return true;
+ }
+ if (mPortrait != null && mPortrait.hasLegacyLists()) {
+ return true;
+ }
+
+ return false;
+ }
+
private static void visitIconUri(Icon icon, @NonNull Consumer visitor) {
if (icon != null && (icon.getType() == Icon.TYPE_URI
|| icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
@@ -960,6 +1084,11 @@ public class RemoteViews implements Parcelable, Filter {
return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
}
+ @Override
+ public String getUniqueKey() {
+ return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+ }
+
int viewTypeCount;
ArrayList list;
}
@@ -1006,28 +1135,101 @@ public class RemoteViews implements Parcelable, Filter {
}
private class SetRemoteCollectionItemListAdapterAction extends Action {
- private final RemoteCollectionItems mItems;
+ private @NonNull CompletableFuture mItemsFuture;
+ final Intent mServiceIntent;
- SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id,
+ @NonNull RemoteCollectionItems items) {
viewId = id;
- mItems = items;
- mItems.setHierarchyRootData(getHierarchyRootData());
+ items.setHierarchyRootData(getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(items);
+ mServiceIntent = null;
+ }
+
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
+ viewId = id;
+ mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
+ setHierarchyRootData(getHierarchyRootData());
+ mServiceIntent = intent;
+ }
+
+ private static CompletableFuture getItemsFutureFromIntentWithTimeout(
+ Intent intent) {
+ if (intent == null) {
+ Log.e(LOG_TAG, "Null intent received when generating adapter future");
+ return CompletableFuture.completedFuture(new RemoteCollectionItems
+ .Builder().build());
+ }
+
+ final Context context = ActivityThread.currentApplication();
+ final CompletableFuture result = new CompletableFuture<>();
+
+ context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ result.defaultExecutor(), new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName,
+ IBinder iBinder) {
+ RemoteCollectionItems items;
+ try {
+ items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+ .getRemoteCollectionItems();
+ } catch (RemoteException re) {
+ items = new RemoteCollectionItems.Builder().build();
+ Log.e(LOG_TAG, "Error getting collection items from the factory",
+ re);
+ } finally {
+ context.unbindService(this);
+ }
+
+ result.complete(items);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) { }
+ });
+
+ result.completeOnTimeout(
+ new RemoteCollectionItems.Builder().build(),
+ MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+ return result;
}
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
viewId = parcel.readInt();
- mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(
+ new RemoteCollectionItems(parcel, getHierarchyRootData()));
+ mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
}
@Override
public void setHierarchyRootData(HierarchyRootData rootData) {
- mItems.setHierarchyRootData(rootData);
+ mItemsFuture = mItemsFuture
+ .thenApply(rc -> {
+ rc.setHierarchyRootData(rootData);
+ return rc;
+ });
+ }
+
+ private static RemoteCollectionItems getCollectionItemsFromFuture(
+ CompletableFuture itemsFuture) {
+ RemoteCollectionItems items;
+ try {
+ items = itemsFuture.get();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error getting collection items from future", e);
+ items = new RemoteCollectionItems.Builder().build();
+ }
+
+ return items;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- mItems.writeToParcel(dest, flags, /* attached= */ true);
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.writeToParcel(dest, flags, /* attached= */ true);
+ dest.writeTypedObject(mServiceIntent, flags);
}
@Override
@@ -1036,6 +1238,8 @@ public class RemoteViews implements Parcelable, Filter {
View target = root.findViewById(viewId);
if (target == null) return;
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
@@ -1056,10 +1260,10 @@ public class RemoteViews implements Parcelable, Filter {
// recycling in setAdapter, so we must call setAdapter again if the number of view types
// increases.
if (adapter instanceof RemoteCollectionItemsAdapter
- && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+ && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
try {
((RemoteCollectionItemsAdapter) adapter).setData(
- mItems, params.handler, params.colorResources);
+ items, params.handler, params.colorResources);
} catch (Throwable throwable) {
// setData should never failed with the validation in the items builder, but if
// it does, catch and rethrow.
@@ -1069,7 +1273,7 @@ public class RemoteViews implements Parcelable, Filter {
}
try {
- adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+ adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
params.handler, params.colorResources));
} catch (Throwable throwable) {
// This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
@@ -1082,6 +1286,11 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
}
+
+ @Override
+ public String getUniqueKey() {
+ return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+ }
}
private class SetRemoteViewsAdapterIntent extends Action {
@@ -4638,9 +4847,23 @@ public class RemoteViews implements Parcelable, Filter {
* providing data to the RemoteViewsAdapter
*/
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
+ if (isAdapterConversionEnabled()) {
+ addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
+ return;
+ }
addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
+ /**
+ * @hide
+ * @return True if the remote adapter conversion is enabled
+ */
+ public static boolean isAdapterConversionEnabled() {
+ return AppGlobals.getIntCoreSetting(
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
+ }
+
/**
* Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
* ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
@@ -5648,6 +5871,9 @@ public class RemoteViews implements Parcelable, Filter {
// we don't add a filter to the static version returned by getSystemService.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this);
+ if (mLayoutInflaterFactory2 != null) {
+ inflater.setFactory2(mLayoutInflaterFactory2);
+ }
View v = inflater.inflate(rv.getLayoutId(), parent, false);
if (mViewId != View.NO_ID) {
v.setId(mViewId);
@@ -6760,6 +6986,7 @@ public class RemoteViews implements Parcelable, Filter {
// something is going to start.
opts.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
return Pair.create(intent, opts);
}
}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 214e5cc01b9edabf3ba32f54ccfae60e614948ee..a250a867b9ded22aa26a77d1d17c9a31ec09ea16 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -42,6 +42,13 @@ public abstract class RemoteViewsService extends Service {
new HashMap();
private static final Object sLock = new Object();
+ /**
+ * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
+ *
+ * @hide
+ */
+ private static final int MAX_NUM_ENTRY = 10;
+
/**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
@@ -227,6 +234,30 @@ public abstract class RemoteViewsService extends Service {
}
}
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+ .Builder().build();
+
+ try {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ mFactory.onDataSetChanged();
+
+ itemsBuilder.setHasStableIds(mFactory.hasStableIds());
+ final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+ for (int i = 0; i < numOfEntries; i++) {
+ itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+ }
+
+ items = itemsBuilder.build();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return items;
+ }
+
private RemoteViewsFactory mFactory;
private boolean mIsCreated;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index cb5dbe6c5618f3bfc6c418cf80db73c3c8c890d2..e591e9ee78eb843eda7d74025d763a19952885d6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1545,6 +1545,11 @@ public class ScrollView extends FrameLayout {
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
+ int scrollY = getScrollY();
+ if (scrollY < 0 || scrollY > getScrollRange()) {
+ // We've overscrolled, so don't stretch
+ return unconsumed;
+ }
if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
int size = getHeight();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 6c53a44c79faa211972ed2fd3eaa21e5a9a34d04..1317b51f7bfa0af9a41c9512db8d8be4054e9ee3 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -39,6 +39,7 @@ import android.util.AttributeSet;
import android.util.IntArray;
import android.util.MathUtils;
import android.util.StateSet;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
@@ -1041,12 +1042,15 @@ class SimpleMonthView extends View {
if (!isEnabled()) {
return null;
}
- // Add 0.5f to event coordinates to match the logic in onTouchEvent.
- final int x = (int) (event.getX() + 0.5f);
- final int y = (int) (event.getY() + 0.5f);
- final int dayUnderPointer = getDayAtLocation(x, y);
- if (dayUnderPointer >= 0) {
- return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ // Add 0.5f to event coordinates to match the logic in onTouchEvent.
+ final int x = (int) (event.getX() + 0.5f);
+ final int y = (int) (event.getY() + 0.5f);
+ final int dayUnderPointer = getDayAtLocation(x, y);
+ if (dayUnderPointer >= 0) {
+ return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+ }
}
return super.onResolvePointerIcon(event, pointerIndex);
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index ad431efc0bd2b9cf863333e7af66c005b0f0ad2e..ecc41a5ec6c904ce538acb9eea39285c1d3f816d 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -38,6 +38,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.View;
@@ -935,7 +936,8 @@ public class Spinner extends AbsSpinner implements OnClickListener {
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
- if (getPointerIcon() == null && isClickable() && isEnabled()) {
+ if (getPointerIcon() == null && isClickable() && isEnabled()
+ && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
}
return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index e48afb2a3cc8b1e011bd27a043d475fc918e423d..255bd679dc355f7a75962d93d9cacd75ef4c8105 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -16,6 +16,7 @@
package android.widget;
+import static android.os.Process.myUserHandle;
import static android.view.ViewDebug.ExportedProperty;
import static android.widget.RemoteViews.RemoteView;
@@ -24,7 +25,6 @@ import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -43,6 +43,7 @@ import android.view.ViewHierarchyEncoder;
import android.view.inspector.InspectableProperty;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import java.time.Duration;
import java.time.Instant;
@@ -141,6 +142,8 @@ public class TextClock extends TextView {
private boolean mRegistered;
private boolean mShouldRunTicker;
+ private ClockEventDelegate mClockEventDelegate;
+
private Calendar mTime;
private String mTimeZone;
@@ -178,8 +181,7 @@ public class TextClock extends TextView {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
createTime(timeZone);
- } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction())
- || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) {
+ } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
return;
}
onTimeChanged();
@@ -282,6 +284,7 @@ public class TextClock extends TextView {
if (mFormat24 == null) {
mFormat24 = getBestDateTimePattern("Hm");
}
+ mClockEventDelegate = new ClockEventDelegate(getContext());
createTime(mTimeZone);
chooseFormat();
@@ -430,6 +433,17 @@ public class TextClock extends TextView {
registerObserver();
}
+ /**
+ * Sets a delegate to handle clock event registration. This must be called before the view is
+ * attached to the window
+ *
+ * @hide
+ */
+ public void setClockEventDelegate(ClockEventDelegate delegate) {
+ Preconditions.checkState(!mRegistered, "Clock events already registered");
+ mClockEventDelegate = delegate;
+ }
+
/**
* Update the displayed time if necessary and invalidate the view.
*/
@@ -557,7 +571,7 @@ public class TextClock extends TextView {
if (!mRegistered) {
mRegistered = true;
- registerReceiver();
+ mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
registerObserver();
createTime(mTimeZone);
@@ -582,7 +596,7 @@ public class TextClock extends TextView {
super.onDetachedFromWindow();
if (mRegistered) {
- unregisterReceiver();
+ mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
unregisterObserver();
mRegistered = false;
@@ -598,56 +612,27 @@ public class TextClock extends TextView {
mStopTicking = true;
}
- private void registerReceiver() {
- final IntentFilter filter = new IntentFilter();
-
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views mechanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For example, when adding widgets from a managed profile to the
- // home screen. Therefore, we register the receiver as the user
- // the app is running as not the one the context is for.
- getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(),
- filter, null, getHandler());
- }
-
private void registerObserver() {
if (mRegistered) {
if (mFormatChangeObserver == null) {
mFormatChangeObserver = new FormatChangeObserver(getHandler());
}
- final ContentResolver resolver = getContext().getContentResolver();
- Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
- if (mShowCurrentUserTime) {
- resolver.registerContentObserver(uri, true,
- mFormatChangeObserver, UserHandle.USER_ALL);
- } else {
- // UserHandle.myUserId() is needed. This class is supported by the
- // remote views mechanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For example, when adding widgets from a managed profile to the
- // home screen. Therefore, we register the ContentObserver with the user
- // the app is running (e.g. the launcher) and not the user of the
- // context (e.g. the widget's profile).
- resolver.registerContentObserver(uri, true,
- mFormatChangeObserver, UserHandle.myUserId());
- }
+ // UserHandle.myUserId() is needed. This class is supported by the
+ // remote views mechanism and as a part of that the remote views
+ // can be inflated by a context for another user without the app
+ // having interact users permission - just for loading resources.
+ // For example, when adding widgets from a managed profile to the
+ // home screen. Therefore, we register the ContentObserver with the user
+ // the app is running (e.g. the launcher) and not the user of the
+ // context (e.g. the widget's profile).
+ int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId();
+ mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle);
}
}
- private void unregisterReceiver() {
- getContext().unregisterReceiver(mIntentReceiver);
- }
-
private void unregisterObserver() {
if (mFormatChangeObserver != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- resolver.unregisterContentObserver(mFormatChangeObserver);
+ mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver);
}
}
@@ -674,4 +659,59 @@ public class TextClock extends TextView {
stream.addProperty("format", mFormat == null ? null : mFormat.toString());
stream.addProperty("hasSeconds", mHasSeconds);
}
+
+ /**
+ * Utility class to delegate some system event handling to allow overring the default behavior
+ *
+ * @hide
+ */
+ public static class ClockEventDelegate {
+
+ private final Context mContext;
+
+ public ClockEventDelegate(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and
+ * {@link Intent#ACTION_TIMEZONE_CHANGED}
+ *
+ * OK, this is gross but needed. This class is supported by the remote views mechanism and
+ * as a part of that the remote views can be inflated by a context for another user without
+ * the app having interact users permission - just for loading resources. For example,
+ * when adding widgets from a managed profile to the home screen. Therefore, we register
+ * the receiver as the user the app is running as not the one the context is for.
+ */
+ public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
+ final IntentFilter filter = new IntentFilter();
+
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+
+ mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler);
+ }
+
+ /**
+ * Unregisters a previously registered receiver
+ */
+ public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
+ mContext.unregisterReceiver(receiver);
+ }
+
+ /**
+ * Registers an observer for time format changes
+ */
+ public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+ Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
+ mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle);
+ }
+
+ /**
+ * Unregisters a previously registered observer
+ */
+ public void unregisterFormatChangeObserver(ContentObserver observer) {
+ mContext.getContentResolver().unregisterContentObserver(observer);
+ }
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b5f05e49d6c3af14d92454ee64259bc27ef0bf78..6512d90a56a9aa6de06a1aaee21789d56a1293f0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -156,7 +156,6 @@ import android.text.util.Linkify;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.FeatureFlagUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.SparseIntArray;
@@ -831,11 +830,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
- // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to
- // one-way flag flipping. This is a tentative limitation during experiment and will not have the
- // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option.
- private boolean mUserSpeficiedLineBreakwordStyle = false;
-
// This is used to reflect the current user preference for changing font weight and making text
// more bold.
private int mFontWeightAdjustment;
@@ -1546,9 +1540,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
- if (a.hasValue(attr)) {
- mUserSpeficiedLineBreakwordStyle = true;
- }
mLineBreakWordStyle = a.getInt(attr,
LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
break;
@@ -1865,6 +1856,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean clickable = canInputOrMove || isClickable();
boolean longClickable = canInputOrMove || isLongClickable();
int focusable = getFocusable();
+ boolean isAutoHandwritingEnabled = true;
n = a.getIndexCount();
for (int i = 0; i < n; i++) {
@@ -1887,6 +1879,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.View_longClickable:
longClickable = a.getBoolean(attr, longClickable);
break;
+
+ case com.android.internal.R.styleable.View_autoHandwritingEnabled:
+ isAutoHandwritingEnabled = a.getBoolean(attr, true);
+ break;
}
}
a.recycle();
@@ -1900,6 +1896,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
setClickable(clickable);
setLongClickable(longClickable);
+ setAutoHandwritingEnabled(isAutoHandwritingEnabled);
if (mEditor != null) mEditor.prepareCursorControllers();
@@ -2795,11 +2792,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEditor != null) {
mEditor.setTransformationMethod(method);
} else {
- setTransformationMethodInternal(method);
+ setTransformationMethodInternal(method, /* updateText */ true);
}
}
- void setTransformationMethodInternal(@Nullable TransformationMethod method) {
+ /**
+ * Set the transformation that is applied to the text that this TextView is displaying,
+ * optionally call the setText.
+ * @param method the new transformation method to be set.
+ * @param updateText whether the call {@link #setText} which will update the TextView to display
+ * the new content. This method is helpful when updating
+ * {@link TransformationMethod} inside {@link #setText}. It should only be
+ * false if text will be updated immediately after this call, otherwise the
+ * TextView will enter an inconsistent state.
+ */
+ void setTransformationMethodInternal(@Nullable TransformationMethod method,
+ boolean updateText) {
if (method == mTransformation) {
// Avoid the setText() below if the transformation is
// the same.
@@ -2821,7 +2829,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mAllowTransformationLengthChange = false;
}
- setText(mText);
+ if (updateText) {
+ setText(mText);
+ }
if (hasPasswordTransformationMethod()) {
notifyViewAccessibilityStateChangedIfNeeded(
@@ -4337,7 +4347,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
attributes.mHasLineBreakWordStyle = true;
- mUserSpeficiedLineBreakwordStyle = true;
attributes.mLineBreakWordStyle =
appearance.getInt(attr, attributes.mLineBreakWordStyle);
break;
@@ -5073,7 +5082,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param lineBreakWordStyle The line-break word style for the text.
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
- mUserSpeficiedLineBreakwordStyle = true;
if (mLineBreakWordStyle != lineBreakWordStyle) {
mLineBreakWordStyle = lineBreakWordStyle;
if (mLayout != null) {
@@ -5109,12 +5117,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see PrecomputedText
*/
public @NonNull PrecomputedText.Params getTextMetricsParams() {
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
return new PrecomputedText.Params(new TextPaint(mTextPaint),
- LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle,
- autoPhraseBreaking),
+ LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
getTextDirectionHeuristic(),
mBreakStrategy, mHyphenationFrequency);
}
@@ -5134,7 +5138,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
- mUserSpeficiedLineBreakwordStyle = true;
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -6941,11 +6944,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* parameters used to create the PrecomputedText mismatches
* with this TextView.
*/
- @android.view.RemotableViewMethod
+ @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
public final void setText(CharSequence text) {
setText(text, mBufferType);
}
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param text text to be displayed
+ * @return Runnable that sets text; must be called on the main thread by the caller of this
+ * method to complete the operation
+ * @hide
+ */
+ @NonNull
+ public Runnable setTextAsync(@Nullable CharSequence text) {
+ return () -> setText(text);
+ }
+
/**
* Sets the text to be displayed but retains the cursor position. Same as
* {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
@@ -7000,6 +7017,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@UnsupportedAppUsage
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
+ if (mEditor != null) {
+ mEditor.beforeSetText();
+ }
mTextSetFromXmlOrResourceId = false;
if (text == null) {
text = "";
@@ -7061,13 +7081,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mTextDir == null) {
mTextDir = getTextDirectionHeuristic();
}
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
switch (checkResult) {
case PrecomputedText.Params.UNUSABLE:
throw new IllegalArgumentException(
@@ -9223,18 +9240,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
- if (mSpannable != null && mLinksClickable) {
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
- final int offset = getOffsetForPosition(x, y);
- final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
- ClickableSpan.class);
- if (clickables.length > 0) {
- return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (mSpannable != null && mLinksClickable) {
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ final int offset = getOffsetForPosition(x, y);
+ final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
+ ClickableSpan.class);
+ if (clickables.length > 0) {
+ return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
+ }
+ }
+ if (isTextSelectable() || isTextEditable()) {
+ return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
}
- }
- if (isTextSelectable() || isTextEditable()) {
- return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
}
return super.onResolvePointerIcon(event, pointerIndex);
}
@@ -10622,9 +10641,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// TODO: code duplication with makeSingleLayout()
if (mHintLayout == null) {
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
mHint.length(), mTextPaint, hintWidth)
.setAlignment(alignment)
@@ -10637,7 +10653,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10741,9 +10757,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (result == null) {
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
0, mTransformed.length(), mTextPaint, wantWidth)
.setAlignment(alignment)
@@ -10756,7 +10769,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setJustificationMode(mJustificationMode)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -11114,9 +11127,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right));
- final boolean autoPhraseBreaking =
- !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
layoutBuilder.setAlignment(getLayoutAlignment())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
@@ -11127,7 +11137,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
- mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+ mLineBreakStyle, mLineBreakWordStyle));
final StaticLayout layout = layoutBuilder.build();
@@ -12877,6 +12887,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
}
+ /**
+ * @return true if this TextView could be filled by an Autofill service. Note that disabled
+ * fields can still be filled.
+ */
+ @UnsupportedAppUsage
+ boolean isTextAutofillable() {
+ return mText instanceof Editable && onCheckIsTextEditor();
+ }
+
/**
* Returns true, only while processing a touch gesture, if the initial
* touch down event caused focus to move to the text view and as a result
@@ -13600,7 +13619,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public void autofill(AutofillValue value) {
- if (!isTextEditable()) {
+ if (!isTextAutofillable()) {
Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
return;
}
@@ -13616,7 +13635,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public @AutofillType int getAutofillType() {
- return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
+ return isTextAutofillable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
}
/**
@@ -13630,7 +13649,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
@Nullable
public AutofillValue getAutofillValue() {
- if (isTextEditable()) {
+ if (isTextAutofillable()) {
final CharSequence text = TextUtils.trimToParcelableSize(getText());
return AutofillValue.forText(text);
}
@@ -13809,13 +13828,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Helper method to set {@code rect} to the text content's non-clipped area in the view's
- * coordinates.
+ * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
+ * This method obtains the view's visible rectangle whereas the method
+ * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
*
* @return true if at least part of the text content is visible; false if the text content is
* completely clipped or translated out of the visible area.
*/
- private boolean getContentVisibleRect(Rect rect) {
+ private boolean getViewVisibleRect(Rect rect) {
if (!getLocalVisibleRect(rect)) {
return false;
}
@@ -13824,6 +13844,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// view's coordinates. So we need to offset it with the negative scrolled amount to convert
// it to view's coordinate.
rect.offset(-getScrollX(), -getScrollY());
+ return true;
+ }
+
+ /**
+ * Helper method to set {@code rect} to the text content's non-clipped area in the view's
+ * coordinates.
+ *
+ * @return true if at least part of the text content is visible; false if the text content is
+ * completely clipped or translated out of the visible area.
+ */
+ private boolean getContentVisibleRect(Rect rect) {
+ if (!getViewVisibleRect(rect)) {
+ return false;
+ }
// Clip the view's visible rect with the text layout's visible rect.
return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
@@ -13953,14 +13987,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
builder.setMatrix(viewToScreenMatrix);
if (includeEditorBounds) {
- final RectF editorBounds = new RectF();
- editorBounds.set(0 /* left */, 0 /* top */,
- getWidth(), getHeight());
- final RectF handwritingBounds = new RectF(
- -getHandwritingBoundsOffsetLeft(),
- -getHandwritingBoundsOffsetTop(),
- getWidth() + getHandwritingBoundsOffsetRight(),
- getHeight() + getHandwritingBoundsOffsetBottom());
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect bounds = mTempRect;
+ final RectF editorBounds;
+ final RectF handwritingBounds;
+ if (getViewVisibleRect(bounds)) {
+ editorBounds = new RectF(bounds);
+ handwritingBounds = new RectF(editorBounds);
+ handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+ handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+ handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+ handwritingBounds.right += getHandwritingBoundsOffsetRight();
+ } else {
+ // The editor is not visible at all, return empty rectangles. We still need to
+ // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
+ editorBounds = new RectF();
+ handwritingBounds = new RectF();
+ }
EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
.setHandwritingBounds(handwritingBounds).build();
@@ -14006,7 +14051,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
final int line = layout.getLineForOffset(offsetTransformed);
final float insertionMarkerX =
- layout.getPrimaryHorizontal(offsetTransformed)
+ layout.getPrimaryHorizontal(
+ offsetTransformed, layout.shouldClampCursor(line))
+ viewportToContentHorizontalOffset;
final float insertionMarkerTop = layout.getLineTop(line)
+ viewportToContentVerticalOffset;
@@ -14964,7 +15010,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
boolean canShare() {
- if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
+ if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
+ || !getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_textShareSupported)) {
return false;
}
return canCopy();
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 7cb61fe597db8b67ec32f8e2eb191035e47a92fa..0a0071ae30013f4514ad7acb4943bcb84f153f05 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -89,9 +89,10 @@ public class ToastPresenter {
return view;
}
+ private final WeakReference mContext;
private final Resources mResources;
private final WeakReference mWindowManager;
- private final WeakReference mAccessibilityManager;
+ private final IAccessibilityManager mAccessibilityManagerService;
private final INotificationManager mNotificationManager;
private final String mPackageName;
private final String mContextPackageName;
@@ -101,21 +102,14 @@ public class ToastPresenter {
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
INotificationManager notificationManager, String packageName) {
+ mContext = new WeakReference<>(context);
mResources = context.getResources();
mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
mNotificationManager = notificationManager;
mPackageName = packageName;
mContextPackageName = context.getPackageName();
mParams = createLayoutParams();
-
- // We obtain AccessibilityManager manually via its constructor instead of using method
- // AccessibilityManager.getInstance() for 2 reasons:
- // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
- // 2. getInstance() caches the instance for the process even if we pass a different
- // context to it. This is problematic for multi-user because callers can pass a context
- // created via Context.createContextAsUser().
- mAccessibilityManager = new WeakReference<>(
- new AccessibilityManager(context, accessibilityManager, context.getUserId()));
+ mAccessibilityManagerService = accessibilityManager;
}
public String getPackageName() {
@@ -306,11 +300,20 @@ public class ToastPresenter {
* enabled.
*/
public void trySendAccessibilityEvent(View view, String packageName) {
- final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
- if (accessibilityManager == null) {
+ final Context context = mContext.get();
+ if (context == null) {
return;
}
+ // We obtain AccessibilityManager manually via its constructor instead of using method
+ // AccessibilityManager.getInstance() for 2 reasons:
+ // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
+ // 2. getInstance() caches the instance for the process even if we pass a different
+ // context to it. This is problematic for multi-user because callers can pass a context
+ // created via Context.createContextAsUser().
+ final AccessibilityManager accessibilityManager = new AccessibilityManager(context,
+ mAccessibilityManagerService, context.getUserId());
+
if (!accessibilityManager.isEnabled()) {
accessibilityManager.removeClient();
return;
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 5abb6e1637e705572c1f89c76561c272fa080eb4..eaf037e689765f87cf3bf9e7efdbba84f3afb2a9 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -18,10 +18,7 @@ package android.widget;
import android.annotation.IntRange;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Message;
@@ -51,8 +48,6 @@ public class ViewFlipper extends ViewAnimator {
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mVisible = false;
- @UnsupportedAppUsage
- private boolean mUserPresent = true;
public ViewFlipper(Context context) {
super(context);
@@ -70,39 +65,10 @@ public class ViewFlipper extends ViewAnimator {
a.recycle();
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mUserPresent = false;
- updateRunning();
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- mUserPresent = true;
- updateRunning(false);
- }
- }
- };
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- // Listen for broadcasts related to user-presence
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views machanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For exmaple, when adding widgets from a user profile to the
- // home screen. Therefore, we register the receiver as the current
- // user not the one the context is for.
- getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
- filter, null, getHandler());
-
if (mAutoStart) {
// Automatically start when requested
startFlipping();
@@ -113,8 +79,6 @@ public class ViewFlipper extends ViewAnimator {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
-
- getContext().unregisterReceiver(mReceiver);
updateRunning();
}
@@ -186,7 +150,7 @@ public class ViewFlipper extends ViewAnimator {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void updateRunning(boolean flipNow) {
- boolean running = mVisible && mStarted && mUserPresent;
+ boolean running = mVisible && mStarted;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
@@ -198,7 +162,7 @@ public class ViewFlipper extends ViewAnimator {
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
- + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+ + ", mRunning=" + mRunning);
}
}
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 269ce083d2059ae1f916c180dd1b46e1d6a73c13..e32adcf23a3b8c1bd2cbd9aec37c3ab9345976d5 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -106,7 +106,7 @@ public class ConfigurationHelper {
* @see WindowManager#getCurrentWindowMetrics()
* @see WindowManager#getMaximumWindowMetrics()
*/
- public static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+ private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
@NonNull Configuration newConfig) {
final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
final Rect newBounds = newConfig.windowConfiguration.getBounds();
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 534c9de8102c08c22db79080e1f042ecb7e96da4..5ba2f6caac2dc7528fbc78ddd854fcba9b515922 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -80,13 +80,8 @@ interface IWindowOrganizerController {
* Finishes a transition. This must be called for all created transitions.
* @param transitionToken Which transition to finish
* @param t Changes to make before finishing but in the same SF Transaction. Can be null.
- * @param callback Called when t is finished applying.
- * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be
- * negative if no sync transaction was attached (null t or callback)
*/
- int finishTransition(in IBinder transitionToken,
- in @nullable WindowContainerTransaction t,
- in IWindowContainerTransactionCallback callback);
+ void finishTransition(in IBinder transitionToken, in @nullable WindowContainerTransaction t);
/** @return An interface enabling the management of task organizers. */
ITaskOrganizerController getTaskOrganizerController();
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 413f0ccbc27552997189d1d4bdec038a80637b62..43fa0be6c1b74ece429d836af84af06548ca8b10 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -73,6 +73,21 @@ public final class TaskFragmentOperation implements Parcelable {
/** Sets the relative bounds with {@link WindowContainerTransaction#setRelativeBounds}. */
public static final int OP_TYPE_SET_RELATIVE_BOUNDS = 9;
+ /**
+ * Reorders the TaskFragment to be the front-most TaskFragment in the Task.
+ * Note that there could still have other WindowContainer on top of the front-most
+ * TaskFragment, such as a non-embedded Activity.
+ */
+ public static final int OP_TYPE_REORDER_TO_FRONT = 10;
+
+ /**
+ * Sets the activity navigation to be isolated, where the activity navigation on the
+ * TaskFragment is separated from the rest activities in the Task. Activities cannot be
+ * started on an isolated TaskFragment unless the activities are launched from the same
+ * TaskFragment or explicitly requested to.
+ */
+ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -84,7 +99,9 @@ public final class TaskFragmentOperation implements Parcelable {
OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT,
OP_TYPE_SET_COMPANION_TASK_FRAGMENT,
OP_TYPE_SET_ANIMATION_PARAMS,
- OP_TYPE_SET_RELATIVE_BOUNDS
+ OP_TYPE_SET_RELATIVE_BOUNDS,
+ OP_TYPE_REORDER_TO_FRONT,
+ OP_TYPE_SET_ISOLATED_NAVIGATION
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -110,11 +127,14 @@ public final class TaskFragmentOperation implements Parcelable {
@Nullable
private final TaskFragmentAnimationParams mAnimationParams;
+ private final boolean mIsolatedNav;
+
private TaskFragmentOperation(@OperationType int opType,
@Nullable TaskFragmentCreationParams taskFragmentCreationParams,
@Nullable IBinder activityToken, @Nullable Intent activityIntent,
@Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
- @Nullable TaskFragmentAnimationParams animationParams) {
+ @Nullable TaskFragmentAnimationParams animationParams,
+ boolean isolatedNav) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -122,6 +142,7 @@ public final class TaskFragmentOperation implements Parcelable {
mBundle = bundle;
mSecondaryFragmentToken = secondaryFragmentToken;
mAnimationParams = animationParams;
+ mIsolatedNav = isolatedNav;
}
private TaskFragmentOperation(Parcel in) {
@@ -132,6 +153,7 @@ public final class TaskFragmentOperation implements Parcelable {
mBundle = in.readBundle(getClass().getClassLoader());
mSecondaryFragmentToken = in.readStrongBinder();
mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
+ mIsolatedNav = in.readBoolean();
}
@Override
@@ -143,6 +165,7 @@ public final class TaskFragmentOperation implements Parcelable {
dest.writeBundle(mBundle);
dest.writeStrongBinder(mSecondaryFragmentToken);
dest.writeTypedObject(mAnimationParams, flags);
+ dest.writeBoolean(mIsolatedNav);
}
@NonNull
@@ -215,6 +238,14 @@ public final class TaskFragmentOperation implements Parcelable {
return mAnimationParams;
}
+ /**
+ * Returns whether the activity navigation on this TaskFragment is isolated. This is only
+ * useful when the op type is {@link OP_TYPE_SET_ISOLATED_NAVIGATION}.
+ */
+ public boolean isIsolatedNav() {
+ return mIsolatedNav;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -237,6 +268,7 @@ public final class TaskFragmentOperation implements Parcelable {
if (mAnimationParams != null) {
sb.append(", animationParams=").append(mAnimationParams);
}
+ sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append('}');
return sb.toString();
@@ -245,7 +277,7 @@ public final class TaskFragmentOperation implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
- mBundle, mSecondaryFragmentToken, mAnimationParams);
+ mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav);
}
@Override
@@ -260,7 +292,8 @@ public final class TaskFragmentOperation implements Parcelable {
&& Objects.equals(mActivityIntent, other.mActivityIntent)
&& Objects.equals(mBundle, other.mBundle)
&& Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
- && Objects.equals(mAnimationParams, other.mAnimationParams);
+ && Objects.equals(mAnimationParams, other.mAnimationParams)
+ && mIsolatedNav == other.mIsolatedNav;
}
@Override
@@ -292,6 +325,8 @@ public final class TaskFragmentOperation implements Parcelable {
@Nullable
private TaskFragmentAnimationParams mAnimationParams;
+ private boolean mIsolatedNav;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -354,13 +389,23 @@ public final class TaskFragmentOperation implements Parcelable {
return this;
}
+ /**
+ * Sets the activity navigation of this TaskFragment to be isolated.
+ */
+ @NonNull
+ public Builder setIsolatedNav(boolean isolatedNav) {
+ mIsolatedNav = isolatedNav;
+ return this;
+ }
+
/**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
- mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams);
+ mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
+ mIsolatedNav);
}
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index d2a16a3a9212638d2a9342c85f3176244d321dfa..61f340a856c41962aee419ac601521633e2bfe08 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -360,6 +361,15 @@ public final class TransitionInfo implements Parcelable {
mChanges.add(change);
}
+ /**
+ * Whether this transition contains any changes to the window hierarchy,
+ * including keyguard visibility.
+ */
+ public boolean hasChangesOrSideEffects() {
+ return !mChanges.isEmpty() || isKeyguardGoingAway()
+ || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+ }
+
/**
* Whether this transition includes keyguard going away.
*/
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 14046945ede0eb5ed531f0e7ba492eef49937a5d..edea2978c3406bd6d98072cc9ae13114bdfe3689 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -16,8 +16,6 @@
package android.window;
-import static android.view.WindowManager.transitTypeToString;
-
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -51,14 +49,26 @@ public final class TransitionRequestInfo implements Parcelable {
* The reliable parts should be flags, rotation start/end (if rotating), and start/end bounds
* (if size is changing).
*/
- private @Nullable DisplayChange mDisplayChange;
+ private @Nullable TransitionRequestInfo.DisplayChange mDisplayChange;
+
+ /** The transition flags known at the time of the request. These may not be complete. */
+ private final int mFlags;
/** constructor override */
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition) {
- this(type, triggerTask, remoteTransition, null /* displayChange */);
+ this(type, triggerTask, remoteTransition, null /* displayChange */, 0 /* flags */);
+ }
+
+ /** constructor override */
+ public TransitionRequestInfo(
+ @WindowManager.TransitionType int type,
+ @Nullable ActivityManager.RunningTaskInfo triggerTask,
+ @Nullable RemoteTransition remoteTransition,
+ int flags) {
+ this(type, triggerTask, remoteTransition, null /* displayChange */, flags);
}
/** Requested change to a display. */
@@ -236,7 +246,7 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1648141181315L,
+ time = 1691627678294L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -279,19 +289,23 @@ public final class TransitionRequestInfo implements Parcelable {
* If non-null, this request was triggered by this display change. This will not be complete:
* The reliable parts should be flags, rotation start/end (if rotating), and start/end bounds
* (if size is changing).
+ * @param flags
+ * The transition flags known at the time of the request. These may not be complete.
*/
@DataClass.Generated.Member
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition,
- @Nullable DisplayChange displayChange) {
+ @Nullable TransitionRequestInfo.DisplayChange displayChange,
+ int flags) {
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
this.mTriggerTask = triggerTask;
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
+ this.mFlags = flags;
// onConstructed(); // You can define this method to get a callback
}
@@ -327,10 +341,18 @@ public final class TransitionRequestInfo implements Parcelable {
* (if size is changing).
*/
@DataClass.Generated.Member
- public @Nullable DisplayChange getDisplayChange() {
+ public @Nullable TransitionRequestInfo.DisplayChange getDisplayChange() {
return mDisplayChange;
}
+ /**
+ * The transition flags known at the time of the request. These may not be complete.
+ */
+ @DataClass.Generated.Member
+ public int getFlags() {
+ return mFlags;
+ }
+
/**
* If non-null, If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
@@ -356,7 +378,7 @@ public final class TransitionRequestInfo implements Parcelable {
* (if size is changing).
*/
@DataClass.Generated.Member
- public @android.annotation.NonNull TransitionRequestInfo setDisplayChange(@android.annotation.NonNull DisplayChange value) {
+ public @android.annotation.NonNull TransitionRequestInfo setDisplayChange(@android.annotation.NonNull TransitionRequestInfo.DisplayChange value) {
mDisplayChange = value;
return this;
}
@@ -368,10 +390,11 @@ public final class TransitionRequestInfo implements Parcelable {
// String fieldNameToString() { ... }
return "TransitionRequestInfo { " +
- "type = " + transitTypeToString(mType) + ", " +
+ "type = " + mType + ", " +
"triggerTask = " + mTriggerTask + ", " +
"remoteTransition = " + mRemoteTransition + ", " +
- "displayChange = " + mDisplayChange +
+ "displayChange = " + mDisplayChange + ", " +
+ "flags = " + mFlags +
" }";
}
@@ -390,6 +413,7 @@ public final class TransitionRequestInfo implements Parcelable {
if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
+ dest.writeInt(mFlags);
}
@Override
@@ -407,7 +431,8 @@ public final class TransitionRequestInfo implements Parcelable {
int type = in.readInt();
ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
- DisplayChange displayChange = (flg & 0x8) == 0 ? null : (DisplayChange) in.readTypedObject(DisplayChange.CREATOR);
+ TransitionRequestInfo.DisplayChange displayChange = (flg & 0x8) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
+ int flags = in.readInt();
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
@@ -415,6 +440,7 @@ public final class TransitionRequestInfo implements Parcelable {
this.mTriggerTask = triggerTask;
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
+ this.mFlags = flags;
// onConstructed(); // You can define this method to get a callback
}
@@ -434,10 +460,10 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1639445520938L,
+ time = 1691627678327L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
- inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9f804b1e1a7fabb21a002011c2553a78844affba..3323ae576f33e44d90b596333b280adb4049e23d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,6 +688,7 @@ public final class WindowContainerTransaction implements Parcelable {
.setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
.setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
.setArbitraryRectangle(frame))
+ .setInsetsFrameOwner(owner)
.build();
mHierarchyOps.add(hierarchyOp);
return this;
@@ -712,6 +713,7 @@ public final class WindowContainerTransaction implements Parcelable {
new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
.setContainer(receiver.asBinder())
.setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
+ .setInsetsFrameOwner(owner)
.build();
mHierarchyOps.add(hierarchyOp);
return this;
@@ -1344,8 +1346,12 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private IBinder mReparent;
+ @Nullable
private InsetsFrameProvider mInsetsFrameProvider;
+ @Nullable
+ private IBinder mInsetsFrameOwner;
+
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
private boolean mToTop;
@@ -1478,6 +1484,7 @@ public final class WindowContainerTransaction implements Parcelable {
mContainer = copy.mContainer;
mReparent = copy.mReparent;
mInsetsFrameProvider = copy.mInsetsFrameProvider;
+ mInsetsFrameOwner = copy.mInsetsFrameOwner;
mToTop = copy.mToTop;
mReparentTopOnly = copy.mReparentTopOnly;
mWindowingModes = copy.mWindowingModes;
@@ -1496,6 +1503,7 @@ public final class WindowContainerTransaction implements Parcelable {
mContainer = in.readStrongBinder();
mReparent = in.readStrongBinder();
mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR);
+ mInsetsFrameOwner = in.readStrongBinder();
mToTop = in.readBoolean();
mReparentTopOnly = in.readBoolean();
mWindowingModes = in.createIntArray();
@@ -1527,6 +1535,11 @@ public final class WindowContainerTransaction implements Parcelable {
return mInsetsFrameProvider;
}
+ @Nullable
+ public IBinder getInsetsFrameOwner() {
+ return mInsetsFrameOwner;
+ }
+
@NonNull
public IBinder getContainer() {
return mContainer;
@@ -1657,7 +1670,8 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
sb.append("container=").append(mContainer)
- .append(" provider=").append(mInsetsFrameProvider);
+ .append(" provider=").append(mInsetsFrameProvider)
+ .append(" owner=").append(mInsetsFrameOwner);
break;
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
sb.append("container=").append(mContainer)
@@ -1697,6 +1711,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeStrongBinder(mContainer);
dest.writeStrongBinder(mReparent);
dest.writeTypedObject(mInsetsFrameProvider, flags);
+ dest.writeStrongBinder(mInsetsFrameOwner);
dest.writeBoolean(mToTop);
dest.writeBoolean(mReparentTopOnly);
dest.writeIntArray(mWindowingModes);
@@ -1737,8 +1752,12 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private IBinder mReparent;
+ @Nullable
private InsetsFrameProvider mInsetsFrameProvider;
+ @Nullable
+ private IBinder mInsetsFrameOwner;
+
private boolean mToTop;
private boolean mReparentTopOnly;
@@ -1782,8 +1801,13 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
- Builder setInsetsFrameProvider(InsetsFrameProvider providers) {
- mInsetsFrameProvider = providers;
+ Builder setInsetsFrameProvider(InsetsFrameProvider provider) {
+ mInsetsFrameProvider = provider;
+ return this;
+ }
+
+ Builder setInsetsFrameOwner(IBinder owner) {
+ mInsetsFrameOwner = owner;
return this;
}
@@ -1854,6 +1878,7 @@ public final class WindowContainerTransaction implements Parcelable {
? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
: null;
hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider;
+ hierarchyOp.mInsetsFrameOwner = mInsetsFrameOwner;
hierarchyOp.mToTop = mToTop;
hierarchyOp.mReparentTopOnly = mReparentTopOnly;
hierarchyOp.mLaunchOptions = mLaunchOptions;
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index ec792197a329717460349e19a95d0e01d31f7736..df62cc4b83caa1dea58205a1ef9b4673905944c0 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -89,6 +89,14 @@ public class WindowInfosListenerForTest {
this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
}
+
+ @Override
+ public String toString() {
+ return name + ", frame=" + bounds
+ + ", isVisible=" + isVisible
+ + ", isTrustedOverlay=" + isTrustedOverlay
+ + ", token=" + windowToken;
+ }
}
private static final String TAG = "WindowInfosListenerForTest";
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 954f68633e57139f577f24ca5509588ed7c81931..e32c8e58bb21a2849c28f67e59ecdd589cdfef16 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -16,7 +16,7 @@
package android.window;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -80,7 +80,7 @@ public final class WindowMetricsController {
final Rect bounds;
final float density;
final boolean isScreenRound;
- final int windowingMode;
+ final int activityType;
synchronized (ResourcesManager.getInstance()) {
final Configuration config = mContext.getResources().getConfiguration();
final WindowConfiguration winConfig = config.windowConfiguration;
@@ -90,11 +90,11 @@ public final class WindowMetricsController {
// as DisplayMetrics#density
density = config.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
isScreenRound = config.isScreenRound();
- windowingMode = winConfig.getWindowingMode();
+ activityType = winConfig.getActivityType();
}
final IBinder token = Context.getToken(mContext);
final Supplier insetsSupplier = () -> getWindowInsetsFromServerForDisplay(
- mContext.getDisplayId(), token, bounds, isScreenRound, windowingMode);
+ mContext.getDisplayId(), token, bounds, isScreenRound, activityType);
return new WindowMetrics(new Rect(bounds), insetsSupplier, density);
}
@@ -105,23 +105,22 @@ public final class WindowMetricsController {
* @param token the token of Activity or WindowContext
* @param bounds the window bounds to calculate insets for
* @param isScreenRound if the display identified by displayId is round
- * @param windowingMode the windowing mode of the window to calculate insets for
+ * @param activityType the activity type of the window to calculate insets for
* @return WindowInsets calculated for the given window bounds, on the given display
*/
private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, IBinder token,
- Rect bounds, boolean isScreenRound, int windowingMode) {
+ Rect bounds, boolean isScreenRound, int activityType) {
try {
final InsetsState insetsState = new InsetsState();
- final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
- .getWindowInsets(displayId, token, insetsState);
+ WindowManagerGlobal.getWindowManagerService().getWindowInsets(
+ displayId, token, insetsState);
final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
if (overrideInvScale != 1f) {
insetsState.scale(overrideInvScale);
}
return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
- isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
- 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
- WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
+ isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
+ WindowManager.LayoutParams.INVALID_WINDOW_TYPE, activityType,
null /* idSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -145,19 +144,19 @@ public final class WindowMetricsController {
for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
- // Calculate max bounds for this rotation and state.
- Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
- currentDisplayInfo.logicalHeight);
+ // Calculate max bounds for natural rotation and state.
+ Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+ currentDisplayInfo.getNaturalHeight());
- // Calculate insets for the rotated max bounds.
+ // Calculate insets for the natural max bounds.
final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
- // Initialize insets based upon display rotation. Note any window-provided insets
+ // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets
// will not be set.
windowInsets = getWindowInsetsFromServerForDisplay(
currentDisplayInfo.displayId, null /* token */,
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
- WINDOWING_MODE_FULLSCREEN);
+ ACTIVITY_TYPE_UNDEFINED);
// Set the hardware-provided insets.
windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
currentDisplayInfo.roundedCorners)
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 695d01e92316878e628389a5cc12a1468ce16c26..2dc2cbca0548d59a979186c08e877d942351cbcd 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -115,19 +115,15 @@ public class WindowOrganizer {
* Finishes a running transition.
* @param transitionToken The transition to finish. Can't be null.
* @param t A set of window operations to apply before finishing.
- * @param callback A sync callback (if provided). See {@link #applySyncTransaction}.
- * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}.
*
* @hide
*/
@SuppressLint("ExecutorRegistration")
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public int finishTransition(@NonNull IBinder transitionToken,
- @Nullable WindowContainerTransaction t,
- @Nullable WindowContainerTransactionCallback callback) {
+ public void finishTransition(@NonNull IBinder transitionToken,
+ @Nullable WindowContainerTransaction t) {
try {
- return getWindowOrganizerController().finishTransition(transitionToken, t,
- callback != null ? callback.mInterface : null);
+ getWindowOrganizerController().finishTransition(transitionToken, t);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index f2ae973500af83ae4c8b41cdca6933d20071cd2e..611da3cec5c690cdedd1df405904efbf0cbbea61 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -34,6 +34,7 @@ import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
@@ -52,6 +53,8 @@ import android.view.WindowManagerImpl;
@UiContext
public abstract class WindowProviderService extends Service implements WindowProvider {
+ private static final String TAG = WindowProviderService.class.getSimpleName();
+
private final Bundle mOptions;
private final WindowTokenClient mWindowToken = new WindowTokenClient();
private final WindowContextController mController = new WindowContextController(mWindowToken);
@@ -194,8 +197,16 @@ public abstract class WindowProviderService extends Service implements WindowPro
public final Context createServiceBaseContext(ActivityThread mainThread,
LoadedApk packageInfo) {
final Context context = super.createServiceBaseContext(mainThread, packageInfo);
- final Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(getInitialDisplayId());
+ final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ final int initialDisplayId = getInitialDisplayId();
+ Display display = displayManager.getDisplay(initialDisplayId);
+ // Fallback to use the default display if the initial display to start WindowProviderService
+ // is detached.
+ if (display == null) {
+ Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to "
+ + "DEFAULT_DISPLAY");
+ display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ }
return context.createTokenContext(mWindowToken, display);
}
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 94c230bc94fbbc045559647bc23c5bd052ffdf33..2c493031ea8aeec470a0a175babccf02dd914639 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -17,7 +17,7 @@
package com.android.internal.accessibility.common;
/**
- * Collection of common constants for accessibility shortcut.
+ * Collection of common constants for accessibility magnification.
*/
public final class MagnificationConstants {
private MagnificationConstants() {}
@@ -27,4 +27,10 @@ public final class MagnificationConstants {
* the min value, there will be no obvious magnification effect.
*/
public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+
+ /** Minimum supported value for magnification scale. */
+ public static final float SCALE_MIN_VALUE = 1.0f;
+
+ /** Maximum supported value for magnification scale. */
+ public static final float SCALE_MAX_VALUE = 8.0f;
}
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 5e2eceb23789d328f060a8672cbc8fc7bff7216b..dee49350d93e385ceb0dc9aedde0e92c80cc429d 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -177,7 +177,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* 1
would return the work profile {@link ProfileDescriptor}.
*
*/
- abstract ProfileDescriptor getItem(int pageIndex);
+ public abstract ProfileDescriptor getItem(int pageIndex);
/**
* Returns the number of {@link ProfileDescriptor} objects.
@@ -438,8 +438,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
&& isQuietModeEnabled(mWorkProfileUserHandle));
}
- protected class ProfileDescriptor {
- final ViewGroup rootView;
+ public static class ProfileDescriptor {
+ public final ViewGroup rootView;
private final ViewGroup mEmptyStateView;
ProfileDescriptor(ViewGroup rootView) {
this.rootView = rootView;
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 57cc38cc6dfd58f23ee1cc9e3e2129dc66ff1963..4261a0f14767214cd9cfd8673f70813bae699180 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -59,6 +59,8 @@ public class AssistUtils {
public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 6;
/** value for INVOCATION_TYPE_KEY: press on physcial assistant button */
public static final int INVOCATION_TYPE_ASSIST_BUTTON = 7;
+ /** value for INVOCATION_TYPE_KEY: long press on nav handle */
+ public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS = 8;
private final Context mContext;
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
@@ -231,6 +233,23 @@ public class AssistUtils {
}
}
+ /**
+ * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
+ * status.
+ *
+ * @param listener to receive visual service start/stop events.
+ */
+ public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+ listener) {
+ try {
+ if (mVoiceInteractionManagerService != null) {
+ mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register visual query detection start listener", e);
+ }
+ }
+
/**
* Enables visual detection service.
*
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2b39bb4eb7a50ac974c1a69c939af45ffcb11192..0a726d99723adb76d7a011255dde5afc88a110d9 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3023,28 +3023,31 @@ public class ChooserActivity extends ResolverActivity implements
return shouldShowTabs()
&& (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
UserHandle.of(UserHandle.myUserId())).getCount() > 0
- || shouldShowContentPreviewWhenEmpty())
+ || shouldShowStickyContentPreviewWhenEmpty())
&& shouldShowContentPreview();
}
/**
- * This method could be used to override the default behavior when we hide the preview area
- * when the current tab doesn't have any items.
+ * This method could be used to override the default behavior when we hide the sticky preview
+ * area when the current tab doesn't have any items.
*
- * @return true if we want to show the content preview area even if the tab for the current
- * user is empty
+ * @return {@code true} if we want to show the sticky content preview area even if the tab for
+ * the current user is empty
*/
- protected boolean shouldShowContentPreviewWhenEmpty() {
+ protected boolean shouldShowStickyContentPreviewWhenEmpty() {
return false;
}
- /**
- * @return true if we want to show the content preview area
- */
- protected boolean shouldShowContentPreview() {
+ @Override
+ public boolean shouldShowContentPreview() {
return isSendAction(getTargetIntent());
}
+ @Override
+ public boolean shouldShowServiceTargets() {
+ return shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic();
+ }
+
private void updateStickyContentPreview() {
if (shouldShowStickyContentPreviewNoOrientationCheck()) {
// The sticky content preview is only shown when we show the work and personal tabs.
@@ -3406,11 +3409,7 @@ public class ChooserActivity extends ResolverActivity implements
// There can be at most one row in the listview, that is internally
// a ViewGroup with 2 rows
public int getServiceTargetRowCount() {
- if (shouldShowContentPreview()
- && !ActivityManager.isLowRamDeviceStatic()) {
- return 1;
- }
- return 0;
+ return shouldShowServiceTargets() ? 1 : 0;
}
public int getAzLabelRowCount() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 1eecb413adcbf15c0d6916669c45509cf3533821..36038ae1585383ff591d284abbbbacf7f895e852 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -19,7 +19,6 @@ package com.android.internal.app;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
-import android.app.ActivityManager;
import android.app.prediction.AppPredictor;
import android.content.ComponentName;
import android.content.Context;
@@ -425,11 +424,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
public int getServiceTargetCount() {
- if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
- && !ActivityManager.isLowRamDeviceStatic()) {
+ if (mChooserListCommunicator.shouldShowServiceTargets()) {
return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets());
}
-
return 0;
}
@@ -771,6 +768,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
void sendListViewUpdateMessage(UserHandle userHandle);
boolean isSendAction(Intent targetIntent);
+
+ boolean shouldShowContentPreview();
+
+ boolean shouldShowServiceTargets();
}
/**
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 7beb059fb6484c908aab34c15a693601593edc4f..8197e265ca29385dc5b5a466ada9c7b42bf48359 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -94,7 +94,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
}
@Override
- ChooserProfileDescriptor getItem(int pageIndex) {
+ public ChooserProfileDescriptor getItem(int pageIndex) {
return mItems[pageIndex];
}
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index 380118846dc7c3bd52a37bc38cf06749fb89d09c..ba87caa0697c6ba144b9066eed37c2c9734ad435 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -22,6 +22,7 @@ import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionServiceFailure;
+import com.android.internal.infra.AndroidFuture;
/**
* @hide
@@ -113,4 +114,9 @@ oneway interface IHotwordRecognitionStatusCallback {
/** Called when the hotword detection process is restarted */
void onProcessRestarted();
+
+ /**
+ * Called when a file open request is sent.
+ */
+ void onOpenFile(in String filename, in AndroidFuture future);
}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProvider.java b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
similarity index 55%
rename from packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProvider.java
rename to core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
index 99ee0d4d7c68a7043e816f030ffe516977ed7d77..cc49a7539cf0c231cbfd102ef6320443f495987f 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProvider.java
+++ b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.monet.quantize;
+ package com.android.internal.app;
-/** An interface to allow use of different color spaces by quantizers. */
-public interface PointProvider {
- /** The three components in the color space of an sRGB color. */
- double[] fromInt(int argb);
- /** The ARGB (i.e. hex code) representation of this color. */
- int toInt(double[] point);
+ oneway interface IVisualQueryRecognitionStatusListener {
+ /**
+ * Called when {@link VisualQueryDetectionService#onStartDetection} is scheduled from the system
+ * server via {@link VoiceInteractionManagerService#StartPerceiving}.
+ */
+ void onStartPerceiving();
/**
- * Squared distance between two colors.
- * Distance is defined by scientific color spaces and referred to as delta E.
+ * Called when {@link VisualQueryDetectionService#onStopDetection} is scheduled from the system
+ * server via {@link VoiceInteractionManagerService#StopPerceiving}.
*/
- double distance(double[] a, double[] b);
-}
+ void onStopPerceiving();
+ }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 24d5afc42d8ff77f5e2265a440f359df1aefd5ec..314ed69cb885072d47c35b57a5fbb53632f33663 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -40,6 +40,7 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
interface IVoiceInteractionManagerService {
void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -324,6 +325,9 @@ interface IVoiceInteractionManagerService {
*/
void shutdownHotwordDetectionService();
+ @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+ void subscribeVisualQueryRecognitionStatus(in IVisualQueryRecognitionStatusListener listener);
+
@EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 43d263bc8a6d19974f6dca27b316e2ab8fe2c2a1..b3b06037ec24c465694bc8d91b55a167ec1f8092 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -390,12 +390,17 @@ public class LocaleStore {
public static Set transformImeLanguageTagToLocaleInfo(
List list) {
Set imeLocales = new HashSet<>();
+ Set languageTagSet = new HashSet<>();
for (InputMethodSubtype subtype : list) {
- Locale locale = Locale.forLanguageTag(subtype.getLanguageTag());
- LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
- LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
- localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
- imeLocales.add(localeInfo);
+ String languageTag = subtype.getLanguageTag();
+ if (!languageTagSet.contains(languageTag)) {
+ languageTagSet.add(languageTag);
+ Locale locale = Locale.forLanguageTag(languageTag);
+ LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
+ LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
+ localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
+ imeLocales.add(localeInfo);
+ }
}
return imeLocales;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 50f393b53277cf8c99cf6999ded557d763e6c0bb..7534d2960b7c7599c048ae485d399a48945594cc 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -31,6 +31,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERS
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.PermissionChecker.PID_UNKNOWN;
import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
@@ -39,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE
import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UiThread;
@@ -67,6 +69,7 @@ import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Insets;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
@@ -92,6 +95,7 @@ import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
@@ -356,6 +360,12 @@ public class ResolverActivity extends Activity implements
// flag set, we are now losing it. That should be a very rare case
// and we can live with this.
intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+ // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate
+ // side, which means we want to open the target app on the same side as ResolverActivity.
+ if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
+ intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT);
+ }
return intent;
}
@@ -481,6 +491,14 @@ public class ResolverActivity extends Activity implements
rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
mResolverDrawerLayout = rdl;
+
+ for (int i = 0, size = mMultiProfilePagerAdapter.getCount(); i < size; i++) {
+ View view = mMultiProfilePagerAdapter.getItem(i).rootView.findViewById(
+ R.id.resolver_list);
+ if (view != null) {
+ view.setAccessibilityDelegate(new AppListAccessibilityDelegate(rdl));
+ }
+ }
}
mProfileView = findViewById(R.id.profile_button);
@@ -2600,4 +2618,41 @@ public class ResolverActivity extends Activity implements
}
return resolveInfo.userHandle;
}
+
+ /**
+ * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
+ * invisible target in the list.
+ */
+ public static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
+ private final ResolverDrawerLayout mDrawer;
+ @Nullable
+ private final View mBottomBar;
+ private final Rect mRect = new Rect();
+
+ public AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
+ mDrawer = drawer;
+ mBottomBar = mDrawer.findViewById(R.id.button_bar_container);
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host,
+ @NonNull View child,
+ @NonNull AccessibilityEvent event) {
+ boolean result = super.onRequestSendAccessibilityEvent(host, child, event);
+ if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ && mDrawer.isCollapsed()) {
+ child.getBoundsOnScreen(mRect);
+ int childTop = mRect.top;
+ int childBottom = mRect.bottom;
+ mDrawer.getBoundsOnScreen(mRect, true);
+ int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight();
+ int drawerTop = mRect.top;
+ int drawerBottom = mRect.bottom - bottomBarHeight;
+ if (drawerTop > childTop || childBottom > drawerBottom) {
+ mDrawer.setCollapsed(false);
+ }
+ }
+ return result;
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 767791263673982aaa30a14c074b91405ba20423..031f9d3168bff1e00fe607710e8756a70ad45c11 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -79,7 +79,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
}
@Override
- ResolverProfileDescriptor getItem(int pageIndex) {
+ public ResolverProfileDescriptor getItem(int pageIndex) {
return mItems[pageIndex];
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index fff778c616eeca10eca4ce9fd08b4ae2af93f23b..755113b22088b2a308d69a17a529328d536789a7 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -33,6 +33,7 @@ import static com.android.internal.app.procstats.ProcessStats.STATE_BOUND_TOP;
import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -142,6 +143,7 @@ public final class ProcessState {
private ProcessState mCommonProcess;
private int mCurCombinedState = STATE_NOTHING;
private long mStartTime;
+ private int mStateBeforeFrozen = STATE_NOTHING;
private int mLastPssState = STATE_NOTHING;
private long mLastPssTime;
@@ -422,6 +424,27 @@ public final class ProcessState {
&& mTotalRunningPss[PSS_SAMPLE_COUNT] == 0);
}
+ /**
+ * Used to notify that this process was frozen.
+ */
+ public void onProcessFrozen(long now,
+ ArrayMap pkgList) {
+ mStateBeforeFrozen = mCurCombinedState % STATE_COUNT;
+ int currentMemFactor = mCurCombinedState / STATE_COUNT;
+ int combinedState = STATE_FROZEN + (currentMemFactor * STATE_COUNT);
+ setCombinedState(combinedState, now, pkgList);
+ }
+
+ /**
+ * Used to notify that this process was unfrozen.
+ */
+ public void onProcessUnfrozen(long now,
+ ArrayMap pkgList) {
+ int currentMemFactor = mCurCombinedState / STATE_COUNT;
+ int combinedState = mStateBeforeFrozen + (currentMemFactor * STATE_COUNT);
+ setCombinedState(combinedState, now, pkgList);
+ }
+
/**
* Update the current state of the given list of processes.
*
@@ -434,13 +457,20 @@ public final class ProcessState {
ArrayMap pkgList) {
if (state < 0) {
state = mNumStartedServices > 0
- ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
+ ? (STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT)) : STATE_NOTHING;
} else {
- state = PROCESS_STATE_TO_STATE[state] + (memFactor*STATE_COUNT);
+ state = PROCESS_STATE_TO_STATE[state] + (memFactor * STATE_COUNT);
}
+ setCombinedState(state, now, pkgList);
+ }
+ /**
+ * Sets combined state on the corresponding ProcessState objects.
+ */
+ void setCombinedState(int state, long now,
+ ArrayMap pkgList) {
// First update the common process.
- mCommonProcess.setCombinedState(state, now);
+ mCommonProcess.setCombinedStateIdv(state, now);
// If the common process is not multi-package, there is nothing else to do.
if (!mCommonProcess.mMultiPackage) {
@@ -449,12 +479,15 @@ public final class ProcessState {
if (pkgList != null) {
for (int ip=pkgList.size()-1; ip>=0; ip--) {
- pullFixedProc(pkgList, ip).setCombinedState(state, now);
+ pullFixedProc(pkgList, ip).setCombinedStateIdv(state, now);
}
}
}
- public void setCombinedState(int state, long now) {
+ /**
+ * Sets the combined state for this individual ProcessState object.
+ */
+ void setCombinedStateIdv(int state, long now) {
ensureNotDead();
if (!mDead && (mCurCombinedState != state)) {
//Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
@@ -545,7 +578,7 @@ public final class ProcessState {
}
mNumStartedServices++;
if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
- setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+ setCombinedStateIdv(STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT), now);
}
}
@@ -561,7 +594,7 @@ public final class ProcessState {
}
mNumStartedServices--;
if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
- setCombinedState(STATE_NOTHING, now);
+ setCombinedStateIdv(STATE_NOTHING, now);
} else if (mNumStartedServices < 0) {
Slog.wtfStack(TAG, "Proc started services underrun: pkg="
+ mPackage + " uid=" + mUid + " name=" + mName);
@@ -1588,7 +1621,9 @@ public final class ProcessState {
case STATE_CACHED:
cachedMs += duration;
break;
- // TODO (b/261910877) Add support for tracking frozenMs.
+ case STATE_FROZEN:
+ frozenMs += duration;
+ break;
}
}
statsEventOutput.write(
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index dd52de4f84c72248f9867c795dfb56c8f74b0c40..3aa554a759de0e54aa5b7df59a37eb158a65312a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -531,6 +531,24 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
+ /**
+ * (boolean) Whether to enable the adapter conversion in RemoteViews
+ */
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+ "CursorControlFeature__remoteviews_adapter_conversion";
+
+ /**
+ * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+ */
+ public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+ "systemui__remoteviews_adapter_conversion";
+
+ /**
+ * Default value for whether the adapter conversion is enabled or not. This is set for
+ * RemoteViews and should not be a common practice.
+ */
+ public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
+
/**
* (boolean) Whether the task manager should show a stop button if the app is allowlisted
* by the user.
@@ -574,6 +592,11 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
+ /**
+ * (boolean) Whether to allow cursor hover states for certain elements.
+ */
+ public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index c9e76009136ab2ea9532c8906d019ab08f03186e..561b5a6de4abfa3a274e5617bba4e1777cc01192 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -66,25 +66,24 @@ public class SystemUiSystemPropertiesFlags {
public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
- /** Gating the ability for users to dismiss ongoing event notifications */
- public static final Flag ALLOW_DISMISS_ONGOING =
- releasedFlag("persist.sysui.notification.ongoing_dismissal");
-
/** Gating the redaction of OTP notifications on the lockscreen */
public static final Flag OTP_REDACTION =
devFlag("persist.sysui.notification.otp_redaction");
- /** Gating the removal of sorting-notifications-by-interruptiveness. */
- public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
- releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
-
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
/** Gating the holding of WakeLocks until NLSes are told about a new notification. */
public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
- devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+ releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+
+ /** Gating storing NotificationRankingUpdate ranking map in shared memory. */
+ public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
+ "persist.sysui.notification.ranking_update_ashmem");
+
+ public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = devFlag(
+ "persist.sysui.notification.propagate_channel_updates_to_conversations");
}
//// == End of flags. Everything below this line is the implementation. == ////
diff --git a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
index 03f10b65629a89ca83e4652bb1f28609469e0a44..d4fe7c8d7f365c5a66f3f4e4ef23ac1db06a126d 100644
--- a/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
+++ b/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java
@@ -651,7 +651,7 @@ public abstract class DynamicAnimation>
if (!mStartValueIsSet) {
mValue = getPropertyValue();
}
- // Initial check:
+ // Sanity check:
if (mValue > mMaxValue || mValue < mMinValue) {
throw new IllegalArgumentException("Starting value need to be in between min"
+ " value and max value");
diff --git a/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
index dea4907cd6898a414fd02d92ff4991e62f0563d1..36242ae2cf3dc7cef5ab9f184725f6fb865a085e 100644
--- a/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
+++ b/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java
@@ -228,7 +228,7 @@ public final class SpringForce implements Force {
}
/**
- * Initialize the string by doing the necessary pre-calculation as well as some initial check
+ * Initialize the string by doing the necessary pre-calculation as well as some sanity check
* on the setup.
*
* @throws IllegalStateException if the final position is not yet set by the time the spring
diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java
new file mode 100644
index 0000000000000000000000000000000000000000..f177ef88c38f79cf6357f8637e7c19846fad0b91
--- /dev/null
+++ b/core/java/com/android/internal/flags/CoreFlags.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.flags;
+
+import android.flags.BooleanFlag;
+import android.flags.DynamicBooleanFlag;
+import android.flags.FeatureFlags;
+import android.flags.FusedOffFlag;
+import android.flags.FusedOnFlag;
+import android.flags.SyncableFlag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Flags defined here are can be read by code in core.
+ *
+ * Flags not defined here will throw a security exception if third-party processes attempts to read
+ * them.
+ *
+ * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that
+ * runs inside a third party process.
+ */
+public abstract class CoreFlags {
+ private static final List sKnownFlags = new ArrayList<>();
+
+ public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false);
+ public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag");
+ public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag");
+ public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true);
+
+ /** Returns true if the passed in flag matches a flag in this class. */
+ public static boolean isCoreFlag(SyncableFlag flag) {
+ for (SyncableFlag knownFlag : sKnownFlags) {
+ if (knownFlag.getName().equals(flag.getName())
+ && knownFlag.getNamespace().equals(flag.getNamespace())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static List getCoreFlags() {
+ return sKnownFlags;
+ }
+
+ private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) {
+ BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false));
+
+ return f;
+ }
+
+ private static FusedOffFlag fusedOffFlag(String namespace, String name) {
+ FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "false", false));
+
+ return f;
+ }
+
+ private static FusedOnFlag fusedOnFlag(String namespace, String name) {
+ FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "true", false));
+
+ return f;
+ }
+
+ private static DynamicBooleanFlag dynamicBooleanFlag(
+ String namespace, String name, boolean defaultValue) {
+ DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true));
+
+ return f;
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 66e3333acf7c1f00467a044e37d35fcc51d7a02b..30ebbe2bb111c5303e363fa155fd3495a13e7d5c 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -26,6 +26,7 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
@@ -253,13 +254,11 @@ public final class InputMethodPrivilegedOperations {
/**
* Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
*
- * @param flags additional operating flags
* @param reason the reason to hide soft input
- * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
- * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS
*/
@AnyThread
- public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) {
+ public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+ @SoftInputShowHideReason int reason) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
@@ -275,13 +274,9 @@ public final class InputMethodPrivilegedOperations {
/**
* Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)}
- *
- * @param flags additional operating flags
- * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT
- * @see android.view.inputmethod.InputMethodManager#SHOW_FORCED
*/
@AnyThread
- public void showMySoftInput(int flags) {
+ public void showMySoftInput(@InputMethodManager.ShowFlags int flags) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
@@ -391,8 +386,12 @@ public final class InputMethodPrivilegedOperations {
@Nullable ImeTracker.Token statsToken) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
return;
}
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
try {
ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
} catch (RemoteException e) {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e530aec2119a8974961c97e8c9c7147acab9bef5..1ed06b4eac3e45208af13b673230c453f7df76b9 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -25,9 +25,11 @@ import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -68,6 +70,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -79,6 +82,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE;
@@ -255,11 +259,23 @@ public class InteractionJankMonitor {
public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
- public static final int CUJ_IME_INSETS_ANIMATION = 69;
public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+ // 72 - 77 are reserved for b/281564325.
- private static final int LAST_CUJ = CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+ /**
+ * In some cases when we do not have any end-target, we play a simple slide-down animation.
+ * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+ * eg: Exit the app using back gesture.
+ */
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+ public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR = 79;
+ public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+ public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
+
+ public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
+
+ private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
@@ -337,9 +353,21 @@ public class InteractionJankMonitor {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+ // 72 - 77 are reserved for b/281564325.
+ CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_EXPAND_FROM_STATUS_BAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
}
private static class InstanceHolder {
@@ -436,9 +464,13 @@ public class InteractionJankMonitor {
CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
- CUJ_IME_INSETS_ANIMATION,
CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+ CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
+ CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
+ CUJ_IME_INSETS_SHOW_ANIMATION,
+ CUJ_IME_INSETS_HIDE_ANIMATION,
+ CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -1044,12 +1076,20 @@ public class InteractionJankMonitor {
return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
- case CUJ_IME_INSETS_ANIMATION:
- return "IME_INSETS_ANIMATION";
case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
return "LAUNCHER_OPEN_SEARCH_RESULT";
+ case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+ return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
+ case CUJ_SHADE_EXPAND_FROM_STATUS_BAR:
+ return "SHADE_EXPAND_FROM_STATUS_BAR";
+ case CUJ_IME_INSETS_SHOW_ANIMATION:
+ return "IME_INSETS_SHOW_ANIMATION";
+ case CUJ_IME_INSETS_HIDE_ANIMATION:
+ return "IME_INSETS_HIDE_ANIMATION";
+ case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
+ return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index bbaaa472cbbbafe2ed31f3732c5729b0814a581b..a823c29b3f6ec7f07e4519c2e7e6263d3580d86f 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -178,7 +178,7 @@ public class BatteryStatsHistory {
private boolean mHaveBatteryLevel;
private boolean mRecordingHistory;
- private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+ static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
private final HashMap mHistoryTagPool = new HashMap<>();
@@ -1848,6 +1848,7 @@ public class BatteryStatsHistory {
}
return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
} else {
+ tag.poolIdx = HistoryTag.HISTORY_TAG_POOL_OVERFLOW;
// Tag pool overflow: include the tag itself in the parcel
return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index ccc3454624f8a05a4b1d2f274e240bbc0d48ba4b..4c2b2854df88bfcf0dd8b40013a578b4a524673b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -309,7 +309,11 @@ public class BatteryStatsHistoryIterator implements Iterator> mMap = new SparseArray<>();
@@ -77,6 +85,34 @@ public final class AttributeCache {
}
}
+ /**
+ * Start monitor package change, so the resources can be loaded correctly.
+ */
+ void monitorPackageRemove(Handler handler) {
+ if (mPackageMonitor == null) {
+ mPackageMonitor = new PackageMonitor(mContext, handler);
+ }
+ }
+
+ static class PackageMonitor extends BroadcastReceiver {
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ PackageMonitor(Context context, Handler handler) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter,
+ null /* broadcastPermission */, handler);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Uri packageUri = intent.getData();
+ if (packageUri != null) {
+ final String packageName = packageUri.getEncodedSchemeSpecificPart();
+ AttributeCache.instance().removePackage(packageName);
+ }
+ }
+ }
+
public static AttributeCache instance() {
return sInstance;
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 2579ca116efc0b1830c7f54961294b0722c46a98..1be916f44f5b86cef6b9d7f538157800d83931e3 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -232,7 +232,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private boolean mLastHasRightStableInset = false;
private boolean mLastHasLeftStableInset = false;
private int mLastWindowFlags = 0;
- private boolean mLastShouldAlwaysConsumeSystemBars = false;
+ private @InsetsType int mLastForceConsumingTypes = 0;
private @InsetsType int mLastSuppressScrimTypes = 0;
private int mRootScrollY = 0;
@@ -1111,19 +1111,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
: controller.getSystemBarsAppearance();
if (insets != null) {
- mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars();
+ mLastForceConsumingTypes = insets.getForceConsumingTypes();
- final boolean clearsCompatInsets =
- clearsCompatInsets(attrs.type, attrs.flags,
- getResources().getConfiguration().windowConfiguration
- .getWindowingMode())
- && !mLastShouldAlwaysConsumeSystemBars;
+ final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
+ getResources().getConfiguration().windowConfiguration.getActivityType(),
+ mLastForceConsumingTypes);
+ final @InsetsType int compatInsetsTypes =
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars());
final Insets systemInsets = clearsCompatInsets
? Insets.NONE
- : Insets.min(insets.getInsets(WindowInsets.Type.systemBars()
- | WindowInsets.Type.displayCutout()), stableBarInsets);
+ : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
mLastTopInset = systemInsets.top;
mLastBottomInset = systemInsets.bottom;
mLastRightInset = systemInsets.right;
@@ -1161,7 +1160,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
mDrawLegacyNavigationBarBackground =
- (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
+ ((requestedVisibleTypes | mLastForceConsumingTypes)
+ & WindowInsets.Type.navigationBars()) != 0
+ && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
mDrawLegacyNavigationBarBackgroundHandled =
mWindow.onDrawLegacyNavigationBarBackgroundChanged(
@@ -1208,7 +1209,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
&& decorFitsSystemWindows
&& !hideNavigation)
- || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
+ || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0
+ && hideNavigation);
boolean consumingNavBar =
((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
@@ -1224,13 +1226,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| (attrs.flags & FLAG_FULLSCREEN) != 0
|| (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
- boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
- && decorFitsSystemWindows
- && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
- && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
- && mForceWindowDrawsBarBackgrounds
- && mLastTopInset != 0
- || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
+ boolean consumingStatusBar =
+ ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+ && decorFitsSystemWindows
+ && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
+ && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
+ && mForceWindowDrawsBarBackgrounds
+ && mLastTopInset != 0)
+ || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
+ && fullscreen);
int consumedTop = consumingStatusBar ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
@@ -1434,9 +1438,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
boolean force, @InsetsType int requestedVisibleTypes) {
+ final @InsetsType int type = state.attributes.insetsType;
state.present = state.attributes.isPresent(
- (requestedVisibleTypes & state.attributes.insetsType) != 0
- || mLastShouldAlwaysConsumeSystemBars,
+ (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0,
mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index aa2318d219f80ae4f3e00cc1eb9982b78b918a3c..04f2ea3679f5ed2f1145a01052cd1b22e4918f11 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -48,6 +48,7 @@ import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
+import android.os.Handler;
import android.os.SystemProperties;
import android.util.Slog;
import android.view.InflateException;
@@ -1399,4 +1400,14 @@ public class TransitionAnimation {
// Approximation of WCAG 2.0 relative luminance.
return ((r * 8) + (g * 22) + (b * 2)) >> 5;
}
+
+ /**
+ * For non-system server process, it must call this method to initialize the AttributeCache and
+ * start monitor package change, so the resources can be loaded correctly.
+ */
+ public static void initAttributeCache(Context context, Handler handler) {
+ AttributeCache.init(context);
+ AttributeCache.instance().monitorPackageRemove(handler);
+ }
+
}
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 93765cdf0890a28a770b02f8c55338713f1b1eda..8870096f3db7fa4fdd90a72e70e799661cab47f0 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog.common;
+import android.util.Log;
+
/**
* ProtoLog API - exposes static logging methods. Usage of this API is similar
* to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -53,6 +55,9 @@ public class ProtoLog {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
}
+ if (group.isLogToLogcat()) {
+ Log.d(group.getTag(), String.format(messageString, args));
+ }
}
/**
@@ -68,6 +73,9 @@ public class ProtoLog {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
}
+ if (group.isLogToLogcat()) {
+ Log.v(group.getTag(), String.format(messageString, args));
+ }
}
/**
@@ -83,6 +91,9 @@ public class ProtoLog {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
}
+ if (group.isLogToLogcat()) {
+ Log.i(group.getTag(), String.format(messageString, args));
+ }
}
/**
@@ -98,6 +109,9 @@ public class ProtoLog {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
}
+ if (group.isLogToLogcat()) {
+ Log.w(group.getTag(), String.format(messageString, args));
+ }
}
/**
@@ -113,6 +127,9 @@ public class ProtoLog {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
}
+ if (group.isLogToLogcat()) {
+ Log.e(group.getTag(), String.format(messageString, args));
+ }
}
/**
@@ -128,5 +145,8 @@ public class ProtoLog {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
}
+ if (group.isLogToLogcat()) {
+ Log.wtf(group.getTag(), String.format(messageString, args));
+ }
}
}
diff --git a/core/java/com/android/internal/statusbar/IAppClipsService.aidl b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
index 013d0d32e7a2a008ea167fc96cdcf2b7b2fa1824..d6ab8bcdde20d266ae62ed4e6ae096bb01de2bab 100644
--- a/core/java/com/android/internal/statusbar/IAppClipsService.aidl
+++ b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
@@ -23,4 +23,6 @@ package com.android.internal.statusbar;
*/
interface IAppClipsService {
boolean canLaunchCaptureContentActivityForNote(in int taskId);
-}
\ No newline at end of file
+
+ int canLaunchCaptureContentActivityForNoteInternal(in int taskId);
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d2564fb9c268f2d204751c0559422e234103eeae..c6f5086b834688594c52238b70fcfd62ddf459a2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -63,6 +63,19 @@ oneway interface IStatusBar
void cancelPreloadRecentApps();
void showScreenPinningRequest(int taskId);
+ /**
+ * Notify system UI the immersive prompt should be dismissed as confirmed, and the confirmed
+ * status should be saved without user clicking on the button. This could happen when a user
+ * swipe on the edge with the confirmation prompt showing.
+ */
+ void confirmImmersivePrompt();
+
+ /**
+ * Notify system UI the immersive mode changed. This shall be removed when client immersive is
+ * enabled.
+ */
+ void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode);
+
void dismissKeyboardShortcutsMenu();
void toggleKeyboardShortcutsMenu(int deviceId);
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index f974d9d10efd36b8c25fbe971e405726c47011f5..21c3e7b5c6b829b4ff9fb34f80a92cd7b6a50a1e 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.usb;
+import static android.hardware.usb.UsbPort.FLAG_ALT_MODE_TYPE_DISPLAYPORT;
import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY;
import static android.hardware.usb.UsbPortStatus.MODE_DEBUG_ACCESSORY;
import static android.hardware.usb.UsbPortStatus.MODE_DFP;
@@ -26,6 +27,7 @@ import static android.hardware.usb.UsbPortStatus.MODE_UFP;
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
import android.annotation.NonNull;
+import android.hardware.usb.DisplayPortAltModeInfo;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbDevice;
@@ -177,6 +179,10 @@ public class DumpUtils {
dump.write("supports_compliance_warnings",
UsbPortProto.SUPPORTS_COMPLIANCE_WARNINGS,
port.supportsComplianceWarnings());
+ if (port.isAltModeSupported(FLAG_ALT_MODE_TYPE_DISPLAYPORT)) {
+ dump.write("supported_alt_modes", UsbPortProto.SUPPORTED_ALT_MODES,
+ FLAG_ALT_MODE_TYPE_DISPLAYPORT);
+ }
dump.end(token);
}
@@ -255,6 +261,12 @@ public class DumpUtils {
UsbPort.powerBrickConnectionStatusToString(status.getPowerBrickConnectionStatus()));
dump.write("compliance_warning_status", UsbPortStatusProto.COMPLIANCE_WARNINGS_STRING,
UsbPort.complianceWarningsToString(status.getComplianceWarnings()));
+ DisplayPortAltModeInfo displayPortAltModeInfo = status.getDisplayPortAltModeInfo();
+ if (displayPortAltModeInfo != null) {
+ dump.write("displayport_alt_mode_status",
+ UsbPortStatusProto.DISPLAYPORT_ALT_MODE_STATUS,
+ status.getDisplayPortAltModeInfo().toString());
+ }
dump.end(token);
}
}
diff --git a/core/java/com/android/internal/util/NotificationMessagingUtil.java b/core/java/com/android/internal/util/NotificationMessagingUtil.java
index d3cc0e7c767c24ab760beaa023fba9a0cd6e38fd..10856b37ef8d7a9f2b7b2ae9c2dfa74d8b0162ae 100644
--- a/core/java/com/android/internal/util/NotificationMessagingUtil.java
+++ b/core/java/com/android/internal/util/NotificationMessagingUtil.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
@@ -39,10 +40,12 @@ public class NotificationMessagingUtil {
private static final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
private final Context mContext;
- private SparseArray mDefaultSmsApp = new SparseArray<>();
+ private final SparseArray mDefaultSmsApp = new SparseArray<>();
+ private final Object mStateLock;
- public NotificationMessagingUtil(Context context) {
+ public NotificationMessagingUtil(Context context, @Nullable Object stateLock) {
mContext = context;
+ mStateLock = stateLock != null ? stateLock : new Object();
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
}
@@ -63,16 +66,20 @@ public class NotificationMessagingUtil {
private boolean isDefaultMessagingApp(StatusBarNotification sbn) {
final int userId = sbn.getUserId();
if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
- if (mDefaultSmsApp.get(userId) == null) {
- cacheDefaultSmsApp(userId);
+ synchronized (mStateLock) {
+ if (mDefaultSmsApp.get(userId) == null) {
+ cacheDefaultSmsApp(userId);
+ }
+ return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
}
- return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
}
private void cacheDefaultSmsApp(int userId) {
- mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
+ String smsApp = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ synchronized (mStateLock) {
+ mDefaultSmsApp.put(userId, smsApp);
+ }
}
private final ContentObserver mSmsContentObserver = new ContentObserver(
diff --git a/core/java/com/android/internal/util/SettingsWrapper.java b/core/java/com/android/internal/util/SettingsWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..8cf6c18adb3a08a4cb811f7dd77c271e1961aab0
--- /dev/null
+++ b/core/java/com/android/internal/util/SettingsWrapper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for accessing and modifying system settings that would help with testing.
+ */
+public class SettingsWrapper {
+
+ /** Retrieves the string value of a system setting */
+ public String getStringForUser(ContentResolver contentResolver, String name, int userHandle) {
+ return Settings.System.getStringForUser(contentResolver, name, userHandle);
+ }
+
+ /** Updates the string value of a system setting */
+ public String putStringForUser(ContentResolver contentResolver, String name, int userHandle) {
+ return Settings.System.getStringForUser(contentResolver, name, userHandle);
+ }
+}
diff --git a/core/java/com/android/internal/util/TraceBuffer.java b/core/java/com/android/internal/util/TraceBuffer.java
index fcc77bd4f043bcf02958b817aee5d9d2ca72584c..c23e90254179ab0fb59a9c13041e5c169b1fe7c2 100644
--- a/core/java/com/android/internal/util/TraceBuffer.java
+++ b/core/java/com/android/internal/util/TraceBuffer.java
@@ -18,6 +18,7 @@ package com.android.internal.util;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
@@ -39,12 +40,13 @@ import java.util.function.Consumer;
* {@hide}
*/
public class TraceBuffer {
- private final Object mBufferLock = new Object();
-
private final ProtoProvider
mProtoProvider;
+ @GuardedBy("this")
private final Queue mBuffer = new ArrayDeque<>();
private final Consumer mProtoDequeuedCallback;
+ @GuardedBy("this")
private int mBufferUsedSize;
+ @GuardedBy("this")
private int mBufferCapacity;
/**
@@ -115,18 +117,18 @@ public class TraceBuffer {
resetBuffer();
}
- public int getAvailableSpace() {
+ public synchronized int getAvailableSpace() {
return mBufferCapacity - mBufferUsedSize;
}
/**
* Returns buffer size.
*/
- public int size() {
+ public synchronized int size() {
return mBuffer.size();
}
- public void setCapacity(int capacity) {
+ public synchronized void setCapacity(int capacity) {
mBufferCapacity = capacity;
}
@@ -137,22 +139,19 @@ public class TraceBuffer
{
* @throws IllegalStateException if the element cannot be added because it is larger
* than the buffer size.
*/
- public void add(T proto) {
+ public synchronized void add(T proto) {
int protoLength = mProtoProvider.getItemSize(proto);
if (protoLength > mBufferCapacity) {
throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+ mBufferCapacity + " Object size: " + protoLength);
}
- synchronized (mBufferLock) {
- discardOldest(protoLength);
- mBuffer.add(proto);
- mBufferUsedSize += protoLength;
- mBufferLock.notify();
- }
+ discardOldest(protoLength);
+ mBuffer.add(proto);
+ mBufferUsedSize += protoLength;
}
@VisibleForTesting
- public boolean contains(byte[] other) {
+ public synchronized boolean contains(byte[] other) {
return mBuffer.stream()
.anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other));
}
@@ -160,15 +159,13 @@ public class TraceBuffer
{
/**
* Writes the trace buffer to disk inside the encapsulatingProto.
*/
- public void writeTraceToFile(File traceFile, S encapsulatingProto)
+ public synchronized void writeTraceToFile(File traceFile, S encapsulatingProto)
throws IOException {
- synchronized (mBufferLock) {
- traceFile.delete();
- try (OutputStream os = new FileOutputStream(traceFile)) {
- traceFile.setReadable(true /* readable */, false /* ownerOnly */);
- mProtoProvider.write(encapsulatingProto, mBuffer, os);
- os.flush();
- }
+ traceFile.delete();
+ try (OutputStream os = new FileOutputStream(traceFile)) {
+ traceFile.setReadable(true /* readable */, false /* ownerOnly */);
+ mProtoProvider.write(encapsulatingProto, mBuffer, os);
+ os.flush();
}
}
@@ -199,31 +196,27 @@ public class TraceBuffer
{
/**
* Removes all elements from the buffer
*/
- public void resetBuffer() {
- synchronized (mBufferLock) {
- if (mProtoDequeuedCallback != null) {
- for (T item : mBuffer) {
- mProtoDequeuedCallback.accept(item);
- }
+ public synchronized void resetBuffer() {
+ if (mProtoDequeuedCallback != null) {
+ for (T item : mBuffer) {
+ mProtoDequeuedCallback.accept(item);
}
- mBuffer.clear();
- mBufferUsedSize = 0;
}
+ mBuffer.clear();
+ mBufferUsedSize = 0;
}
@VisibleForTesting
- public int getBufferSize() {
+ public synchronized int getBufferSize() {
return mBufferUsedSize;
}
/**
* Returns the buffer status in human-readable form.
*/
- public String getStatus() {
- synchronized (mBufferLock) {
- return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
- + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
- + "Elements in the buffer: " + mBuffer.size();
- }
+ public synchronized String getStatus() {
+ return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
+ + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
+ + "Elements in the buffer: " + mBuffer.size();
}
}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adcad0e7bf1aac0d750a6201ccf117b7becd2..5b6b36043684474fd90369052c2b48976271f00a 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@ public class ConversationLayout extends FrameLayout
updateContentEndPaddings();
}
- @RemotableViewMethod
+ /**
+ * Set conversation data
+ * @param extras Bundle contains conversation data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List newMessages
@@ -393,8 +397,7 @@ public class ConversationLayout extends FrameLayout
= Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
// mUser now set (would be nice to avoid the side effect but WHATEVER)
- setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
-
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
@@ -402,11 +405,32 @@ public class ConversationLayout extends FrameLayout
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
+ int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
+
+ // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
+ // if they exist
+ final List newMessagingMessages =
+ createMessages(newMessages, /* isHistoric= */false,
+ /* usePrecomputedText= */false);
+ final List newHistoricMessagingMessages =
+ createMessages(newHistoricMessages, /* isHistoric= */true,
+ /* usePrecomputedText= */false);
// bind it, baby
- bind(newMessages, newHistoricMessages, showSpinner);
+ bindViews(user, showSpinner, unreadCount,
+ newMessagingMessages,
+ newHistoricMessagingMessages);
+ }
- int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
- setUnreadCount(unreadCount);
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains conversation data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
}
@Override
@@ -436,15 +460,17 @@ public class ConversationLayout extends FrameLayout
}
}
- private void bind(List newMessages,
- List newHistoricMessages,
- boolean showSpinner) {
- // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
- // if they exist
- List historicMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
- List messages = createMessages(newMessages, false /* isHistoric */);
+ private void bindViews(Person user,
+ boolean showSpinner, int unreadCount, List newMessagingMessages,
+ List newHistoricMessagingMessages) {
+ setUser(user);
+ setUnreadCount(unreadCount);
+ bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
+ }
+
+ private void bind(boolean showSpinner, List messages,
+ List historicMessages) {
// Copy our groups, before they get clobbered
ArrayList oldGroups = new ArrayList<>(mGroups);
@@ -957,15 +983,17 @@ public class ConversationLayout extends FrameLayout
* @param newMessages the messages to parse.
*/
private List createMessages(
- List newMessages, boolean historic) {
+ List newMessages, boolean isHistoric,
+ boolean usePrecomputedText) {
List result = new ArrayList<>();
for (int i = 0; i < newMessages.size(); i++) {
Notification.MessagingStyle.Message m = newMessages.get(i);
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
- message = MessagingMessage.createMessage(this, m, mImageResolver);
+ message = MessagingMessage.createMessage(this, m,
+ mImageResolver, usePrecomputedText);
}
- message.setIsHistoric(historic);
+ message.setIsHistoric(isHistoric);
result.add(message);
}
return result;
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index c06dab9f75d6d1492c656be8db3053aba8139a31..918d9c029ef52e84c5b418330384f038487275d9 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,5 +39,6 @@ interface IRemoteViewsFactory {
boolean hasStableIds();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isCreated();
+ RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
}
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 1ac5e1f12bfa36b6469a63baad5eae8aa9a26dc3..0704cb8094d73b41663f004ca13fce58bd70e752 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,6 +18,7 @@ package com.android.internal.widget;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Trace;
import android.text.BoringLayout;
import android.text.Layout;
import android.text.StaticLayout;
@@ -62,12 +63,15 @@ public class ImageFloatingTextView extends TextView {
public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+ setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
}
@Override
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize,
TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
+ Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
TransformationMethod transformationMethod = getTransformationMethod();
CharSequence text = getText();
if (transformationMethod != null) {
@@ -81,8 +85,8 @@ public class ImageFloatingTextView extends TextView {
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
.setUseLineSpacingFromFallbacks(true)
- .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
- .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+ .setBreakStrategy(getBreakStrategy())
+ .setHyphenationFrequency(getHyphenationFrequency());
int maxLines;
if (mMaxLinesForHeight > 0) {
maxLines = mMaxLinesForHeight;
@@ -110,7 +114,9 @@ public class ImageFloatingTextView extends TextView {
builder.setIndents(null, margins);
}
- return builder.build();
+ final StaticLayout result = builder.build();
+ Trace.endSection();
+ return result;
}
/**
@@ -135,6 +141,7 @@ public class ImageFloatingTextView extends TextView {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("ImageFloatingTextView#onMeasure");
int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
if (getLayout() != null && getLayout().getHeight() != availableHeight) {
// We've been measured before and the new size is different than before, lets make sure
@@ -161,6 +168,7 @@ public class ImageFloatingTextView extends TextView {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
+ Trace.endSection();
}
@Override
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
index 098bce14e619299a715bccd99dfb97ace0ff074b..c132d6a90f6c77857a549d31f1dce8356641e184 100644
--- a/core/java/com/android/internal/widget/MessagingImageMessage.java
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -93,8 +93,9 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage
}
@Override
- public boolean setMessage(Notification.MessagingStyle.Message message) {
- MessagingMessage.super.setMessage(message);
+ public boolean setMessage(Notification.MessagingStyle.Message message,
+ boolean usePrecomputedText) {
+ MessagingMessage.super.setMessage(message, usePrecomputedText);
Drawable drawable;
try {
Uri uri = message.getDataUri();
@@ -114,32 +115,42 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage
}
mDrawable = drawable;
mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
- setImageDrawable(drawable);
- setContentDescription(message.getText());
+ if (!usePrecomputedText) {
+ finalizeInflate();
+ }
return true;
}
static MessagingMessage createMessage(IMessagingLayout layout,
- Notification.MessagingStyle.Message m, ImageResolver resolver) {
+ Notification.MessagingStyle.Message m, ImageResolver resolver,
+ boolean usePrecomputedText) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingImageMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingImageMessage) LayoutInflater.from(
layout.getContext()).inflate(
- R.layout.notification_template_messaging_image_message,
- messagingLinearLayout,
- false);
+ R.layout.notification_template_messaging_image_message,
+ messagingLinearLayout,
+ false);
createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
createdMessage.setImageResolver(resolver);
- boolean created = createdMessage.setMessage(m);
- if (!created) {
+ // MessagingImageMessage does not use usePrecomputedText.
+ boolean populated = createdMessage.setMessage(m, /* usePrecomputedText= */false);
+ if (!populated) {
createdMessage.recycle();
- return MessagingTextMessage.createMessage(layout, m);
+ return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
}
return createdMessage;
}
+
+ @Override
+ public void finalizeInflate() {
+ setImageDrawable(mDrawable);
+ setContentDescription(getMessage().getText());
+ }
+
private void setImageResolver(ImageResolver resolver) {
mImageResolver = resolver;
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f689b98a65e3a9c73c53ad0c0b77a5d639e..83557cd8a719023692d95a935143372fd0c8c2e1 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@ public class MessagingLayout extends FrameLayout
mConversationTitle = conversationTitle;
}
- @RemotableViewMethod
+ /**
+ * Set Messaging data
+ * @param extras Bundle contains messaging data
+ */
+ @RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List newMessages
@@ -168,9 +172,28 @@ public class MessagingLayout extends FrameLayout
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
addRemoteInputHistoryToMessages(newMessages, history);
+
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- bind(newMessages, newHistoricMessages, showSpinner);
+
+ final List historicMessagingMessages = createMessages(newHistoricMessages,
+ /* isHistoric= */true, /* usePrecomputedText= */ false);
+ final List newMessagingMessages =
+ createMessages(newMessages, /* isHistoric= */false, /* usePrecomputedText= */false);
+ bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+ }
+
+ /**
+ * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+ * This should be called on a background thread, and returns a Runnable which is then must be
+ * called on the main thread to complete the operation and set text.
+ * @param extras Bundle contains messaging data
+ * @hide
+ */
+ @NonNull
+ public Runnable setDataAsync(Bundle extras) {
+ return () -> setData(extras);
}
@Override
@@ -195,14 +218,15 @@ public class MessagingLayout extends FrameLayout
}
}
- private void bind(List newMessages,
- List newHistoricMessages,
- boolean showSpinner) {
-
- List historicMessages = createMessages(newHistoricMessages,
- true /* isHistoric */);
- List messages = createMessages(newMessages, false /* isHistoric */);
+ private void bindViews(Person user, boolean showSpinner,
+ List historicMessagingMessages,
+ List newMessagingMessages) {
+ setUser(user);
+ bind(showSpinner, historicMessagingMessages, newMessagingMessages);
+ }
+ private void bind(boolean showSpinner, List historicMessages,
+ List messages) {
ArrayList oldGroups = new ArrayList<>(mGroups);
addMessagesToGroups(historicMessages, messages, showSpinner);
@@ -494,15 +518,17 @@ public class MessagingLayout extends FrameLayout
* @param newMessages the messages to parse.
*/
private List createMessages(
- List newMessages, boolean historic) {
+ List newMessages, boolean isHistoric,
+ boolean usePrecomputedText) {
List result = new ArrayList<>();
for (int i = 0; i < newMessages.size(); i++) {
Notification.MessagingStyle.Message m = newMessages.get(i);
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
- message = MessagingMessage.createMessage(this, m, mImageResolver);
+ message = MessagingMessage.createMessage(this, m,
+ mImageResolver, usePrecomputedText);
}
- message.setIsHistoric(historic);
+ message.setIsHistoric(isHistoric);
result.add(message);
}
return result;
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index 5ecd3b82053dd69ad28b84a195c27edd01338adf..ad90a63ab1877bfa50ac1a0c8f66d8d32fad91c4 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -34,11 +34,12 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
String IMAGE_MIME_TYPE_PREFIX = "image/";
static MessagingMessage createMessage(IMessagingLayout layout,
- Notification.MessagingStyle.Message m, ImageResolver resolver) {
+ Notification.MessagingStyle.Message m, ImageResolver resolver,
+ boolean usePrecomputedText) {
if (hasImage(m) && !ActivityManager.isLowRamDeviceStatic()) {
- return MessagingImageMessage.createMessage(layout, m, resolver);
+ return MessagingImageMessage.createMessage(layout, m, resolver, usePrecomputedText);
} else {
- return MessagingTextMessage.createMessage(layout, m);
+ return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
}
}
@@ -55,9 +56,11 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
/**
* Set a message for this view.
+ *
* @return true if setting the message worked
*/
- default boolean setMessage(Notification.MessagingStyle.Message message) {
+ default boolean setMessage(Notification.MessagingStyle.Message message,
+ boolean usePrecomputedText) {
getState().setMessage(message);
return true;
}
@@ -151,4 +154,10 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
void setVisibility(int visibility);
int getVisibility();
+
+ /**
+ * Finalize inflation of the MessagingMessages, which should be called on Main Thread.
+ * @hide
+ */
+ void finalizeInflate();
}
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
index 19791dbad31ede297cbc13c6341762f6c2fefa95..bd62aad15b349b4f637dfe6426dedcd1705e8a64 100644
--- a/core/java/com/android/internal/widget/MessagingTextMessage.java
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -23,7 +23,9 @@ import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
import android.text.Layout;
+import android.text.PrecomputedText;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.widget.RemoteViews;
@@ -35,10 +37,13 @@ import com.android.internal.R;
@RemoteViews.RemoteView
public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
+ private static final String TAG = "MessagingTextMessage";
private static final MessagingPool sInstancePool =
new MessagingPool<>(20);
private final MessagingMessageState mState = new MessagingMessageState(this);
+ private PrecomputedText mPrecomputedText = null;
+
public MessagingTextMessage(@NonNull Context context) {
super(context);
}
@@ -63,25 +68,32 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa
}
@Override
- public boolean setMessage(Notification.MessagingStyle.Message message) {
- MessagingMessage.super.setMessage(message);
- setText(message.getText());
+ public boolean setMessage(Notification.MessagingStyle.Message message,
+ boolean usePrecomputedText) {
+ MessagingMessage.super.setMessage(message, usePrecomputedText);
+ if (usePrecomputedText) {
+ mPrecomputedText = PrecomputedText.create(message.getText(), getTextMetricsParams());
+ } else {
+ setText(message.getText());
+ mPrecomputedText = null;
+ }
+
return true;
}
static MessagingMessage createMessage(IMessagingLayout layout,
- Notification.MessagingStyle.Message m) {
+ Notification.MessagingStyle.Message m, boolean usePrecomputedText) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingTextMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingTextMessage) LayoutInflater.from(
layout.getContext()).inflate(
- R.layout.notification_template_messaging_text_message,
- messagingLinearLayout,
- false);
+ R.layout.notification_template_messaging_text_message,
+ messagingLinearLayout,
+ false);
createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
- createdMessage.setMessage(m);
+ createdMessage.setMessage(m, usePrecomputedText);
return createdMessage;
}
@@ -135,4 +147,20 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa
public void setColor(int color) {
setTextColor(color);
}
+
+ @Override
+ public void finalizeInflate() {
+ try {
+ setText(mPrecomputedText != null ? mPrecomputedText
+ : getState().getMessage().getText());
+ } catch (IllegalArgumentException exception) {
+ Log.wtf(
+ /* tag = */ TAG,
+ /* msg = */ "PrecomputedText setText failed for TextView:" + this,
+ /* tr = */ exception
+ );
+ mPrecomputedText = null;
+ setText(getState().getMessage().getText());
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 07ee9b5d2ff16af5f7c9179377a03a6819f21d4a..d4dd1e70565330f8a4b6f06de56e6fae92313540 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -21,6 +21,8 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -42,7 +44,7 @@ import java.util.Locale;
@RemoteViews.RemoteView
public class NotificationExpandButton extends FrameLayout {
- private View mPillView;
+ private Drawable mPillDrawable;
private TextView mNumberView;
private ImageView mIconView;
private boolean mExpanded;
@@ -73,7 +75,10 @@ public class NotificationExpandButton extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPillView = findViewById(R.id.expand_button_pill);
+
+ final View pillView = findViewById(R.id.expand_button_pill);
+ final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground();
+ mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer);
mNumberView = findViewById(R.id.expand_button_number);
mIconView = findViewById(R.id.expand_button_icon);
}
@@ -156,7 +161,7 @@ public class NotificationExpandButton extends FrameLayout {
private void updateColors() {
if (shouldShowNumber()) {
if (mHighlightPillColor != 0) {
- mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+ mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
}
mIconView.setColorFilter(mHighlightTextColor);
if (mHighlightTextColor != 0) {
@@ -164,7 +169,7 @@ public class NotificationExpandButton extends FrameLayout {
}
} else {
if (mDefaultPillColor != 0) {
- mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+ mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
}
mIconView.setColorFilter(mDefaultTextColor);
if (mDefaultTextColor != 0) {
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index f55d15de1d51460c186e8c0a7dcab3fbe541bd82..e65b4b65945f8f6647c8980a8b65ec02df3fd0eb 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -163,6 +163,8 @@ public class PointerLocationView extends View implements InputDeviceListener,
@UnsupportedAppUsage
private boolean mPrintCoords = true;
+ private float mDensity;
+
public PointerLocationView(Context c) {
super(c);
setFocusableInTouchMode(true);
@@ -365,11 +367,8 @@ public class PointerLocationView extends View implements InputDeviceListener,
drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
- // Draw the orientation arrow.
- float arrowSize = ps.mCoords.toolMajor * 0.7f;
- if (arrowSize < 20) {
- arrowSize = 20;
- }
+ // Draw the orientation arrow, and ensure it has a minimum size of 24dp.
+ final float arrowSize = Math.max(ps.mCoords.toolMajor * 0.7f, 24 * mDensity);
mPaint.setARGB(255, pressureLevel, 255, 0);
float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
* arrowSize);
@@ -398,7 +397,7 @@ public class PointerLocationView extends View implements InputDeviceListener,
canvas.drawCircle(
ps.mCoords.x + orientationVectorX * tiltScale,
ps.mCoords.y + orientationVectorY * tiltScale,
- 3.0f, mPaint);
+ 3.0f * mDensity, mPaint);
// Draw the current bounding box
if (ps.mHasBoundingBox) {
@@ -1003,10 +1002,10 @@ public class PointerLocationView extends View implements InputDeviceListener,
// Compute size by display density.
private void configureDensityDependentFactors() {
- final float density = getResources().getDisplayMetrics().density;
- mTextPaint.setTextSize(10 * density);
- mPaint.setStrokeWidth(1 * density);
- mCurrentPointPaint.setStrokeWidth(1 * density);
- mPathPaint.setStrokeWidth(1 * density);
+ mDensity = getResources().getDisplayMetrics().density;
+ mTextPaint.setTextSize(10 * mDensity);
+ mPaint.setStrokeWidth(1 * mDensity);
+ mCurrentPointPaint.setStrokeWidth(1 * mDensity);
+ mPathPaint.setStrokeWidth(1 * mDensity);
}
}
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 52ffc984c41e3f1d57c898f81ffc339ce16512bf..a513ca535620ffcef7f05d19f5b04fe62962f29a 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -21,6 +21,7 @@ import static android.content.res.Resources.ID_NULL;
import android.annotation.IdRes;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -53,10 +54,13 @@ public class ResolverDrawerLayout extends ViewGroup {
private static final String TAG = "ResolverDrawerLayout";
private MetricsLogger mMetricsLogger;
+
+
/**
- * Max width of the whole drawer layout
+ * Max width of the whole drawer layout and its res id
*/
- private final int mMaxWidth;
+ private int mMaxWidthResId;
+ private int mMaxWidth;
/**
* Max total visible height of views not marked always-show when in the closed/initial state
@@ -152,6 +156,7 @@ public class ResolverDrawerLayout extends ViewGroup {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout,
defStyleAttr, 0);
+ mMaxWidthResId = a.getResourceId(R.styleable.ResolverDrawerLayout_maxWidth, -1);
mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_maxWidth, -1);
mMaxCollapsedHeight = a.getDimensionPixelSize(
R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0);
@@ -1042,6 +1047,18 @@ public class ResolverDrawerLayout extends ViewGroup {
return mAlwaysShowHeight;
}
+ /**
+ * Max width of the drawer needs to be updated after the configuration is changed.
+ * For example, foldables have different layout width when the device is folded and unfolded.
+ */
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mMaxWidthResId > 0) {
+ mMaxWidth = getResources().getDimensionPixelSize(mMaxWidthResId);
+ }
+ }
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = getWidth();
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index f7af67b3b2a89518eae899226e4adf7bd6fba8f9..e9449eb96bb34a6bd1133d98db4f3af8c9ee47e8 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.graphics.Rect;
import android.view.MenuItem;
import android.view.View;
-import android.view.selectiontoolbar.SelectionToolbarManager;
import android.widget.PopupWindow;
import java.util.List;
@@ -89,14 +88,10 @@ public interface FloatingToolbarPopup {
@Nullable PopupWindow.OnDismissListener onDismiss);
/**
- * Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar
- * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
+ * Returns {@link LocalFloatingToolbarPopup} implementation.
*/
static FloatingToolbarPopup createInstance(Context context, View parent) {
- boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
- return enabled
- ? new RemoteFloatingToolbarPopup(context, parent)
- : new LocalFloatingToolbarPopup(context, parent);
+ return new LocalFloatingToolbarPopup(context, parent);
}
}
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/OWNERS b/core/java/com/android/internal/widget/floatingtoolbar/OWNERS
deleted file mode 100644
index ed9425cc26c9dd1cef2f049125f9ae344eb17453..0000000000000000000000000000000000000000
--- a/core/java/com/android/internal/widget/floatingtoolbar/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/view/selectiontoolbar/OWNERS
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
deleted file mode 100644
index 8787c39458b9f1fa779ce24b51abcfdc5a06f707..0000000000000000000000000000000000000000
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ /dev/null
@@ -1,572 +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.internal.widget.floatingtoolbar;
-
-import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiThread;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.MenuItem;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.SelectionToolbarManager;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-
-import com.android.internal.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A popup window used by the floating toolbar to render menu items in the remote system process.
- *
- * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
- * to transition between panels.
- */
-public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
-
- private static final boolean DEBUG =
- Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.VERBOSE);
-
- private static final int TOOLBAR_STATE_SHOWN = 1;
- private static final int TOOLBAR_STATE_HIDDEN = 2;
- private static final int TOOLBAR_STATE_DISMISSED = 3;
-
- @IntDef(prefix = {"TOOLBAR_STATE_"}, value = {
- TOOLBAR_STATE_SHOWN,
- TOOLBAR_STATE_HIDDEN,
- TOOLBAR_STATE_DISMISSED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ToolbarState {
- }
-
- @NonNull
- private final SelectionToolbarManager mSelectionToolbarManager;
- // Parent for the popup window.
- @NonNull
- private final View mParent;
- // A popup window used for showing menu items rendered by the remote system process
- @NonNull
- private final PopupWindow mPopupWindow;
- // The callback to handle remote rendered selection toolbar.
- @NonNull
- private final SelectionToolbarCallbackImpl mSelectionToolbarCallback;
-
- // tracks this popup state.
- private @ToolbarState int mState;
-
- // The token of the current showing floating toolbar.
- private long mFloatingToolbarToken;
- private final Rect mPreviousContentRect = new Rect();
- private List