Snap for 7209848 from d5a19955eb9856372e5b0dd369bc4e6be5683f49 to tm-release
Change-Id: Id26d01534c529e7b8b9ab47d8ba6f686f82ce733
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 5ded446..386c6e2 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -18,9 +18,10 @@
}
public static final class AppSearchManager.SearchContext.Builder {
- ctor public AppSearchManager.SearchContext.Builder();
+ ctor @Deprecated public AppSearchManager.SearchContext.Builder();
+ ctor public AppSearchManager.SearchContext.Builder(@NonNull String);
method @NonNull public android.app.appsearch.AppSearchManager.SearchContext build();
- method @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
+ method @Deprecated @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
}
public interface AppSearchMigrationHelper {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a62bb50..0c6b86b 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -30,17 +30,19 @@
* Provides access to the centralized AppSearch index maintained by the system.
*
* <p>AppSearch is a search library for managing structured data featuring:
+ *
* <ul>
- * <li>A fully offline on-device solution
- * <li>A set of APIs for applications to index documents and retrieve them via full-text search
- * <li>APIs for applications to allow the System to display their content on system UI surfaces
- * <li>Similarly, APIs for applications to allow the System to share their content with other
- * specified applications.
+ * <li>A fully offline on-device solution
+ * <li>A set of APIs for applications to index documents and retrieve them via full-text search
+ * <li>APIs for applications to allow the System to display their content on system UI surfaces
+ * <li>Similarly, APIs for applications to allow the System to share their content with other
+ * specified applications.
* </ul>
*
* <p>Applications create a database by opening an {@link AppSearchSession}.
*
* <p>Example:
+ *
* <pre>
* AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
*
@@ -51,11 +53,12 @@
* });</pre>
*
* <p>After opening the session, a schema must be set in order to define the organizational
- * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema
- * is composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique
- * type of data.
+ * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is
+ * composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type
+ * of data.
*
* <p>Example:
+ *
* <pre>
* AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
* .addProperty(new StringPropertyConfig.Builder("subject")
@@ -73,15 +76,16 @@
* });</pre>
*
* <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object,
- * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a
- * logical group of documents. For example, a namespace can be created to group documents on a
- * per-account basis. A URI identifies a single document within a namespace. The combination
- * of URI and namespace uniquely identifies a {@link GenericDocument} in the database.
+ * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a logical
+ * group of documents. For example, a namespace can be created to group documents on a per-account
+ * basis. A URI identifies a single document within a namespace. The combination of URI and
+ * namespace uniquely identifies a {@link GenericDocument} in the database.
*
- * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database
- * and indexed by calling {@link AppSearchSession#put}.
+ * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and
+ * indexed by calling {@link AppSearchSession#put}.
*
* <p>Example:
+ *
* <pre>
* // Although for this example we use GenericDocument directly, we recommend extending
* // GenericDocument to create specific types (i.e. Email) with specific setters/getters.
@@ -106,18 +110,12 @@
* and namespace.
*
* <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove
- * operation. Remove operations can be done by URI and namespace via
- * {@link AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)},
- * or by query via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
+ * operation. Remove operations can be done by URI and namespace via {@link
+ * AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)}, or by query via
+ * {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
- /**
- * The default empty database name.
- *
- * @hide
- */
- public static final String DEFAULT_DATABASE_NAME = "";
private final IAppSearchManager mService;
private final Context mContext;
@@ -149,10 +147,42 @@
/** Builder for {@link SearchContext} objects. */
public static final class Builder {
- private String mDatabaseName = DEFAULT_DATABASE_NAME;
+ private String mDatabaseName;
private boolean mBuilt = false;
/**
+ * TODO(b/181887768): This method exists only for dogfooder transition and must be
+ * removed.
+ *
+ * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
+ * method exists only for dogfooder transition and must be removed.
+ */
+ @Deprecated
+ public Builder() {
+ mDatabaseName = "";
+ }
+
+ /**
+ * Creates a new {@link SearchContext.Builder}.
+ *
+ * <p>{@link AppSearchSession} will create or open a database under the given name.
+ *
+ * <p>Databases with different names are fully separate with distinct types, namespaces,
+ * and data.
+ *
+ * <p>Database name cannot contain {@code '/'}.
+ *
+ * @param databaseName The name of the database.
+ * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+ */
+ public Builder(@NonNull String databaseName) {
+ Objects.requireNonNull(databaseName);
+ Preconditions.checkArgument(
+ !databaseName.contains("/"), "Database name cannot contain '/'");
+ mDatabaseName = databaseName;
+ }
+
+ /**
* Sets the name of the database associated with {@link AppSearchSession}.
*
* <p>{@link AppSearchSession} will create or open a database under the given name.
@@ -164,16 +194,21 @@
*
* <p>If not specified, defaults to the empty string.
*
+ * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
+ * removed.
+ *
* @param databaseName The name of the database.
* @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+ * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
+ * method exists only for dogfooder transition and must be removed.
*/
+ @Deprecated
@NonNull
public Builder setDatabaseName(@NonNull String databaseName) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Objects.requireNonNull(databaseName);
- if (databaseName.contains("/")) {
- throw new IllegalArgumentException("Database name cannot contain '/'");
- }
+ Preconditions.checkArgument(
+ !databaseName.contains("/"), "Database name cannot contain '/'");
mDatabaseName = databaseName;
return this;
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index f379739..486acb4 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -241,6 +241,7 @@
documentBundles.add(documents.get(i).getBundle());
}
try {
+ // TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency
mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
new IAppSearchBatchResultCallback.Stub() {
public void onResult(AppSearchBatchResult result) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
new file mode 100644
index 0000000..aeb66d9
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.appsearch.external.localstorage.AppSearchLogger;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
+
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Logger Implementation using Westworld.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @hide
+ */
+public final class PlatformLogger implements AppSearchLogger {
+ private static final String TAG = "AppSearchPlatformLogger";
+
+ // Context of the system service.
+ private final Context mContext;
+
+ // User ID of the caller who we're logging for.
+ private final int mUserId;
+
+ // Configuration for the logger
+ private final Config mConfig;
+
+ private final Random mRng = new Random();
+ private final Object mLock = new Object();
+
+ /**
+ * SparseArray to track how many stats we skipped due to
+ * {@link Config#mMinTimeIntervalBetweenSamplesMillis}.
+ *
+ * <p> We can have correct extrapolated number by adding those counts back when we log
+ * the same type of stats next time. E.g. the true count of an event could be estimated as:
+ * SUM(sampling_ratio * (num_skipped_sample + 1)) as est_count
+ *
+ * <p>The key to the SparseArray is {@link CallStats.CallType}
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mSkippedSampleCountLocked =
+ new SparseIntArray();
+
+ /**
+ * Map to cache the packageUid for each package.
+ *
+ * <p>It maps packageName to packageUid.
+ *
+ * <p>The entry will be removed whenever the app gets uninstalled
+ */
+ @GuardedBy("mLock")
+ private final Map<String, Integer> mPackageUidCacheLocked =
+ new ArrayMap<>();
+
+ /**
+ * Elapsed time for last stats logged from boot in millis
+ */
+ @GuardedBy("mLock")
+ private long mLastPushTimeMillisLocked = 0;
+
+ /**
+ * Class to configure the {@link PlatformLogger}
+ */
+ public static final class Config {
+ // Minimum time interval (in millis) since last message logged to Westworld before
+ // logging again.
+ private final long mMinTimeIntervalBetweenSamplesMillis;
+
+ // Default sampling ratio for all types of stats
+ private final int mDefaultSamplingRatio;
+
+ /**
+ * Sampling ratios for different types of stats
+ *
+ * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
+ * {@link CallStats.CallType}
+ *
+ * <p>If sampling ratio is missing for certain stats type,
+ * {@link Config#mDefaultSamplingRatio} will be used.
+ *
+ * <p>E.g. sampling ratio=10 means that one out of every 10 stats was logged. If sampling
+ * ratio is 1, we will log each sample and it acts as if the sampling is disabled.
+ */
+ @NonNull
+ private final SparseIntArray mSamplingRatios;
+
+ /**
+ * Configuration for {@link PlatformLogger}
+ *
+ * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
+ * required for two consecutive stats logged
+ * @param defaultSamplingRatio default sampling ratio
+ * @param samplingRatios SparseArray to customize sampling ratio for
+ * different stat types
+ */
+ public Config(long minTimeIntervalBetweenSamplesMillis,
+ int defaultSamplingRatio,
+ @Nullable SparseIntArray samplingRatios) {
+ mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
+ mDefaultSamplingRatio = defaultSamplingRatio;
+ if (samplingRatios != null) {
+ mSamplingRatios = samplingRatios;
+ } else {
+ mSamplingRatios = new SparseIntArray();
+ }
+ }
+ }
+
+ /**
+ * Helper class to hold platform specific stats for Westworld.
+ */
+ static final class ExtraStats {
+ // UID for the calling package of the stats.
+ final int mPackageUid;
+ // sampling ratio for the call type of the stats.
+ final int mSamplingRatio;
+ // number of samplings skipped before the current one for the same call type.
+ final int mSkippedSampleCount;
+
+ ExtraStats(int packageUid, int samplingRatio, int skippedSampleCount) {
+ mPackageUid = packageUid;
+ mSamplingRatio = samplingRatio;
+ mSkippedSampleCount = skippedSampleCount;
+ }
+ }
+
+ /**
+ * Westworld constructor
+ */
+ public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) {
+ mContext = Preconditions.checkNotNull(context);
+ mConfig = Preconditions.checkNotNull(config);
+ mUserId = userId;
+ }
+
+ /** Logs {@link CallStats}. */
+ @Override
+ public void logStats(@NonNull CallStats stats) {
+ Preconditions.checkNotNull(stats);
+ synchronized (mLock) {
+ if (shouldLogForTypeLocked(stats.getCallType())) {
+ logToWestworldLocked(stats);
+ }
+ }
+ }
+
+ /** Logs {@link PutDocumentStats}. */
+ @Override
+ public void logStats(@NonNull PutDocumentStats stats) {
+ Preconditions.checkNotNull(stats);
+ synchronized (mLock) {
+ if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
+ logToWestworldLocked(stats);
+ }
+ }
+ }
+
+ /**
+ * Removes cached UID for package.
+ *
+ * @return removed UID for the package, or {@code INVALID_UID} if package was not previously
+ * cached.
+ */
+ public int removeCachedUidForPackage(@NonNull String packageName) {
+ // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
+ Preconditions.checkNotNull(packageName);
+ synchronized (mLock) {
+ Integer uid = mPackageUidCacheLocked.remove(packageName);
+ return uid != null ? uid : Process.INVALID_UID;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void logToWestworldLocked(@NonNull CallStats stats) {
+ mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+ ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
+ stats.getCallType());
+ /* TODO(b/173532925) Log the CallStats to Westworld
+ stats.log(..., samplingRatio, skippedSampleCount, ...)
+ */
+ }
+
+ @GuardedBy("mLock")
+ private void logToWestworldLocked(@NonNull PutDocumentStats stats) {
+ mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+ ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+ /* TODO(b/173532925) Log the PutDocumentStats to Westworld
+ stats.log(..., samplingRatio, skippedSampleCount, ...)
+ */
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ ExtraStats createExtraStatsLocked(@NonNull String packageName,
+ @CallStats.CallType int callType) {
+ int packageUid = getPackageUidAsUserLocked(packageName);
+ int samplingRatio = mConfig.mSamplingRatios.get(callType,
+ mConfig.mDefaultSamplingRatio);
+
+ int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
+ /*valueOfKeyIfNotFound=*/ 0);
+ mSkippedSampleCountLocked.put(callType, 0);
+
+ return new ExtraStats(packageUid, samplingRatio, skippedSampleCount);
+ }
+
+ /**
+ * Checks if this stats should be logged.
+ *
+ * <p>It won't be logged if it is "sampled" out, or it is too close to the previous logged
+ * stats.
+ */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
+ int samplingRatio = mConfig.mSamplingRatios.get(callType,
+ mConfig.mDefaultSamplingRatio);
+
+ // Sampling
+ if (!shouldSample(samplingRatio)) {
+ return false;
+ }
+
+ // Rate limiting
+ // Check the timestamp to see if it is too close to last logged sample
+ long currentTimeMillis = SystemClock.elapsedRealtime();
+ if (mLastPushTimeMillisLocked
+ > currentTimeMillis - mConfig.mMinTimeIntervalBetweenSamplesMillis) {
+ int count = mSkippedSampleCountLocked.get(callType, /*valueOfKeyIfNotFound=*/ 0);
+ ++count;
+ mSkippedSampleCountLocked.put(callType, count);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the stats should be "sampled"
+ *
+ * @param samplingRatio sampling ratio
+ * @return if the stats should be sampled
+ */
+ private boolean shouldSample(int samplingRatio) {
+ if (samplingRatio <= 0) {
+ return false;
+ }
+
+ return mRng.nextInt((int) samplingRatio) == 0;
+ }
+
+ /**
+ * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
+ * find the UID.
+ */
+ @GuardedBy("mLock")
+ private int getPackageUidAsUserLocked(@NonNull String packageName) {
+ Integer packageUid = mPackageUidCacheLocked.get(packageName);
+ if (packageUid != null) {
+ return packageUid;
+ }
+
+ // TODO(b/173532925) since VisibilityStore has the same method, we can make this a
+ // utility function
+ try {
+ packageUid = mContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
+ mPackageUidCacheLocked.put(packageName, packageUid);
+ return packageUid;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package doesn't exist, continue
+ }
+ return Process.INVALID_UID;
+ }
+
+ //
+ // Functions below are used for tests only
+ //
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void setLastPushTimeMillisLocked(long lastPushElapsedTimeMillis) {
+ mLastPushTimeMillisLocked = lastPushElapsedTimeMillis;
+ }
+}
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 260c8a4..f5bee6c 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -258,7 +258,7 @@
public void runDisableAppDataIsolation() throws RemoteException {
if (!SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
throw new IllegalStateException("Storage app data isolation is not enabled.");
}
final String pkgName = nextArg();
diff --git a/core/api/current.txt b/core/api/current.txt
index 1acd0fa..6735268 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3027,6 +3027,7 @@
field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc
field public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; // 0xd
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
+ field public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15; // 0xf
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
@@ -9909,6 +9910,7 @@
method public String getHtmlText();
method public android.content.Intent getIntent();
method public CharSequence getText();
+ method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
method public android.net.Uri getUri();
}
@@ -9918,6 +9920,8 @@
method public static boolean compareMimeTypes(String, String);
method public int describeContents();
method public String[] filterMimeTypes(String);
+ method public int getClassificationStatus();
+ method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(@NonNull String);
method public android.os.PersistableBundle getExtras();
method public CharSequence getLabel();
method public String getMimeType(int);
@@ -9927,6 +9931,9 @@
method public boolean isStyledText();
method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CLASSIFICATION_COMPLETE = 3; // 0x3
+ field public static final int CLASSIFICATION_NOT_COMPLETE = 1; // 0x1
+ field public static final int CLASSIFICATION_NOT_PERFORMED = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
field public static final String MIMETYPE_TEXT_HTML = "text/html";
field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
@@ -10954,6 +10961,7 @@
field public static final String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED";
field public static final String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
field public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+ field public static final String ACTION_MANAGE_UNUSED_APPS = "android.intent.action.MANAGE_UNUSED_APPS";
field public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
field public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
field public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
@@ -21889,6 +21897,7 @@
method @NonNull public java.util.List<byte[]> getOfflineLicenseKeySetIds();
method public int getOfflineLicenseState(@NonNull byte[]);
method public int getOpenSessionCount();
+ method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
method @NonNull public byte[] getPropertyByteArray(String);
method @NonNull public String getPropertyString(@NonNull String);
method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
@@ -36863,11 +36872,12 @@
method @NonNull public static android.content.Intent createInstallIntent();
method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy);
method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException;
- method @NonNull public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException;
+ method @NonNull @WorkerThread public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException;
method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException;
method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String);
- method public static boolean isCredentialManagementApp(@NonNull android.content.Context);
+ method @WorkerThread public static boolean isCredentialManagementApp(@NonNull android.content.Context);
method public static boolean isKeyAlgorithmSupported(@NonNull String);
+ method @RequiresPermission(value="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP", conditional=true) @WorkerThread public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
field public static final String ACTION_KEYCHAIN_CHANGED = "android.security.action.KEYCHAIN_CHANGED";
field public static final String ACTION_KEY_ACCESS_CHANGED = "android.security.action.KEY_ACCESS_CHANGED";
field @Deprecated public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
@@ -51913,6 +51923,7 @@
field public static final String TYPE_PHONE = "phone";
field public static final String TYPE_UNKNOWN = "";
field public static final String TYPE_URL = "url";
+ field public static final String WIDGET_TYPE_CLIPBOARD = "clipboard";
field public static final String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
field public static final String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
field public static final String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
@@ -55027,6 +55038,7 @@
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
method public void setString(@IdRes int, String, String);
@@ -55066,6 +55078,25 @@
ctor public RemoteViews.ActionException(String);
}
+ public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getItemCount();
+ method public long getItemId(int);
+ method @NonNull public android.widget.RemoteViews getItemView(int);
+ method public int getViewTypeCount();
+ method public boolean hasStableIds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews.RemoteCollectionItems> CREATOR;
+ }
+
+ public static final class RemoteViews.RemoteCollectionItems.Builder {
+ ctor public RemoteViews.RemoteCollectionItems.Builder();
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder addItem(long, @NonNull android.widget.RemoteViews);
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems build();
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setHasStableIds(boolean);
+ method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setViewTypeCount(int);
+ }
+
public static class RemoteViews.RemoteResponse {
ctor public RemoteViews.RemoteResponse();
method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 18b0a43..a140e8a 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -55,6 +55,10 @@
package android.content {
+ public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+ method @NonNull public static android.net.Uri createContentUriAsUser(@NonNull android.net.Uri, @NonNull android.os.UserHandle);
+ }
+
public abstract class Context {
method @NonNull public android.os.UserHandle getUser();
}
@@ -203,6 +207,16 @@
method @Nullable public byte[] getWatchlistConfigHash();
}
+ public class PacProxyManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void addPacProxyInstalledListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.PacProxyManager.PacProxyInstalledListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void removePacProxyInstalledListener(@NonNull android.net.PacProxyManager.PacProxyInstalledListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setCurrentProxyScriptUrl(@Nullable android.net.ProxyInfo);
+ }
+
+ public static interface PacProxyManager.PacProxyInstalledListener {
+ method public void onPacProxyInstalled(@Nullable android.net.Network, @NonNull android.net.ProxyInfo);
+ }
+
public final class Proxy {
method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8744ce1..560dded 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1888,6 +1888,7 @@
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
@@ -2064,6 +2065,54 @@
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraints> CREATOR;
}
+ public final class OobData implements android.os.Parcelable {
+ method @NonNull public static android.bluetooth.OobData.ClassicBuilder createClassicBuilder(@NonNull byte[], @NonNull byte[], @NonNull byte[]);
+ method @NonNull public static android.bluetooth.OobData.LeBuilder createLeBuilder(@NonNull byte[], @NonNull byte[], int);
+ method @NonNull public byte[] getClassOfDevice();
+ method @NonNull public byte[] getClassicLength();
+ method @NonNull public byte[] getConfirmationHash();
+ method @NonNull public byte[] getDeviceAddressWithType();
+ method @Nullable public byte[] getDeviceName();
+ method @Nullable public byte[] getLeAppearance();
+ method @NonNull public int getLeDeviceRole();
+ method @NonNull public int getLeFlags();
+ method @Nullable public byte[] getLeTemporaryKey();
+ method @NonNull public byte[] getRandomizerHash();
+ field public static final int CLASS_OF_DEVICE_OCTETS = 3; // 0x3
+ field public static final int CONFIRMATION_OCTETS = 16; // 0x10
+ field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
+ field public static final int DEVICE_ADDRESS_OCTETS = 7; // 0x7
+ field public static final int LE_APPEARANCE_OCTETS = 2; // 0x2
+ field public static final int LE_DEVICE_FLAG_OCTETS = 1; // 0x1
+ field public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 3; // 0x3
+ field public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 2; // 0x2
+ field public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 1; // 0x1
+ field public static final int LE_DEVICE_ROLE_OCTETS = 1; // 0x1
+ field public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0; // 0x0
+ field public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 2; // 0x2
+ field public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 1; // 0x1
+ field public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0; // 0x0
+ field public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 3; // 0x3
+ field public static final int LE_FLAG_SIMULTANEOUS_HOST = 4; // 0x4
+ field public static final int LE_TK_OCTETS = 16; // 0x10
+ field public static final int RANDOMIZER_OCTETS = 16; // 0x10
+ }
+
+ public static final class OobData.ClassicBuilder {
+ method @NonNull public android.bluetooth.OobData build();
+ method @NonNull public android.bluetooth.OobData.ClassicBuilder setClassOfDevice(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.ClassicBuilder setDeviceName(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.ClassicBuilder setRandomizerHash(@NonNull byte[]);
+ }
+
+ public static final class OobData.LeBuilder {
+ method @NonNull public android.bluetooth.OobData build();
+ method @NonNull public android.bluetooth.OobData.LeBuilder setDeviceName(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.LeBuilder setLeFlags(int);
+ method @NonNull public android.bluetooth.OobData.LeBuilder setLeTemporaryKey(@NonNull byte[]);
+ method @NonNull public android.bluetooth.OobData.LeBuilder setRandomizerHash(@NonNull byte[]);
+ }
+
}
package android.bluetooth.le {
@@ -5145,6 +5194,21 @@
field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
}
+ public final class MediaRouter2 {
+ method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
+ method @Nullable public String getClientPackageName();
+ method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
+ method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+ method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
+ method public void startScan();
+ method public void stopScan();
+ method public void transfer(@NonNull android.media.MediaRouter2.RoutingController, @NonNull android.media.MediaRoute2Info);
+ }
+
+ public abstract static class MediaRouter2.RouteCallback {
+ method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>);
+ }
+
public class PlayerProxy {
method public void pause();
method public void setPan(float);
@@ -9630,10 +9694,28 @@
package android.service.displayhash {
+ public final class DisplayHashParams implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.util.Size getBufferSize();
+ method public boolean isBufferScaleWithFiltering();
+ method public boolean isGrayscaleBuffer();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.displayhash.DisplayHashParams> CREATOR;
+ }
+
+ public static final class DisplayHashParams.Builder {
+ ctor public DisplayHashParams.Builder();
+ method @NonNull public android.service.displayhash.DisplayHashParams build();
+ method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean);
+ method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferSize(int, int);
+ method @NonNull public android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean);
+ }
+
public abstract class DisplayHasherService extends android.app.Service {
ctor public DisplayHasherService();
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Nullable public abstract void onGenerateDisplayHash(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String, @NonNull android.view.displayhash.DisplayHashResultCallback);
+ method @NonNull public abstract java.util.Map<java.lang.String,android.service.displayhash.DisplayHashParams> onGetDisplayHashAlgorithms();
method @Nullable public abstract android.view.displayhash.VerifiedDisplayHash onVerifyDisplayHash(@NonNull byte[], @NonNull android.view.displayhash.DisplayHash);
field public static final String SERVICE_INTERFACE = "android.service.displayhash.DisplayHasherService";
}
@@ -11616,7 +11698,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
- method public boolean isNrDualConnectivityEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNrDualConnectivityEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11702,6 +11784,7 @@
field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+ field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9fde791..1587475 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1380,10 +1380,6 @@
method public int getMaxMacroBlocks();
}
- public final class MediaDrm implements java.lang.AutoCloseable {
- method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
- }
-
public final class MediaRoute2Info implements android.os.Parcelable {
method @NonNull public String getOriginalId();
}
@@ -1950,8 +1946,7 @@
package android.security {
public final class KeyChain {
- method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
- method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
+ method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") @WorkerThread public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
}
public class KeyStoreException extends java.lang.Exception {
@@ -2736,6 +2731,7 @@
public final class InputMethodManager {
method public int getDisplayId();
+ method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method public boolean isInputMethodPickerShown();
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index dab4a5d..d536821 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -555,6 +555,11 @@
*/
public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14;
+ /**
+ * Action to dismiss the notification shade
+ */
+ public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3af0763..0358fe56 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2586,9 +2586,7 @@
@Override
public Context createTokenContext(@NonNull IBinder token, @NonNull Display display) {
if (display == null) {
- throw new UnsupportedOperationException("Token context can only be created from "
- + "other visual contexts, such as Activity or one created with "
- + "Context#createDisplayContext(Display)");
+ throw new IllegalArgumentException("Display must not be null");
}
final ContextImpl tokenContext = createBaseWindowContext(token, display);
tokenContext.setResources(createWindowContextResources());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2b45723..a3a8a5e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5011,9 +5011,13 @@
boolean showProgress = handleProgressBar(contentView, ex, p);
boolean hasSecondLine = showProgress;
if (p.hasTitle()) {
- contentView.setViewVisibility(R.id.title, View.VISIBLE);
- contentView.setTextViewText(R.id.title, processTextSpans(p.title));
- setTextViewColorPrimary(contentView, R.id.title, p);
+ contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
+ contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
+ setTextViewColorPrimary(contentView, p.mTitleViewId, p);
+ } else if (p.mTitleViewId != R.id.title) {
+ // This alternate title view ID is not cleared by resetStandardTemplate
+ contentView.setViewVisibility(p.mTitleViewId, View.GONE);
+ contentView.setTextViewText(p.mTitleViewId, null);
}
if (p.text != null && p.text.length() != 0
&& (!showProgress || p.mAllowTextWithProgress)) {
@@ -5307,7 +5311,7 @@
contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors except when low-priority mode prevents that
- if (!p.forceDefaultColor) {
+ if (!p.mReduceHighlights) {
textColor = getBackgroundColor(p);
pillColor = getAccentColor(p);
}
@@ -5441,6 +5445,11 @@
// keep the divider visible between that title and the next text element.
return true;
}
+ if (p.mHideAppName) {
+ // The app name is being hidden, so we definitely want to return here.
+ // Assume that there is a title which will replace it in the header.
+ return p.hasTitle();
+ }
contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
@@ -5817,7 +5826,7 @@
*
* @hide
*/
- public RemoteViews makeNotificationHeader() {
+ public RemoteViews makeNotificationGroupHeader() {
return makeNotificationHeader(mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
.fillTextsFrom(this));
@@ -5943,7 +5952,7 @@
.viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
.fillTextsFrom(this);
if (isLowPriority) {
- params.forceDefaultColor();
+ params.reduceHighlights();
}
view = makeNotificationHeader(params);
view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
@@ -5966,7 +5975,7 @@
public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
- .forceDefaultColor()
+ .reduceHighlights()
.fillTextsFrom(this);
if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) {
p.summaryText(createSummaryText());
@@ -6306,7 +6315,9 @@
* @param p the template params to inflate this with
*/
private @ColorInt int getRawColor(StandardTemplateParams p) {
- if (p.forceDefaultColor) {
+ // When notifications are theme-tinted, the raw color is only used for the icon, so go
+ // ahead and keep that color instead of changing the color for minimized notifs.
+ if (p.mReduceHighlights && !mTintWithThemeAccent) {
return COLOR_DEFAULT;
}
return mN.color;
@@ -6583,10 +6594,6 @@
return R.layout.notification_template_material_conversation;
}
- private int getCallLayoutResource() {
- return R.layout.notification_template_material_call;
- }
-
private int getActionLayoutResource() {
return R.layout.notification_material_action;
}
@@ -9329,7 +9336,7 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- return makeCallLayout();
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
}
/**
@@ -9337,14 +9344,14 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- return makeCallLayout();
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
/**
* @hide
*/
public RemoteViews makeBigContentView() {
- return makeCallLayout();
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
}
@NonNull
@@ -9443,8 +9450,10 @@
return resultActions;
}
- private RemoteViews makeCallLayout() {
+ private RemoteViews makeCallLayout(int viewType) {
+ final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
Bundle extras = mBuilder.mN.extras;
+ CharSequence title = mPerson != null ? mPerson.getName() : null;
CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
if (text == null) {
text = getDefaultText();
@@ -9452,20 +9461,30 @@
// Bind standard template
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(viewType)
.callStyleActions(true)
.allowTextWithProgress(true)
.hideLargeIcon(true)
+ .hideAppName(isCollapsed)
+ .titleViewId(R.id.conversation_text)
+ .title(title)
.text(text)
.summaryText(mBuilder.processLegacyText(mVerificationText));
mBuilder.mActions = getActionsListWithSystemActions();
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getCallLayoutResource(), p, null /* result */);
+ final RemoteViews contentView;
+ if (isCollapsed) {
+ contentView = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_call, p, null /* result */);
+ } else {
+ contentView = mBuilder.applyStandardTemplateWithActions(
+ R.layout.notification_template_material_big_call, p, null /* result */);
+ }
// Bind some extra conversation-specific header fields.
- mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
- mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
- contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+ if (!p.mHideAppName) {
+ mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+ contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+ }
bindCallerVerification(contentView, p);
// Bind some custom CallLayout properties
@@ -12142,12 +12161,13 @@
public static int VIEW_TYPE_NORMAL = 1;
public static int VIEW_TYPE_BIG = 2;
public static int VIEW_TYPE_HEADS_UP = 3;
- public static int VIEW_TYPE_MINIMIZED = 4;
- public static int VIEW_TYPE_PUBLIC = 5;
- public static int VIEW_TYPE_GROUP_HEADER = 6;
+ public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state
+ public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version
+ public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
int mViewType = VIEW_TYPE_UNSPECIFIED;
boolean mHeaderless;
+ boolean mHideAppName;
boolean mHideTitle;
boolean mHideActions;
boolean mHideProgress;
@@ -12155,6 +12175,7 @@
boolean mPromotePicture;
boolean mCallStyleActions;
boolean mAllowTextWithProgress;
+ int mTitleViewId;
int mTextViewId;
CharSequence title;
CharSequence text;
@@ -12163,11 +12184,12 @@
int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
boolean hideLargeIcon;
boolean allowColorization = true;
- boolean forceDefaultColor = false;
+ boolean mReduceHighlights = false;
final StandardTemplateParams reset() {
mViewType = VIEW_TYPE_UNSPECIFIED;
mHeaderless = false;
+ mHideAppName = false;
mHideTitle = false;
mHideActions = false;
mHideProgress = false;
@@ -12175,6 +12197,7 @@
mPromotePicture = false;
mCallStyleActions = false;
mAllowTextWithProgress = false;
+ mTitleViewId = R.id.title;
mTextViewId = R.id.text;
title = null;
text = null;
@@ -12182,7 +12205,7 @@
headerTextSecondary = null;
maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
allowColorization = true;
- forceDefaultColor = false;
+ mReduceHighlights = false;
return this;
}
@@ -12200,6 +12223,11 @@
return this;
}
+ public StandardTemplateParams hideAppName(boolean hideAppName) {
+ mHideAppName = hideAppName;
+ return this;
+ }
+
final StandardTemplateParams hideActions(boolean hideActions) {
this.mHideActions = hideActions;
return this;
@@ -12235,6 +12263,11 @@
return this;
}
+ public StandardTemplateParams titleViewId(int titleViewId) {
+ mTitleViewId = titleViewId;
+ return this;
+ }
+
public StandardTemplateParams textViewId(int textViewId) {
mTextViewId = textViewId;
return this;
@@ -12270,8 +12303,8 @@
return this;
}
- final StandardTemplateParams forceDefaultColor() {
- this.forceDefaultColor = true;
+ final StandardTemplateParams reduceHighlights() {
+ this.mReduceHighlights = true;
return this;
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 43c14a9..31b0d41 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -129,11 +129,13 @@
import android.net.IEthernetManager;
import android.net.IIpSecService;
import android.net.INetworkPolicyManager;
+import android.net.IPacProxyManager;
import android.net.IVpnManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
+import android.net.PacProxyManager;
import android.net.TetheringManager;
import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
@@ -374,6 +376,15 @@
// (which extends it).
SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
+ registerService(Context.PAC_PROXY_SERVICE, PacProxyManager.class,
+ new CachedServiceFetcher<PacProxyManager>() {
+ @Override
+ public PacProxyManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.PAC_PROXY_SERVICE);
+ IPacProxyManager service = IPacProxyManager.Stub.asInterface(b);
+ return new PacProxyManager(ctx.getOuterContext(), service);
+ }});
+
registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
@Override
public IBinder createService() throws ServiceNotFoundException {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index ec94faa..07dbdce 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1311,7 +1311,6 @@
* the bonding process completes, and its result.
* <p>Android system services will handle the necessary user interactions
* to confirm and complete the bonding process.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
* @param transport The transport to use for the pairing procedure.
* @return false on immediate error, true if bonding will begin
@@ -1319,8 +1318,9 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean createBond(int transport) {
- return createBondOutOfBand(transport, null);
+ return createBondInternal(transport, null, null);
}
/**
@@ -1334,22 +1334,39 @@
* <p>Android system services will handle the necessary user interactions
* to confirm and complete the bonding process.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+ * <p>There are two possible versions of OOB Data. This data can come in as
+ * P192 or P256. This is a reference to the cryptography used to generate the key.
+ * The caller may pass one or both. If both types of data are passed, then the
+ * P256 data will be preferred, and thus used.
*
* @param transport - Transport to use
- * @param oobData - Out Of Band data
+ * @param remoteP192Data - Out Of Band data (P192) or null
+ * @param remoteP256Data - Out Of Band data (P256) or null
* @return false on immediate error, true if bonding will begin
* @hide
*/
- public boolean createBondOutOfBand(int transport, OobData oobData) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data,
+ @Nullable OobData remoteP256Data) {
+ if (remoteP192Data == null && remoteP256Data == null) {
+ throw new IllegalArgumentException(
+ "One or both arguments for the OOB data types are required to not be null."
+ + " Please use createBond() instead if you do not have OOB data to pass.");
+ }
+ return createBondInternal(transport, remoteP192Data, remoteP256Data);
+ }
+
+ private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data,
+ @Nullable OobData remoteP256Data) {
final IBluetooth service = sService;
if (service == null) {
Log.w(TAG, "BT not enabled, createBondOutOfBand failed");
return false;
}
try {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- return service.createBond(this, transport, oobData, adapter.getOpPackageName());
+ return service.createBond(this, transport, remoteP192Data, remoteP256Data,
+ BluetoothAdapter.getDefaultAdapter().getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
@@ -1380,27 +1397,6 @@
}
/**
- * Set the Out Of Band data for a remote device to be used later
- * in the pairing mechanism. Users can obtain this data through other
- * trusted channels
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
- *
- * @param hash Simple Secure pairing hash
- * @param randomizer The random key obtained using OOB
- * @return false on error; true otherwise
- * @hide
- */
- public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
- //TODO(BT)
- /*
- try {
- return sService.setDeviceOutOfBandData(this, hash, randomizer);
- } catch (RemoteException e) {Log.e(TAG, "", e);} */
- return false;
- }
-
- /**
* Cancel an in-progress bonding request started with {@link #createBond}.
*
* @return true on success, false on error
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
index 0d0c6ab..9810746 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,88 +16,950 @@
package android.bluetooth;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
+import java.lang.IllegalArgumentException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Out Of Band Data for Bluetooth device pairing.
*
* <p>This object represents optional data obtained from a remote device through
- * an out-of-band channel (eg. NFC).
+ * an out-of-band channel (eg. NFC, QR).
+ *
+ * <p>References:
+ * NFC AD Forum SSP 1.1 (AD)
+ * {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf}
+ * Core Specification Supplement (CSS) V9
+ *
+ * <p>There are several BR/EDR Examples
+ *
+ * <p>Negotiated Handover:
+ * Bluetooth Carrier Configuration Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Simple Pairing Hash C
+ * - Simple Pairing Randomizer R
+ * - Service Class UUID
+ * - Bluetooth Local Name
+ *
+ * <p>Static Handover:
+ * Bluetooth Carrier Configuration Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Service Class UUID
+ * - Bluetooth Local Name
+ *
+ * <p>Simplified Tag Format for Single BT Carrier:
+ * Bluetooth OOB Data Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Service Class UUID
+ * - Bluetooth Local Name
*
* @hide
*/
-public class OobData implements Parcelable {
- private byte[] mLeBluetoothDeviceAddress;
- private byte[] mSecurityManagerTk;
- private byte[] mLeSecureConnectionsConfirmation;
- private byte[] mLeSecureConnectionsRandom;
+@SystemApi
+public final class OobData implements Parcelable {
- public byte[] getLeBluetoothDeviceAddress() {
- return mLeBluetoothDeviceAddress;
+ private static final String TAG = "OobData";
+ /** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */
+ @SystemApi
+ private static final int OOB_LENGTH_OCTETS = 2;
+ /**
+ * The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1).
+ * (AD 3.1.2) (CSS 1.6.2)
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_ADDRESS_OCTETS = 7;
+ /** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int CLASS_OF_DEVICE_OCTETS = 3;
+ /** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int CONFIRMATION_OCTETS = 16;
+ /** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int RANDOMIZER_OCTETS = 16;
+ /** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_OCTETS = 1;
+ /** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_TK_OCTETS = 16;
+ /** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_APPEARANCE_OCTETS = 2;
+ /** The {@link OobData#mLeFlags} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value.
+
+ // Le Roles
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "LE_DEVICE_ROLE_" },
+ value = {
+ LE_DEVICE_ROLE_PERIPHERAL_ONLY,
+ LE_DEVICE_ROLE_CENTRAL_ONLY,
+ LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL,
+ LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL
+ }
+ )
+ public @interface LeRole {}
+
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03;
+
+ // Le Flags
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "LE_FLAG_" },
+ value = {
+ LE_FLAG_LIMITED_DISCOVERY_MODE,
+ LE_FLAG_GENERAL_DISCOVERY_MODE,
+ LE_FLAG_BREDR_NOT_SUPPORTED,
+ LE_FLAG_SIMULTANEOUS_CONTROLLER,
+ LE_FLAG_SIMULTANEOUS_HOST
+ }
+ )
+ public @interface LeFlag {}
+
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04;
+
+ /**
+ * Main creation method for creating a Classic version of {@link OobData}.
+ *
+ * <p>This object will allow the caller to call {@link ClassicBuilder#build()}
+ * to build the data object or add any option information to the builder.
+ *
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * @param classicLength byte array representing the length of data from 8-65535 across 2
+ * octets (0xXXXX).
+ * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+ * that owns the OOB data. (i.e. the originator) [6 octets]
+ *
+ * @return a Classic Builder instance with all the given data set or null.
+ *
+ * @throws IllegalArgumentException if any of the values fail to be set.
+ * @throws NullPointerException if any argument is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public static ClassicBuilder createClassicBuilder(@NonNull byte[] confirmationHash,
+ @NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType) {
+ return new ClassicBuilder(confirmationHash, classicLength, deviceAddressWithType);
}
/**
- * Sets the LE Bluetooth Device Address value to be used during LE pairing.
- * The value shall be 7 bytes. Please see Bluetooth CSSv6, Part A 1.16 for
- * a detailed description.
+ * Main creation method for creating a LE version of {@link OobData}.
+ *
+ * <p>This object will allow the caller to call {@link LeBuilder#build()}
+ * to build the data object or add any option information to the builder.
+ *
+ * @param deviceAddressWithType the LE device address plus the address type (7 octets);
+ * not null.
+ * @param leDeviceRole whether the device supports Peripheral, Central,
+ * Both including preference; not null. (1 octet)
+ * @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is
+ * required for pairing OOB.
+ *
+ * <p>Possible LE Device Role Values:
+ * 0x00 Only Peripheral supported
+ * 0x01 Only Central supported
+ * 0x02 Central & Peripheral supported; Peripheral Preferred
+ * 0x03 Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @return a LeBuilder instance with all the given data set or null.
+ *
+ * @throws IllegalArgumentException if any of the values fail to be set.
+ * @throws NullPointerException if any argument is null.
+ *
+ * @hide
*/
- public void setLeBluetoothDeviceAddress(byte[] leBluetoothDeviceAddress) {
- mLeBluetoothDeviceAddress = leBluetoothDeviceAddress;
- }
-
- public byte[] getSecurityManagerTk() {
- return mSecurityManagerTk;
+ @NonNull
+ @SystemApi
+ public static LeBuilder createLeBuilder(@NonNull byte[] confirmationHash,
+ @NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole) {
+ return new LeBuilder(confirmationHash, deviceAddressWithType, leDeviceRole);
}
/**
- * Sets the Temporary Key value to be used by the LE Security Manager during
- * LE pairing. The value shall be 16 bytes. Please see Bluetooth CSSv6,
- * Part A 1.8 for a detailed description.
+ * Builds an {@link OobData} object and validates that the required combination
+ * of values are present to create the LE specific OobData type.
+ *
+ * @hide
*/
- public void setSecurityManagerTk(byte[] securityManagerTk) {
- mSecurityManagerTk = securityManagerTk;
+ @SystemApi
+ public static final class LeBuilder {
+
+ /**
+ * It is recommended that this Hash C is generated anew for each
+ * pairing.
+ *
+ * <p>It should be noted that on passive NFC this isn't possible as the data is static
+ * and immutable.
+ */
+ private byte[] mConfirmationHash = null;
+
+ /**
+ * Optional, but adds more validity to the pairing.
+ *
+ * <p>If not present a value of 0 is assumed.
+ */
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+
+ /**
+ * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+ *
+ * <p>This is the name that may be displayed to the device user as part of the UI.
+ */
+ private byte[] mDeviceName = null;
+
+ /**
+ * Sets the Bluetooth Device name to be used for UI purposes.
+ *
+ * <p>Optional attribute.
+ *
+ * @param deviceName byte array representing the name, may be 0 in length, not null.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws NullPointerException if deviceName is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setDeviceName(@NonNull byte[] deviceName) {
+ Preconditions.checkNotNull(deviceName);
+ this.mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * The Bluetooth Device Address is the address to which the OOB data belongs.
+ *
+ * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+ *
+ * <p> Address is encoded in Little Endian order.
+ *
+ * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+ */
+ private final byte[] mDeviceAddressWithType;
+
+ /**
+ * During an LE connection establishment, one must be in the Peripheral mode and the other
+ * in the Central role.
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ */
+ private final @LeRole int mLeDeviceRole;
+
+ /**
+ * Temporary key value from the Security Manager.
+ *
+ * <p> Must be {@link LE_TK_OCTETS} in size
+ */
+ private byte[] mLeTemporaryKey = null;
+
+ /**
+ * Defines the representation of the external appearance of the device.
+ *
+ * <p>For example, a mouse, remote control, or keyboard.
+ *
+ * <p>Used for visual on discovering device to represent icon/string/etc...
+ */
+ private byte[] mLeAppearance = null;
+
+ /**
+ * Contains which discoverable mode to use, BR/EDR support and capability.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller).
+ * Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * <b>0x05- 0x07 Reserved</b>
+ */
+ private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default
+
+ /**
+ * Constructing an OobData object for use with LE requires
+ * a LE Device Address and LE Device Role as well as the Confirmation
+ * and optionally, the Randomizer, however it is recommended to use.
+ *
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+ * octets of data. Data is derived from controller/host stack and is required for
+ * pairing OOB.
+ * @param deviceAddressWithType 7 bytes containing the 6 byte address with the 1 byte
+ * address type.
+ * @param leDeviceRole indicating device's role and preferences (Central or Peripheral)
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @throws IllegalArgumentException if deviceAddressWithType is not
+ * {@link LE_DEVICE_ADDRESS_OCTETS} octets
+ * @throws NullPointerException if any argument is null.
+ */
+ private LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType,
+ @LeRole int leDeviceRole) {
+ Preconditions.checkNotNull(confirmationHash);
+ Preconditions.checkNotNull(deviceAddressWithType);
+ if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.CONFIRMATION_OCTETS + " octets in length.");
+ }
+ this.mConfirmationHash = confirmationHash;
+ if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.DEVICE_ADDRESS_OCTETS+ " octets in length.");
+ }
+ this.mDeviceAddressWithType = deviceAddressWithType;
+ if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY
+ || leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) {
+ throw new IllegalArgumentException("leDeviceRole must be a valid value.");
+ }
+ this.mLeDeviceRole = leDeviceRole;
+ }
+
+ /**
+ * Sets the Temporary Key value to be used by the LE Security Manager during
+ * LE pairing.
+ *
+ * @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6,
+ * Part A 1.8 for a detailed description.
+ *
+ * @return {@link OobData#Builder}
+ *
+ * @throws IllegalArgumentException if the leTemporaryKey is an invalid format.
+ * @throws NullinterException if leTemporaryKey is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) {
+ Preconditions.checkNotNull(leTemporaryKey);
+ if (leTemporaryKey.length != LE_TK_OCTETS) {
+ throw new IllegalArgumentException("leTemporaryKey must be "
+ + LE_TK_OCTETS + " octets in length.");
+ }
+ this.mLeTemporaryKey = leTemporaryKey;
+ return this;
+ }
+
+ /**
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+ *
+ * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+ * @throws NullPointerException if randomizerHash is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+ Preconditions.checkNotNull(randomizerHash);
+ if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+ throw new IllegalArgumentException("randomizerHash must be "
+ + OobData.RANDOMIZER_OCTETS + " octets in length.");
+ }
+ this.mRandomizerHash = randomizerHash;
+ return this;
+ }
+
+ /**
+ * Sets the LE Flags necessary for the pairing scenario or discovery mode.
+ *
+ * @param leFlags enum value representing the 1 octet of data about discovery modes.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * 0x05- 0x07 Reserved
+ *
+ * @throws IllegalArgumentException for invalid flag
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setLeFlags(@LeFlag int leFlags) {
+ if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) {
+ throw new IllegalArgumentException("leFlags must be a valid value.");
+ }
+ this.mLeFlags = leFlags;
+ return this;
+ }
+
+ /**
+ * Validates and builds the {@link OobData} object for LE Security.
+ *
+ * @return {@link OobData} with given builder values
+ *
+ * @throws IllegalStateException if either of the 2 required fields were not set.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public OobData build() {
+ final OobData oob =
+ new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole,
+ this.mConfirmationHash);
+
+ // If we have values, set them, otherwise use default
+ oob.mLeTemporaryKey =
+ (this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey;
+ oob.mLeAppearance = (this.mLeAppearance != null)
+ ? this.mLeAppearance : oob.mLeAppearance;
+ oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags;
+ oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+ oob.mRandomizerHash = this.mRandomizerHash;
+ return oob;
+ }
}
- public byte[] getLeSecureConnectionsConfirmation() {
- return mLeSecureConnectionsConfirmation;
+ /**
+ * Builds an {@link OobData} object and validates that the required combination
+ * of values are present to create the Classic specific OobData type.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class ClassicBuilder {
+ // Used by both Classic and LE
+ /**
+ * It is recommended that this Hash C is generated anew for each
+ * pairing.
+ *
+ * <p>It should be noted that on passive NFC this isn't possible as the data is static
+ * and immutable.
+ *
+ * @hide
+ */
+ private byte[] mConfirmationHash = null;
+
+ /**
+ * Optional, but adds more validity to the pairing.
+ *
+ * <p>If not present a value of 0 is assumed.
+ *
+ * @hide
+ */
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+
+ /**
+ * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+ *
+ * <p>This is the name that may be displayed to the device user as part of the UI.
+ *
+ * @hide
+ */
+ private byte[] mDeviceName = null;
+
+ /**
+ * This length value provides the absolute length of total OOB data block used for
+ * Bluetooth BR/EDR
+ *
+ * <p>OOB communication, which includes the length field itself and the Bluetooth
+ * Device Address.
+ *
+ * <p>The minimum length that may be represented in this field is 8.
+ *
+ * @hide
+ */
+ private final byte[] mClassicLength;
+
+ /**
+ * The Bluetooth Device Address is the address to which the OOB data belongs.
+ *
+ * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+ *
+ * <p> Address is encoded in Little Endian order.
+ *
+ * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+ *
+ * @hide
+ */
+ private final byte[] mDeviceAddressWithType;
+
+ /**
+ * Class of Device information is to be used to provide a graphical representation
+ * to the user as part of UI involving operations.
+ *
+ * <p>This is not to be used to determine a particular service can be used.
+ *
+ * <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ *
+ * @hide
+ */
+ private byte[] mClassOfDevice = null;
+
+ /**
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+ * octets of data. Data is derived from controller/host stack and is required for pairing
+ * OOB.
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required
+ * for pairing OOB. Also, randomizerHash may be all 0s or null in which case
+ * it becomes all 0s.
+ * @param classicLength byte array representing the length of data from 8-65535 across 2
+ * octets (0xXXXX). Inclusive of this value in the length.
+ * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+ * that owns the OOB data. (i.e. the originator) [7 octets] this includes the Address Type
+ * as the last octet.
+ *
+ * @throws IllegalArgumentException if any value is not the correct length
+ * @throws NullPointerException if anything passed is null
+ *
+ * @hide
+ */
+ @SystemApi
+ private ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength,
+ @NonNull byte[] deviceAddressWithType) {
+ Preconditions.checkNotNull(confirmationHash);
+ Preconditions.checkNotNull(classicLength);
+ Preconditions.checkNotNull(deviceAddressWithType);
+ if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.CONFIRMATION_OCTETS + " octets in length.");
+ }
+ this.mConfirmationHash = confirmationHash;
+ if (classicLength.length != OOB_LENGTH_OCTETS) {
+ throw new IllegalArgumentException("classicLength must be "
+ + OOB_LENGTH_OCTETS + " octets in length.");
+ }
+ this.mClassicLength = classicLength;
+ if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) {
+ throw new IllegalArgumentException("deviceAddressWithType must be "
+ + DEVICE_ADDRESS_OCTETS + " octets in length.");
+ }
+ this.mDeviceAddressWithType = deviceAddressWithType;
+ }
+
+ /**
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+ *
+ * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+ * @throws NullPointerException if randomizerHash is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+ Preconditions.checkNotNull(randomizerHash);
+ if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+ throw new IllegalArgumentException("randomizerHash must be "
+ + OobData.RANDOMIZER_OCTETS + " octets in length.");
+ }
+ this.mRandomizerHash = randomizerHash;
+ return this;
+ }
+
+ /**
+ * Sets the Bluetooth Device name to be used for UI purposes.
+ *
+ * <p>Optional attribute.
+ *
+ * @param deviceName byte array representing the name, may be 0 in length, not null.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws NullPointerException if deviceName is null
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) {
+ Preconditions.checkNotNull(deviceName);
+ this.mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * Sets the Bluetooth Class of Device; used for UI purposes only.
+ *
+ * <p>Not an indicator of available services!
+ *
+ * <p>Optional attribute.
+ *
+ * @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws IllegalArgumentException if length is not equal to
+ * {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ * @throws NullPointerException if classOfDevice is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) {
+ Preconditions.checkNotNull(classOfDevice);
+ if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) {
+ throw new IllegalArgumentException("classOfDevice must be "
+ + OobData.CLASS_OF_DEVICE_OCTETS + " octets in length.");
+ }
+ this.mClassOfDevice = classOfDevice;
+ return this;
+ }
+
+ /**
+ * Validates and builds the {@link OobDat object for Classic Security.
+ *
+ * @return {@link OobData} with previously given builder values.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public OobData build() {
+ final OobData oob =
+ new OobData(this.mClassicLength, this.mDeviceAddressWithType,
+ this.mConfirmationHash);
+ // If we have values, set them, otherwise use default
+ oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+ oob.mClassOfDevice = (this.mClassOfDevice != null)
+ ? this.mClassOfDevice : oob.mClassOfDevice;
+ oob.mRandomizerHash = this.mRandomizerHash;
+ return oob;
+ }
}
- public void setLeSecureConnectionsConfirmation(byte[] leSecureConnectionsConfirmation) {
- mLeSecureConnectionsConfirmation = leSecureConnectionsConfirmation;
+ // Members (Defaults for Optionals must be set or Parceling fails on NPE)
+ // Both
+ private final byte[] mDeviceAddressWithType;
+ private final byte[] mConfirmationHash;
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+ // Default the name to "Bluetooth Device"
+ private byte[] mDeviceName = new byte[] {
+ // Bluetooth
+ 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68,
+ // <space>Device
+ 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65
+ };
+
+ // Classic
+ private final byte[] mClassicLength;
+ private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS];
+
+ // LE
+ private final @LeRole int mLeDeviceRole;
+ private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS];
+ private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS];
+ private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE;
+
+ /**
+ * @return byte array representing the MAC address of a bluetooth device.
+ * The Address is 6 octets long with a 1 octet address type associated with the address.
+ *
+ * <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type.
+ * For LE there are more choices for Address Type.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getDeviceAddressWithType() {
+ return mDeviceAddressWithType;
}
- public byte[] getLeSecureConnectionsRandom() {
- return mLeSecureConnectionsRandom;
+ /**
+ * @return byte array representing the confirmationHash value
+ * which is used to confirm the identity to the controller.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getConfirmationHash() {
+ return mConfirmationHash;
}
- public void setLeSecureConnectionsRandom(byte[] leSecureConnectionsRandom) {
- mLeSecureConnectionsRandom = leSecureConnectionsRandom;
+ /**
+ * @return byte array representing the randomizerHash value
+ * which is used to verify the identity of the controller.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getRandomizerHash() {
+ return mRandomizerHash;
}
- public OobData() {
+ /**
+ * @return Device Name used for displaying name in UI.
+ *
+ * <p>Also, this will be populated with the LE Local Name if the data is for LE.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * @return byte array representing the oob data length which is the length
+ * of all of the data including these octets.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getClassicLength() {
+ return mClassicLength;
+ }
+
+ /**
+ * @return byte array representing the class of device for UI display.
+ *
+ * <p>Does not indicate services available; for display only.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getClassOfDevice() {
+ return mClassOfDevice;
+ }
+
+ /**
+ * @return Temporary Key used for LE pairing.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getLeTemporaryKey() {
+ return mLeTemporaryKey;
+ }
+
+ /**
+ * @return Appearance used for LE pairing. For use in UI situations
+ * when determining what sort of icons or text to display regarding
+ * the device.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getLeAppearance() {
+ return mLeTemporaryKey;
+ }
+
+ /**
+ * @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller).
+ * Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * <b>0x05- 0x07 Reserved</b>
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @LeFlag
+ public int getLeFlags() {
+ return mLeFlags;
+ }
+
+ /**
+ * @return the supported and preferred roles of the LE device.
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @LeRole
+ public int getLeDeviceRole() {
+ return mLeDeviceRole;
+ }
+
+ /**
+ * Classic Security Constructor
+ */
+ private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType,
+ @NonNull byte[] confirmationHash) {
+ mClassicLength = classicLength;
+ mDeviceAddressWithType = deviceAddressWithType;
+ mConfirmationHash = confirmationHash;
+ mLeDeviceRole = -1; // Satisfy final
+ }
+
+ /**
+ * LE Security Constructor
+ */
+ private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole,
+ @NonNull byte[] confirmationHash) {
+ mDeviceAddressWithType = deviceAddressWithType;
+ mLeDeviceRole = leDeviceRole;
+ mConfirmationHash = confirmationHash;
+ mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final
}
private OobData(Parcel in) {
- mLeBluetoothDeviceAddress = in.createByteArray();
- mSecurityManagerTk = in.createByteArray();
- mLeSecureConnectionsConfirmation = in.createByteArray();
- mLeSecureConnectionsRandom = in.createByteArray();
+ // Both
+ mDeviceAddressWithType = in.createByteArray();
+ mConfirmationHash = in.createByteArray();
+ mRandomizerHash = in.createByteArray();
+ mDeviceName = in.createByteArray();
+
+ // Classic
+ mClassicLength = in.createByteArray();
+ mClassOfDevice = in.createByteArray();
+
+ // LE
+ mLeDeviceRole = in.readInt();
+ mLeTemporaryKey = in.createByteArray();
+ mLeAppearance = in.createByteArray();
+ mLeFlags = in.readInt();
}
+ /**
+ * @hide
+ */
@Override
public int describeContents() {
return 0;
}
+ /**
+ * @hide
+ */
@Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeByteArray(mLeBluetoothDeviceAddress);
- out.writeByteArray(mSecurityManagerTk);
- out.writeByteArray(mLeSecureConnectionsConfirmation);
- out.writeByteArray(mLeSecureConnectionsRandom);
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ // Both
+ // Required
+ out.writeByteArray(mDeviceAddressWithType);
+ // Required
+ out.writeByteArray(mConfirmationHash);
+ // Optional
+ out.writeByteArray(mRandomizerHash);
+ // Optional
+ out.writeByteArray(mDeviceName);
+
+ // Classic
+ // Required
+ out.writeByteArray(mClassicLength);
+ // Optional
+ out.writeByteArray(mClassOfDevice);
+
+ // LE
+ // Required
+ out.writeInt(mLeDeviceRole);
+ // Required
+ out.writeByteArray(mLeTemporaryKey);
+ // Optional
+ out.writeByteArray(mLeAppearance);
+ // Optional
+ out.writeInt(mLeFlags);
}
+ // For Parcelable
public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR =
new Parcelable.Creator<OobData>() {
public OobData createFromParcel(Parcel in) {
@@ -108,4 +970,47 @@
return new OobData[size];
}
};
+
+ /**
+ * @return a {@link String} representation of the OobData object.
+ *
+ * @hide
+ */
+ @Override
+ @NonNull
+ public String toString() {
+ return "OobData: \n\t"
+ // Both
+ + "Device Address With Type: " + toHexString(mDeviceAddressWithType) + "\n\t"
+ + "Confirmation: " + toHexString(mConfirmationHash) + "\n\t"
+ + "Randomizer: " + toHexString(mRandomizerHash) + "\n\t"
+ + "Device Name: " + toHexString(mDeviceName) + "\n\t"
+ // Classic
+ + "OobData Length: " + toHexString(mClassicLength) + "\n\t"
+ + "Class of Device: " + toHexString(mClassOfDevice) + "\n\t"
+ // LE
+ + "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t"
+ + "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t"
+ + "LE Appearance: " + toHexString(mLeAppearance) + "\n\t"
+ + "LE Flags: " + toHexString(mLeFlags) + "\n\t";
+ }
+
+ @NonNull
+ private String toHexString(@NonNull int b) {
+ return toHexString(new byte[] {(byte) b});
+ }
+
+ @NonNull
+ private String toHexString(@NonNull byte b) {
+ return toHexString(new byte[] {b});
+ }
+
+ @NonNull
+ private String toHexString(@NonNull byte[] array) {
+ StringBuilder builder = new StringBuilder(array.length * 2);
+ for (byte b: array) {
+ builder.append(String.format("%02x", b));
+ }
+ return builder.toString();
+ }
}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index f3ecbf6..6ab1975 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,6 +21,7 @@
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
@@ -38,6 +39,7 @@
import android.text.style.URLSpan;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextLinks;
import com.android.internal.util.ArrayUtils;
@@ -204,6 +206,7 @@
Uri mUri;
// Additional activity info resolved by the system
ActivityInfo mActivityInfo;
+ private TextLinks mTextLinks;
/** @hide */
public Item(Item other) {
@@ -332,6 +335,29 @@
}
/**
+ * Returns the results of text classification run on the raw text contained in this item,
+ * if it was performed, and if any entities were found in the text. Classification is
+ * generally only performed on the first item in clip data, and only if the text is below a
+ * certain length.
+ *
+ * <p>Returns {@code null} if classification was not performed, or if no entities were
+ * found in the text.
+ *
+ * @see ClipDescription#getConfidenceScore(String)
+ */
+ @Nullable
+ public TextLinks getTextLinks() {
+ return mTextLinks;
+ }
+
+ /**
+ * @hide
+ */
+ public void setTextLinks(TextLinks textLinks) {
+ mTextLinks = textLinks;
+ }
+
+ /**
* Turn this item into text, regardless of the type of data it
* actually contains.
*
@@ -1183,6 +1209,7 @@
dest.writeTypedObject(item.mIntent, flags);
dest.writeTypedObject(item.mUri, flags);
dest.writeTypedObject(item.mActivityInfo, flags);
+ dest.writeTypedObject(item.mTextLinks, flags);
}
}
@@ -1201,8 +1228,10 @@
Intent intent = in.readTypedObject(Intent.CREATOR);
Uri uri = in.readTypedObject(Uri.CREATOR);
ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
+ TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
Item item = new Item(text, htmlText, intent, uri);
item.setActivityInfo(info);
+ item.setTextLinks(textLinks);
mItems.add(item);
}
}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index d48f832..f49362e 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -16,16 +16,25 @@
package android.content;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Map;
/**
* Meta-data describing the contents of a {@link ClipData}. Provides enough
@@ -115,12 +124,39 @@
*/
public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value =
+ { CLASSIFICATION_NOT_COMPLETE, CLASSIFICATION_NOT_PERFORMED, CLASSIFICATION_COMPLETE})
+ @interface ClassificationStatus {}
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification has not been
+ * completed on the associated clip. This will be always be the case if the clip has not been
+ * copied to clipboard, or if there is no associated clip.
+ */
+ public static final int CLASSIFICATION_NOT_COMPLETE = 1;
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification was not and will
+ * not be performed on the associated clip. This may be the case if the clip does not contain
+ * text in its first item, or if the text is too long.
+ */
+ public static final int CLASSIFICATION_NOT_PERFORMED = 2;
+
+ /**
+ * Value returned by {@link #getConfidenceScore(String)} if text classification has been
+ * completed.
+ */
+ public static final int CLASSIFICATION_COMPLETE = 3;
final CharSequence mLabel;
private final ArrayList<String> mMimeTypes;
private PersistableBundle mExtras;
private long mTimeStamp;
private boolean mIsStyledText;
+ private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+ private int mClassificationStatus = CLASSIFICATION_NOT_COMPLETE;
/**
* Create a new clip.
@@ -346,6 +382,61 @@
mIsStyledText = isStyledText;
}
+ /**
+ * Sets the current status of text classification for the associated clip.
+ *
+ * @hide
+ */
+ public void setClassificationStatus(@ClassificationStatus int status) {
+ mClassificationStatus = status;
+ }
+
+ /**
+ * Returns a score indicating confidence that an instance of the given entity is present in the
+ * first item of the clip data, if that item is plain text and text classification has been
+ * performed. The value ranges from 0 (low confidence) to 1 (high confidence). 0 indicates that
+ * the entity was not found in the classified text.
+ *
+ * <p>Entities should be as defined in the {@link TextClassifier} class, such as
+ * {@link TextClassifier#TYPE_ADDRESS}, {@link TextClassifier#TYPE_URL}, or
+ * {@link TextClassifier#TYPE_EMAIL}.
+ *
+ * <p>If the result is positive for any entity, the full classification result as a
+ * {@link TextLinks} object may be obtained using the {@link ClipData.Item#getTextLinks()}
+ * method.
+ *
+ * @throws IllegalStateException if {@link #getClassificationStatus()} is not
+ * {@link #CLASSIFICATION_COMPLETE}
+ */
+ @FloatRange(from = 0.0, to = 1.0)
+ public float getConfidenceScore(@NonNull @TextClassifier.EntityType String entity) {
+ if (mClassificationStatus != CLASSIFICATION_COMPLETE) {
+ throw new IllegalStateException("Classification not complete");
+ }
+ return mEntityConfidence.getOrDefault(entity, 0f);
+ }
+
+ /**
+ * Returns {@link #CLASSIFICATION_COMPLETE} if text classification has been performed on the
+ * associated {@link ClipData}. If this is the case then {@link #getConfidenceScore} may be used
+ * to retrieve information about entities within the text. Otherwise, returns
+ * {@link #CLASSIFICATION_NOT_COMPLETE} if classification has not yet returned results, or
+ * {@link #CLASSIFICATION_NOT_PERFORMED} if classification was not attempted (e.g. because the
+ * text was too long).
+ */
+ public @ClassificationStatus int getClassificationStatus() {
+ return mClassificationStatus;
+ }
+
+ /**
+ * @hide
+ */
+ public void setConfidenceScores(Map<String, Float> confidences) {
+ mEntityConfidence.clear();
+ mEntityConfidence.putAll(confidences);
+ mClassificationStatus = CLASSIFICATION_COMPLETE;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
@@ -451,6 +542,23 @@
dest.writePersistableBundle(mExtras);
dest.writeLong(mTimeStamp);
dest.writeBoolean(mIsStyledText);
+ dest.writeInt(mClassificationStatus);
+ dest.writeBundle(confidencesToBundle());
+ }
+
+ private Bundle confidencesToBundle() {
+ Bundle bundle = new Bundle();
+ int size = mEntityConfidence.size();
+ for (int i = 0; i < size; i++) {
+ bundle.putFloat(mEntityConfidence.keyAt(i), mEntityConfidence.valueAt(i));
+ }
+ return bundle;
+ }
+
+ private void readBundleToConfidences(Bundle bundle) {
+ for (String key : bundle.keySet()) {
+ mEntityConfidence.put(key, bundle.getFloat(key));
+ }
}
ClipDescription(Parcel in) {
@@ -459,6 +567,8 @@
mExtras = in.readPersistableBundle();
mTimeStamp = in.readLong();
mIsStyledText = in.readBoolean();
+ mClassificationStatus = in.readInt();
+ readBundleToConfidences(in.readBundle());
}
public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 49248b5..73b4f62 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -2621,6 +2621,48 @@
return !TextUtils.isEmpty(uri.getUserInfo());
}
+ /**
+ * Returns the given content URI explicitly associated with the given {@link UserHandle}.
+ *
+ * @param contentUri The content URI to be associated with a user handle.
+ * @param userHandle The user handle with which to associate the URI.
+ *
+ * @throws IllegalArgumentException if
+ * <ul>
+ * <li>the given URI is not content URI (a content URI has {@link Uri#getScheme} equal to
+ * {@link ContentResolver.SCHEME_CONTENT}) or</li>
+ * <li>the given URI is already explicitly associated with a {@link UserHandle}, which is
+ * different than the given one.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static Uri createContentUriAsUser(
+ @NonNull Uri contentUri, @NonNull UserHandle userHandle) {
+ if (!ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+ throw new IllegalArgumentException(String.format(
+ "Given URI [%s] is not a content URI: ", contentUri));
+ }
+
+ int userId = userHandle.getIdentifier();
+ if (uriHasUserId(contentUri)) {
+ if (String.valueOf(userId).equals(contentUri.getUserInfo())) {
+ return contentUri;
+ }
+ throw new IllegalArgumentException(String.format(
+ "Given URI [%s] already has a user ID, different from given user handle [%s]",
+ contentUri,
+ userId));
+ }
+
+ Uri.Builder builder = contentUri.buildUpon();
+ builder.encodedAuthority(
+ "" + userHandle.getIdentifier() + "@" + contentUri.getEncodedAuthority());
+ return builder.build();
+ }
+
/** @hide */
@UnsupportedAppUsage
public static Uri maybeAddUserId(Uri uri, int userId) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2523459..f8dd0e1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3531,6 +3531,7 @@
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
+ PAC_PROXY_SERVICE,
VCN_MANAGEMENT_SERVICE,
//@hide: IP_MEMORY_STORE_SERVICE,
IPSEC_SERVICE,
@@ -4137,6 +4138,17 @@
public static final String CONNECTIVITY_SERVICE = "connectivity";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.PacProxyManager} for handling management of
+ * pac proxy information.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.PacProxyManager
+ * @hide
+ */
+ public static final String PAC_PROXY_SERVICE = "pac_proxy";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link android.net.vcn.VcnManager}
* for managing Virtual Carrier Networks
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c0384a4..883ffd8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1901,6 +1901,20 @@
"android.intent.action.AUTO_REVOKE_PERMISSIONS";
/**
+ * Activity action: Launch UI to manage unused apps (hibernated apps).
+ *
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_UNUSED_APPS =
+ "android.intent.action.MANAGE_UNUSED_APPS";
+
+ /**
* Activity action: Launch UI to review permissions for an app.
* The system uses this intent if permission review for apps not
* supporting the new runtime permissions model is enabled. In
diff --git a/core/java/android/net/IPacProxyInstalledListener.aidl b/core/java/android/net/IPacProxyInstalledListener.aidl
new file mode 100644
index 0000000..b1f946e
--- /dev/null
+++ b/core/java/android/net/IPacProxyInstalledListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.net;
+
+import android.net.Network;
+import android.net.ProxyInfo;
+
+/** {@hide} */
+oneway interface IPacProxyInstalledListener {
+ void onPacProxyInstalled(in Network network, in ProxyInfo proxy);
+}
diff --git a/core/java/android/net/IPacProxyManager.aidl b/core/java/android/net/IPacProxyManager.aidl
new file mode 100644
index 0000000..8f65c56
--- /dev/null
+++ b/core/java/android/net/IPacProxyManager.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IPacProxyInstalledListener;
+import android.net.ProxyInfo;
+
+/** {@hide} */
+interface IPacProxyManager
+{
+ void addListener(IPacProxyInstalledListener listener);
+ void removeListener(IPacProxyInstalledListener listener);
+ void setCurrentProxyScriptUrl(in ProxyInfo proxyInfo);
+}
diff --git a/core/java/android/net/PacProxyManager.java b/core/java/android/net/PacProxyManager.java
new file mode 100644
index 0000000..8f7ad8c
--- /dev/null
+++ b/core/java/android/net/PacProxyManager.java
@@ -0,0 +1,150 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@SystemService(Context.PAC_PROXY_SERVICE)
+public class PacProxyManager {
+ private final Context mContext;
+ private final IPacProxyManager mService;
+ @GuardedBy("mListenerMap")
+ private final HashMap<PacProxyInstalledListener, PacProxyInstalledListenerProxy>
+ mListenerMap = new HashMap<>();
+
+ /** @hide */
+ public PacProxyManager(Context context, IPacProxyManager service) {
+ Objects.requireNonNull(service, "missing IPacProxyManager");
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Add a listener to start monitoring events reported by PacProxyService.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void addPacProxyInstalledListener(@NonNull Executor executor,
+ @NonNull PacProxyInstalledListener listener) {
+ try {
+ synchronized (mListenerMap) {
+ final PacProxyInstalledListenerProxy listenerProxy =
+ new PacProxyInstalledListenerProxy(executor, listener);
+
+ if (null != mListenerMap.putIfAbsent(listener, listenerProxy)) {
+ throw new IllegalStateException("Listener is already added.");
+ }
+ mService.addListener(listenerProxy);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove the listener to stop monitoring the event of PacProxyInstalledListener.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void removePacProxyInstalledListener(@NonNull PacProxyInstalledListener listener) {
+ try {
+ synchronized (mListenerMap) {
+ final PacProxyInstalledListenerProxy listenerProxy = mListenerMap.remove(listener);
+ if (listenerProxy == null) return;
+ mService.removeListener(listenerProxy);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates the PAC Proxy Installer with current Proxy information.
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public void setCurrentProxyScriptUrl(@Nullable ProxyInfo proxy) {
+ try {
+ mService.setCurrentProxyScriptUrl(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * A callback interface for monitoring changes of PAC proxy information.
+ */
+ public interface PacProxyInstalledListener {
+ /**
+ * Notify that the PAC proxy has been installed. Note that this method will be called with
+ * a ProxyInfo with an empty PAC URL when the PAC proxy is removed.
+ *
+ * This method supports different PAC proxies per-network but not all devices might support
+ * per-network proxies. In that case it will be applied globally.
+ *
+ * @param network the network for which this proxy installed.
+ * @param proxy the installed proxy.
+ */
+ void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy);
+ }
+
+ /**
+ * PacProxyInstalledListener proxy for PacProxyInstalledListener object.
+ * @hide
+ */
+ public class PacProxyInstalledListenerProxy extends IPacProxyInstalledListener.Stub {
+ private final Executor mExecutor;
+ private final PacProxyInstalledListener mListener;
+
+ PacProxyInstalledListenerProxy(Executor executor, PacProxyInstalledListener listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onPacProxyInstalled(Network network, ProxyInfo proxy) {
+ Binder.withCleanCallingIdentity(() -> {
+ mExecutor.execute(() -> {
+ mListener.onPacProxyInstalled(network, proxy);
+ });
+ });
+ }
+ }
+}
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index 326943a..84b7eec 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -51,7 +51,7 @@
ServiceManager.getService(PROXY_SERVICE));
if (mProxyService == null) {
// Added because of b10267814 where mako is restarting.
- Log.e(TAG, "PacProxyInstaller: no proxy service");
+ Log.e(TAG, "PacProxyService: no proxy service");
}
mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bfa8f40..85968e3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9886,6 +9886,14 @@
"reminder_exp_learning_event_count";
/**
+ * Whether to show clipboard access notifications.
+ *
+ * @hide
+ */
+ public static final String CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS =
+ "clipboard_show_access_notifications";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/service/displayhash/DisplayHashParams.aidl b/core/java/android/service/displayhash/DisplayHashParams.aidl
new file mode 100644
index 0000000..90f9bf1
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.displayhash;
+
+parcelable DisplayHashParams;
diff --git a/core/java/android/service/displayhash/DisplayHashParams.java b/core/java/android/service/displayhash/DisplayHashParams.java
new file mode 100644
index 0000000..6a176a33
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.java
@@ -0,0 +1,251 @@
+/*
+ * 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.service.displayhash;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcelable;
+import android.util.Size;
+import android.view.displayhash.DisplayHashResultCallback;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information passed from the {@link DisplayHasherService} to system server about how to get the
+ * display data that will be used to generate the {@link android.view.displayhash.DisplayHash}
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genAidl = true, genToString = true, genParcelable = true, genHiddenConstructor = true)
+public final class DisplayHashParams implements Parcelable {
+ /**
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+ * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+ * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+ * If {@code null}, the buffer size will not be changed.
+ */
+ @Nullable
+ private final Size mBufferSize;
+
+ /**
+ * Whether the content captured will use filtering when scaling.
+ */
+ private final boolean mBufferScaleWithFiltering;
+
+ /**
+ * Whether the content will be captured in grayscale or color.
+ */
+ private final boolean mGrayscaleBuffer;
+
+ /**
+ * A builder for {@link DisplayHashParams}
+ */
+ public static final class Builder {
+ @Nullable
+ private Size mBufferSize;
+ private boolean mBufferScaleWithFiltering;
+ private boolean mGrayscaleBuffer;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash.
+ */
+ @NonNull
+ public Builder setBufferSize(int w, int h) {
+ mBufferSize = new Size(w, h);
+ return this;
+ }
+
+ /**
+ * Whether the content captured will use filtering when scaling.
+ */
+ @NonNull
+ public Builder setBufferScaleWithFiltering(boolean value) {
+ mBufferScaleWithFiltering = value;
+ return this;
+ }
+
+ /**
+ * Whether the content will be captured in grayscale or color.
+ */
+ @NonNull
+ public Builder setGrayscaleBuffer(boolean value) {
+ mGrayscaleBuffer = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ @NonNull
+ public DisplayHashParams build() {
+ return new DisplayHashParams(mBufferSize, mBufferScaleWithFiltering, mGrayscaleBuffer);
+ }
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DisplayHashParams.
+ *
+ * @param bufferSize
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+ * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+ * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+ * If {@code null}, the buffer size will not be changed.
+ * @param bufferScaleWithFiltering
+ * Whether the content captured will use filtering when scaling.
+ * @param grayscaleBuffer
+ * Whether the content will be captured in grayscale or color.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public DisplayHashParams(
+ @Nullable Size bufferSize,
+ boolean bufferScaleWithFiltering,
+ boolean grayscaleBuffer) {
+ this.mBufferSize = bufferSize;
+ this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+ this.mGrayscaleBuffer = grayscaleBuffer;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+ * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+ * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+ * If {@code null}, the buffer size will not be changed.
+ */
+ @DataClass.Generated.Member
+ public @Nullable Size getBufferSize() {
+ return mBufferSize;
+ }
+
+ /**
+ * Whether the content captured will use filtering when scaling.
+ */
+ @DataClass.Generated.Member
+ public boolean isBufferScaleWithFiltering() {
+ return mBufferScaleWithFiltering;
+ }
+
+ /**
+ * Whether the content will be captured in grayscale or color.
+ */
+ @DataClass.Generated.Member
+ public boolean isGrayscaleBuffer() {
+ return mGrayscaleBuffer;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DisplayHashParams { " +
+ "bufferSize = " + mBufferSize + ", " +
+ "bufferScaleWithFiltering = " + mBufferScaleWithFiltering + ", " +
+ "grayscaleBuffer = " + mGrayscaleBuffer +
+ " }";
+ }
+
+ @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 (mBufferScaleWithFiltering) flg |= 0x2;
+ if (mGrayscaleBuffer) flg |= 0x4;
+ if (mBufferSize != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mBufferSize != null) dest.writeSize(mBufferSize);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DisplayHashParams(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean bufferScaleWithFiltering = (flg & 0x2) != 0;
+ boolean grayscaleBuffer = (flg & 0x4) != 0;
+ Size bufferSize = (flg & 0x1) == 0 ? null : (Size) in.readSize();
+
+ this.mBufferSize = bufferSize;
+ this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+ this.mGrayscaleBuffer = grayscaleBuffer;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DisplayHashParams> CREATOR
+ = new Parcelable.Creator<DisplayHashParams>() {
+ @Override
+ public DisplayHashParams[] newArray(int size) {
+ return new DisplayHashParams[size];
+ }
+
+ @Override
+ public DisplayHashParams createFromParcel(@NonNull android.os.Parcel in) {
+ return new DisplayHashParams(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1615565493989L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java",
+ inputSignatures = "private final @android.annotation.Nullable android.util.Size mBufferSize\nprivate final boolean mBufferScaleWithFiltering\nprivate final boolean mGrayscaleBuffer\nclass DisplayHashParams extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.util.Size mBufferSize\nprivate boolean mBufferScaleWithFiltering\nprivate boolean mGrayscaleBuffer\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferSize(int,int)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genAidl=true, genToString=true, genParcelable=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/displayhash/DisplayHasherService.java b/core/java/android/service/displayhash/DisplayHasherService.java
index 331dbe9..2105d84 100644
--- a/core/java/android/service/displayhash/DisplayHasherService.java
+++ b/core/java/android/service/displayhash/DisplayHasherService.java
@@ -34,6 +34,8 @@
import android.view.displayhash.DisplayHashResultCallback;
import android.view.displayhash.VerifiedDisplayHash;
+import java.util.Map;
+
/**
* A service that handles generating and verify {@link DisplayHash}.
*
@@ -50,15 +52,6 @@
"android.service.displayhash.extra.VERIFIED_DISPLAY_HASH";
/**
- * Manifest metadata key for the resource string array containing the names of all hashing
- * algorithms provided by the service.
- *
- * @hide
- */
- public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
- "android.displayhash.available_algorithms";
-
- /**
* The {@link Intent} action that must be declared as handled by a service in its manifest
* for the system to recognize it as a DisplayHash providing service.
*
@@ -96,7 +89,7 @@
* @param buffer The buffer for the content to generate the hash for.
* @param bounds The size and position of the content in window space.
* @param hashAlgorithm The String for the hashing algorithm to use based values in
- * {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
+ * {@link #getDisplayHashAlgorithms(RemoteCallback)}.
* @param callback The callback to invoke
* {@link DisplayHashResultCallback#onDisplayHashResult(DisplayHash)}
* if successfully generated a DisplayHash or {@link
@@ -108,6 +101,12 @@
@NonNull String hashAlgorithm, @NonNull DisplayHashResultCallback callback);
/**
+ * Returns a map of supported algorithms and their {@link DisplayHashParams}
+ */
+ @NonNull
+ public abstract Map<String, DisplayHashParams> onGetDisplayHashAlgorithms();
+
+ /**
* Call to verify that the DisplayHash passed in was generated by the system.
*
* @param salt The salt value to use when verifying the hmac. This should be the
@@ -132,6 +131,15 @@
callback.sendResult(data);
}
+ private void getDisplayHashAlgorithms(RemoteCallback callback) {
+ Map<String, DisplayHashParams> displayHashParams = onGetDisplayHashAlgorithms();
+ final Bundle data = new Bundle();
+ for (Map.Entry<String, DisplayHashParams> entry : displayHashParams.entrySet()) {
+ data.putParcelable(entry.getKey(), entry.getValue());
+ }
+ callback.sendResult(data);
+ }
+
private final class DisplayHasherServiceWrapper extends IDisplayHasherService.Stub {
@Override
public void generateDisplayHash(byte[] salt, HardwareBuffer buffer, Rect bounds,
@@ -164,5 +172,11 @@
obtainMessage(DisplayHasherService::verifyDisplayHash,
DisplayHasherService.this, salt, displayHash, callback));
}
+
+ @Override
+ public void getDisplayHashAlgorithms(RemoteCallback callback) {
+ mHandler.sendMessage(obtainMessage(DisplayHasherService::getDisplayHashAlgorithms,
+ DisplayHasherService.this, callback));
+ }
}
}
diff --git a/core/java/android/service/displayhash/IDisplayHasherService.aidl b/core/java/android/service/displayhash/IDisplayHasherService.aidl
index 236bc28..d9dcdca 100644
--- a/core/java/android/service/displayhash/IDisplayHasherService.aidl
+++ b/core/java/android/service/displayhash/IDisplayHasherService.aidl
@@ -51,4 +51,11 @@
* @param callback The callback invoked to send back the VerifiedDisplayHash.
*/
void verifyDisplayHash(in byte[] salt, in DisplayHash displayHash, in RemoteCallback callback);
+
+ /**
+ * Call to get a map of supported algorithms and their {@link DisplayHashParams}
+ *
+ * @param callback The callback invoked to send back the map of algorithms to DisplayHashParams.
+ */
+ void getDisplayHashAlgorithms(in RemoteCallback callback);
}
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index d47604d..16204d8 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -16,12 +16,15 @@
package android.view;
+import static java.lang.Integer.toHexString;
+
import android.app.Activity;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.view.IDragAndDropPermissions;
@@ -56,9 +59,28 @@
*/
public final class DragAndDropPermissions implements Parcelable {
- private final IDragAndDropPermissions mDragAndDropPermissions;
+ private static final String TAG = "DragAndDrop";
+ private static final boolean DEBUG = false;
- private IBinder mTransientToken;
+ /**
+ * Permissions for a drop can be granted in one of two ways:
+ * <ol>
+ * <li>An app can explicitly request permissions using
+ * {@link Activity#requestDragAndDropPermissions(DragEvent)}. In this case permissions are
+ * revoked automatically when then activity is destroyed. See {@link #take(IBinder)}.
+ * <li>The platform can request permissions on behalf of the app (e.g. in
+ * {@link android.widget.Editor}). In this case permissions are revoked automatically when
+ * the app process terminates. See {@link #takeTransient()}.
+ * </ol>
+ *
+ * <p>In order to implement the second case above, we create a static token object here. This
+ * ensures that the token stays alive for the lifetime of the app process, allowing us to
+ * revoke permissions when the app process terminates using {@link IBinder#linkToDeath} in
+ * {@code DragAndDropPermissionsHandler}.
+ */
+ private static IBinder sAppToken;
+
+ private final IDragAndDropPermissions mDragAndDropPermissions;
/**
* Create a new {@link DragAndDropPermissions} object to control the access permissions for
@@ -81,30 +103,51 @@
}
/**
- * Take the permissions and bind their lifetime to the activity.
+ * Take permissions, binding their lifetime to the activity.
+ *
+ * <p>Note: This API is exposed to apps via
+ * {@link Activity#requestDragAndDropPermissions(DragEvent)}.
+ *
* @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
* @return True if permissions are successfully taken.
+ *
* @hide
*/
public boolean take(IBinder activityToken) {
try {
+ if (DEBUG) {
+ Log.d(TAG, this + ": calling take() with activity-bound token: "
+ + toHexString(activityToken.hashCode()));
+ }
mDragAndDropPermissions.take(activityToken);
} catch (RemoteException e) {
+ Log.w(TAG, this + ": take() failed with a RemoteException", e);
return false;
}
return true;
}
/**
- * Take the permissions. Must call {@link #release} explicitly.
+ * Take permissions transiently. Permissions will be revoked when the app process terminates.
+ *
+ * <p>Note: This API is not exposed to apps.
+ *
* @return True if permissions are successfully taken.
+ *
* @hide
*/
public boolean takeTransient() {
try {
- mTransientToken = new Binder();
- mDragAndDropPermissions.takeTransient(mTransientToken);
+ if (sAppToken == null) {
+ sAppToken = new Binder();
+ }
+ if (DEBUG) {
+ Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
+ + toHexString(sAppToken.hashCode()));
+ }
+ mDragAndDropPermissions.takeTransient(sAppToken);
} catch (RemoteException e) {
+ Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
return false;
}
return true;
@@ -116,8 +159,8 @@
public void release() {
try {
mDragAndDropPermissions.release();
- mTransientToken = null;
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -142,11 +185,9 @@
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeStrongInterface(mDragAndDropPermissions);
- destination.writeStrongBinder(mTransientToken);
}
private DragAndDropPermissions(Parcel in) {
mDragAndDropPermissions = IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
- mTransientToken = in.readStrongBinder();
}
}
diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java
index cc7525b..56b4383 100644
--- a/core/java/android/view/RoundedCorner.java
+++ b/core/java/android/view/RoundedCorner.java
@@ -163,7 +163,7 @@
* @hide
*/
public boolean isEmpty() {
- return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0;
+ return mRadius == 0 || mCenter.x <= 0 || mCenter.y <= 0;
}
private String getPositionString(@Position int position) {
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 569c287..623d969 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -181,16 +181,16 @@
boolean hasRoundedCorner;
switch (position) {
case POSITION_TOP_LEFT:
- hasRoundedCorner = radius > insetTop || radius > insetLeft;
+ hasRoundedCorner = radius > insetTop && radius > insetLeft;
break;
case POSITION_TOP_RIGHT:
- hasRoundedCorner = radius > insetTop || radius > insetRight;
+ hasRoundedCorner = radius > insetTop && radius > insetRight;
break;
case POSITION_BOTTOM_RIGHT:
- hasRoundedCorner = radius > insetBottom || radius > insetRight;
+ hasRoundedCorner = radius > insetBottom && radius > insetRight;
break;
case POSITION_BOTTOM_LEFT:
- hasRoundedCorner = radius > insetBottom || radius > insetLeft;
+ hasRoundedCorner = radius > insetBottom && radius > insetLeft;
break;
default:
throw new IllegalArgumentException(
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c6e5eee..ff4d671 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1308,7 +1308,9 @@
* @return {@link List} of {@link InputMethodInfo}.
* @hide
*/
+ @TestApi
@RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+ @NonNull
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
try {
final Completable.InputMethodInfoList value = Completable.createInputMethodInfoList();
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 6d5077a..ef50045 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -145,7 +145,7 @@
@StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
- WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
+ WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_CLIPBOARD, WIDGET_TYPE_UNKNOWN })
@interface WidgetType {}
/** The widget involved in the text classification context is a standard
@@ -172,6 +172,8 @@
String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
/** The widget involved in the text classification context is a notification */
String WIDGET_TYPE_NOTIFICATION = "notification";
+ /** The text classification context is for use with the system clipboard. */
+ String WIDGET_TYPE_CLIPBOARD = "clipboard";
/** The widget involved in the text classification context is of an unknown/unspecified type. */
String WIDGET_TYPE_UNKNOWN = "unknown";
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 45352e4..c203c790 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -130,7 +130,7 @@
public @interface EdgeEffectType {
}
- private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f;
+ private static final float DEFAULT_MAX_STRETCH_INTENSITY = 0.08f;
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
@@ -177,6 +177,7 @@
private long mStartTime;
private float mDuration;
private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY;
+ private float mStretchDistanceFraction = 0.1f;
private float mStretchDistance = -1f;
private final Interpolator mInterpolator = new DecelerateInterpolator();
@@ -467,7 +468,7 @@
public void onAbsorb(int velocity) {
if (mEdgeEffectType == TYPE_STRETCH) {
mState = STATE_RECEDE;
- mVelocity = velocity / mHeight;
+ mVelocity = velocity;
mDistance = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
} else {
@@ -655,7 +656,7 @@
// for now leverage placeholder logic if no stretch distance is provided to
// consume the displacement ratio times the minimum of the width or height
mStretchDistance > 0 ? mStretchDistance :
- (mDisplacement * Math.min(mWidth, mHeight))
+ (mStretchDistanceFraction * Math.min(mWidth, mHeight))
);
}
@@ -745,9 +746,9 @@
final double mDampedFreq = NATURAL_FREQUENCY * Math.sqrt(1 - DAMPING_RATIO * DAMPING_RATIO);
// We're always underdamped, so we can use only those equations:
- double cosCoeff = mDistance;
+ double cosCoeff = mDistance * mHeight;
double sinCoeff = (1 / mDampedFreq) * (DAMPING_RATIO * NATURAL_FREQUENCY
- * mDistance + mVelocity);
+ * mDistance * mHeight + mVelocity);
double distance = Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
* (cosCoeff * Math.cos(mDampedFreq * deltaT)
+ sinCoeff * Math.sin(mDampedFreq * deltaT));
@@ -755,7 +756,7 @@
+ Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
* (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
+ mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
- mDistance = (float) distance;
+ mDistance = (float) distance / mHeight;
mVelocity = (float) velocity;
mStartTime = time;
if (isAtEquilibrium()) {
@@ -786,9 +787,8 @@
* considered at rest or false if it is still animating.
*/
private boolean isAtEquilibrium() {
- double velocity = mVelocity * mHeight; // in pixels/second
double displacement = mDistance * mHeight; // in pixels
- return Math.abs(velocity) < VELOCITY_THRESHOLD
+ return Math.abs(mVelocity) < VELOCITY_THRESHOLD
&& Math.abs(displacement) < VALUE_THRESHOLD;
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ca89677..238ce85 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2904,9 +2904,6 @@
} finally {
mTextView.endBatchEdit();
mUndoInputFilter.freezeLastEdit();
- if (permissions != null) {
- permissions.release();
- }
}
}
diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java
new file mode 100644
index 0000000..d843308
--- /dev/null
+++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java
@@ -0,0 +1,217 @@
+/*
+ * 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.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.ColorResources;
+import android.widget.RemoteViews.InteractionHandler;
+import android.widget.RemoteViews.RemoteCollectionItems;
+
+import com.android.internal.R;
+
+import java.util.stream.IntStream;
+
+/**
+ * List {@link Adapter} backed by a {@link RemoteCollectionItems}.
+ *
+ * @hide
+ */
+class RemoteCollectionItemsAdapter extends BaseAdapter {
+
+ private final int mViewTypeCount;
+
+ private RemoteCollectionItems mItems;
+ private InteractionHandler mInteractionHandler;
+ private ColorResources mColorResources;
+
+ private SparseIntArray mLayoutIdToViewType;
+
+ RemoteCollectionItemsAdapter(
+ @NonNull RemoteCollectionItems items,
+ @NonNull InteractionHandler interactionHandler,
+ @NonNull ColorResources colorResources) {
+ // View type count can never increase after an adapter has been set on a ListView.
+ // Additionally, decreasing it could inhibit view recycling if the count were to back and
+ // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for
+ // the lifetime of the adapter.
+ mViewTypeCount = items.getViewTypeCount();
+
+ mItems = items;
+ mInteractionHandler = interactionHandler;
+ mColorResources = colorResources;
+
+ initLayoutIdToViewType();
+ }
+
+ /**
+ * Updates the data for the adapter, allowing recycling of views. Note that if the view type
+ * count has increased, a new adapter should be created and set on the AdapterView instead of
+ * calling this method.
+ */
+ void setData(
+ @NonNull RemoteCollectionItems items,
+ @NonNull InteractionHandler interactionHandler,
+ @NonNull ColorResources colorResources) {
+ if (mViewTypeCount < items.getViewTypeCount()) {
+ throw new IllegalArgumentException(
+ "RemoteCollectionItemsAdapter cannot increase view type count after creation");
+ }
+
+ mItems = items;
+ mInteractionHandler = interactionHandler;
+ mColorResources = colorResources;
+
+ initLayoutIdToViewType();
+
+ notifyDataSetChanged();
+ }
+
+ private void initLayoutIdToViewType() {
+ SparseIntArray previousLayoutIdToViewType = mLayoutIdToViewType;
+ mLayoutIdToViewType = new SparseIntArray(mViewTypeCount);
+
+ int[] layoutIds = IntStream.range(0, mItems.getItemCount())
+ .map(position -> mItems.getItemView(position).getLayoutId())
+ .distinct()
+ .toArray();
+ if (layoutIds.length > mViewTypeCount) {
+ throw new IllegalArgumentException(
+ "Collection items uses " + layoutIds.length + " distinct layouts, which is "
+ + "more than view type count of " + mViewTypeCount);
+ }
+
+ // Tracks whether a layout id (by index, not value) has been assigned a view type.
+ boolean[] processedLayoutIdIndices = new boolean[layoutIds.length];
+ // Tracks whether a view type has been assigned to a layout id already.
+ boolean[] assignedViewTypes = new boolean[mViewTypeCount];
+
+ if (previousLayoutIdToViewType != null) {
+ for (int i = 0; i < layoutIds.length; i++) {
+ int layoutId = layoutIds[i];
+ // Copy over any previously used view types for layout ids in the collection to keep
+ // view types stable across data updates.
+ int previousViewType = previousLayoutIdToViewType.get(layoutId, -1);
+ // Skip this layout id if it wasn't assigned to a view type previously.
+ if (previousViewType < 0) continue;
+
+ mLayoutIdToViewType.put(layoutId, previousViewType);
+ processedLayoutIdIndices[i] = true;
+ assignedViewTypes[previousViewType] = true;
+ }
+ }
+
+ int lastViewType = -1;
+ for (int i = 0; i < layoutIds.length; i++) {
+ // If a view type has already been assigned to the layout id, skip it.
+ if (processedLayoutIdIndices[i]) continue;
+
+ int layoutId = layoutIds[i];
+ // If no view type is assigned for the layout id, choose the next possible value that
+ // isn't already assigned to a layout id. There is guaranteed to be some value available
+ // due to the prior validation logic that count(distinct layout ids) <= viewTypeCount.
+ int viewType = IntStream.range(lastViewType + 1, layoutIds.length)
+ .filter(type -> !assignedViewTypes[type])
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalStateException(
+ "RemoteCollectionItems has more distinct layout ids than its "
+ + "view type count"));
+ mLayoutIdToViewType.put(layoutId, viewType);
+ processedLayoutIdIndices[i] = true;
+ assignedViewTypes[viewType] = true;
+ lastViewType = viewType;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.getItemCount();
+ }
+
+ @Override
+ public RemoteViews getItem(int position) {
+ return mItems.getItemView(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mItems.getItemId(position);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mLayoutIdToViewType.get(mItems.getItemView(position).getLayoutId());
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return mItems.hasStableIds();
+ }
+
+ @Nullable
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (position >= getCount()) return null;
+
+ RemoteViews item = mItems.getItemView(position);
+ item.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+ View reapplyView = getViewToReapply(convertView, item);
+
+ // Reapply the RemoteViews if we can.
+ if (reapplyView != null) {
+ try {
+ item.reapply(
+ parent.getContext(),
+ reapplyView,
+ mInteractionHandler,
+ null /* size */,
+ mColorResources);
+ return reapplyView;
+ } catch (RuntimeException e) {
+ // We can't reapply for some reason, we'll fallback to an apply and inflate a
+ // new view.
+ }
+ }
+
+ return item.apply(
+ parent.getContext(),
+ parent,
+ mInteractionHandler,
+ null /* size */,
+ mColorResources);
+ }
+
+ /** Returns {@code convertView} if it can be used to reapply {@code item}, or null otherwise. */
+ @Nullable
+ private static View getViewToReapply(@Nullable View convertView, @NonNull RemoteViews item) {
+ if (convertView == null) return null;
+
+ Object layoutIdTag = convertView.getTag(R.id.widget_frame);
+ if (!(layoutIdTag instanceof Integer)) return null;
+
+ return item.getLayoutId() == (Integer) layoutIdTag ? convertView : null;
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d2f4cea..21589c9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -28,6 +28,7 @@
import android.annotation.Px;
import android.annotation.StringRes;
import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
@@ -74,6 +75,7 @@
import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
+import android.util.LongArray;
import android.util.Pair;
import android.util.SizeF;
import android.util.SparseIntArray;
@@ -112,6 +114,7 @@
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -224,6 +227,7 @@
private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29;
private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
+ private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -899,6 +903,72 @@
ArrayList<RemoteViews> list;
}
+ private static class SetRemoteCollectionItemListAdapterAction extends Action {
+ private final RemoteCollectionItems mItems;
+
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+ viewId = id;
+ mItems = items;
+ }
+
+ SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(viewId);
+ dest.writeTypedObject(mItems, flags);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
+ ColorResources colorResources) throws ActionException {
+ View target = root.findViewById(viewId);
+ if (target == null) return;
+
+ if (!(target instanceof AdapterView)) {
+ Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not "
+ + "an AdapterView (id: " + viewId + ")");
+ return;
+ }
+
+ AdapterView adapterView = (AdapterView) target;
+ Adapter adapter = adapterView.getAdapter();
+ // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type
+ // count hasn't increased. Note that AbsListView allocates a fixed size array for view
+ // recycling in setAdapter, so we must call setAdapter again if the number of view types
+ // increases.
+ if (adapter instanceof RemoteCollectionItemsAdapter
+ && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+ try {
+ ((RemoteCollectionItemsAdapter) adapter).setData(
+ mItems, handler, colorResources);
+ } catch (Throwable throwable) {
+ // setData should never failed with the validation in the items builder, but if
+ // it does, catch and rethrow.
+ throw new ActionException(throwable);
+ }
+ return;
+ }
+
+ try {
+ adapterView.setAdapter(
+ new RemoteCollectionItemsAdapter(mItems, handler, colorResources));
+ } catch (Throwable throwable) {
+ // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
+ // a type error.
+ throw new ActionException(throwable);
+ }
+ }
+
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
+ }
+ }
+
private class SetRemoteViewsAdapterIntent extends Action {
public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) {
this.viewId = id;
@@ -3543,6 +3613,8 @@
return new SetOnCheckedChangeResponse(parcel);
case NIGHT_MODE_REFLECTION_ACTION_TAG:
return new NightModeReflectionAction(parcel);
+ case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG:
+ return new SetRemoteCollectionItemListAdapterAction(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -4215,6 +4287,25 @@
}
/**
+ * 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}.
+ * This is a simpler but less flexible approach to populating collection widgets. Its use is
+ * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
+ * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
+ * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
+ * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
+ *
+ * This API is supported in the compatibility library for previous API levels, see
+ * RemoteViewsCompat.
+ *
+ * @param viewId The id of the {@link AdapterView}.
+ * @param items The items to display in the {@link AdapterView}.
+ */
+ public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) {
+ addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items));
+ }
+
+ /**
* Equivalent to calling {@link ListView#smoothScrollToPosition(int)}.
*
* @param viewId The id of the view to change
@@ -6026,6 +6117,203 @@
return true;
}
+ /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */
+ public static final class RemoteCollectionItems implements Parcelable {
+ private final long[] mIds;
+ private final RemoteViews[] mViews;
+ private final boolean mHasStableIds;
+ private final int mViewTypeCount;
+
+ RemoteCollectionItems(
+ long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) {
+ mIds = ids;
+ mViews = views;
+ mHasStableIds = hasStableIds;
+ mViewTypeCount = viewTypeCount;
+ if (ids.length != views.length) {
+ throw new IllegalArgumentException(
+ "RemoteCollectionItems has different number of ids and views");
+ }
+ if (viewTypeCount < 1) {
+ throw new IllegalArgumentException("View type count must be >= 1");
+ }
+ int layoutIdCount = (int) Arrays.stream(views)
+ .mapToInt(RemoteViews::getLayoutId)
+ .distinct()
+ .count();
+ if (layoutIdCount > viewTypeCount) {
+ throw new IllegalArgumentException(
+ "View type count is set to " + viewTypeCount + ", but the collection "
+ + "contains " + layoutIdCount + " different layout ids");
+ }
+ }
+
+ RemoteCollectionItems(Parcel in) {
+ int length = in.readInt();
+ mIds = new long[length];
+ in.readLongArray(mIds);
+ mViews = new RemoteViews[length];
+ in.readTypedArray(mViews, RemoteViews.CREATOR);
+ mHasStableIds = in.readBoolean();
+ mViewTypeCount = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mIds.length);
+ dest.writeLongArray(mIds);
+ dest.writeTypedArray(mViews, flags);
+ dest.writeBoolean(mHasStableIds);
+ dest.writeInt(mViewTypeCount);
+ }
+
+ /**
+ * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id
+ * should be considered meaningful across collection updates.
+ *
+ * @return Id for the position.
+ */
+ public long getItemId(int position) {
+ return mIds[position];
+ }
+
+ /**
+ * Returns the {@link RemoteViews} to display at {@code position}.
+ *
+ * @return RemoteViews for the position.
+ */
+ @NonNull
+ public RemoteViews getItemView(int position) {
+ return mViews[position];
+ }
+
+ /**
+ * Returns the number of elements in the collection.
+ *
+ * @return Count of items.
+ */
+ public int getItemCount() {
+ return mIds.length;
+ }
+
+ /**
+ * Returns the view type count for the collection when used in an adapter
+ *
+ * @return Count of view types for the collection when used in an adapter.
+ * @see android.widget.Adapter#getViewTypeCount()
+ */
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ /**
+ * Indicates whether the item ids are stable across changes to the underlying data.
+ *
+ * @return True if the same id always refers to the same object.
+ * @see android.widget.Adapter#hasStableIds()
+ */
+ public boolean hasStableIds() {
+ return mHasStableIds;
+ }
+
+ @NonNull
+ public static final Creator<RemoteCollectionItems> CREATOR =
+ new Creator<RemoteCollectionItems>() {
+ @NonNull
+ @Override
+ public RemoteCollectionItems createFromParcel(@NonNull Parcel source) {
+ return new RemoteCollectionItems(source);
+ }
+
+ @NonNull
+ @Override
+ public RemoteCollectionItems[] newArray(int size) {
+ return new RemoteCollectionItems[size];
+ }
+ };
+
+ /** Builder class for {@link RemoteCollectionItems} objects.*/
+ public static final class Builder {
+ private final LongArray mIds = new LongArray();
+ private final List<RemoteViews> mViews = new ArrayList<>();
+ private boolean mHasStableIds;
+ private int mViewTypeCount;
+
+ /**
+ * Adds a {@link RemoteViews} to the collection.
+ *
+ * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to
+ * indicate that ids are stable across changes to the collection.
+ * @param view RemoteViews to display for the row.
+ */
+ @NonNull
+ // Covered by getItemId, getItemView, getItemCount.
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder addItem(long id, @NonNull RemoteViews view) {
+ if (view == null) throw new NullPointerException();
+ if (view.hasMultipleLayouts()) {
+ throw new IllegalArgumentException(
+ "RemoteViews used in a RemoteCollectionItems cannot specify separate "
+ + "layouts for orientations or sizes.");
+ }
+ mIds.add(id);
+ mViews.add(view);
+ return this;
+ }
+
+ /**
+ * Sets whether the item ids are stable across changes to the underlying data.
+ *
+ * @see android.widget.Adapter#hasStableIds()
+ */
+ @NonNull
+ public Builder setHasStableIds(boolean hasStableIds) {
+ mHasStableIds = hasStableIds;
+ return this;
+ }
+
+ /**
+ * Sets the view type count for the collection when used in an adapter. This can be set
+ * to the maximum number of different layout ids that will be used by RemoteViews in
+ * this collection.
+ *
+ * If this value is not set, then a value will be inferred from the provided items. As
+ * a result, the adapter may need to be recreated when the list is updated with
+ * previously unseen RemoteViews layouts for new items.
+ *
+ * @see android.widget.Adapter#getViewTypeCount()
+ */
+ @NonNull
+ public Builder setViewTypeCount(int viewTypeCount) {
+ mViewTypeCount = viewTypeCount;
+ return this;
+ }
+
+ /** Creates the {@link RemoteCollectionItems} defined by this builder. */
+ @NonNull
+ public RemoteCollectionItems build() {
+ if (mViewTypeCount < 1) {
+ // If a view type count wasn't specified, set it to be the number of distinct
+ // layout ids used in the items.
+ mViewTypeCount = (int) mViews.stream()
+ .mapToInt(RemoteViews::getLayoutId)
+ .distinct()
+ .count();
+ }
+ return new RemoteCollectionItems(
+ mIds.toArray(),
+ mViews.toArray(new RemoteViews[0]),
+ mHasStableIds,
+ Math.max(mViewTypeCount, 1));
+ }
+ }
+ }
+
/**
* Set the ID of the top-level view of the XML layout.
*
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index d59a415..2464b4a 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -26,6 +26,7 @@
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.LruCache;
+import android.util.Range;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
@@ -62,7 +63,8 @@
// Pause between each spell check to keep the UI smooth
private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
- private static final int MIN_SENTENCE_LENGTH = 50;
+ // The maximum length of sentence.
+ private static final int MAX_SENTENCE_LENGTH = WORD_ITERATOR_INTERVAL;
private static final int USE_SPAN_RANGE = -1;
@@ -89,7 +91,7 @@
// Shared by all SpellParsers. Cannot be shared with TextView since it may be used
// concurrently due to the asynchronous nature of onGetSuggestions.
- private WordIterator mWordIterator;
+ private SentenceIteratorWrapper mSentenceIterator;
@Nullable
private TextServicesManager mTextServicesManager;
@@ -151,8 +153,9 @@
resetSession();
if (locale != null) {
- // Change SpellParsers' wordIterator locale
- mWordIterator = new WordIterator(locale);
+ // Change SpellParsers' sentenceIterator locale
+ mSentenceIterator = new SentenceIteratorWrapper(
+ BreakIterator.getSentenceInstance(locale));
}
// This class is the listener for locale change: warn other locale-aware objects
@@ -306,22 +309,30 @@
final int start = editable.getSpanStart(spellCheckSpan);
final int end = editable.getSpanEnd(spellCheckSpan);
- // Do not check this word if the user is currently editing it
- final boolean isEditing;
+ // Check the span if any of following conditions is met:
+ // - the user is not currently editing it
+ // - or `forceCheckWhenEditingWord` is true.
+ final boolean isNotEditing;
// Defer spell check when typing a word ending with a punctuation like an apostrophe
// which could end up being a mid-word punctuation.
if (selectionStart == end + 1
&& WordIterator.isMidWordPunctuation(
mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
- isEditing = false;
- } else {
+ isNotEditing = false;
+ } else if (selectionEnd <= start || selectionStart > end) {
// Allow the overlap of the cursor and the first boundary of the spell check span
// no to skip the spell check of the following word because the
// following word will never be spell-checked even if the user finishes composing
- isEditing = selectionEnd <= start || selectionStart > end;
+ isNotEditing = true;
+ } else {
+ // When cursor is at the end of spell check span, allow spell check if the
+ // character before cursor is a separator.
+ isNotEditing = selectionStart == end
+ && selectionStart > 0
+ && isSeparator(Character.codePointBefore(editable, selectionStart));
}
- if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
+ if (start >= 0 && end > start && (forceCheckWhenEditingWord || isNotEditing)) {
spellCheckSpan.setSpellCheckInProgress(true);
final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
textInfos[textInfosCount++] = textInfo;
@@ -346,6 +357,19 @@
}
}
+ private static boolean isSeparator(int codepoint) {
+ final int type = Character.getType(codepoint);
+ return ((1 << type) & ((1 << Character.SPACE_SEPARATOR)
+ | (1 << Character.LINE_SEPARATOR)
+ | (1 << Character.PARAGRAPH_SEPARATOR)
+ | (1 << Character.DASH_PUNCTUATION)
+ | (1 << Character.END_PUNCTUATION)
+ | (1 << Character.FINAL_QUOTE_PUNCTUATION)
+ | (1 << Character.INITIAL_QUOTE_PUNCTUATION)
+ | (1 << Character.START_PUNCTUATION)
+ | (1 << Character.OTHER_PUNCTUATION))) != 0;
+ }
+
private SpellCheckSpan onGetSuggestionsInternal(
SuggestionsInfo suggestionsInfo, int offset, int length) {
if (suggestionsInfo == null || suggestionsInfo.getCookie() != mCookie) {
@@ -534,6 +558,60 @@
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
}
+ /**
+ * A wrapper of sentence iterator which only processes the specified window of the given text.
+ */
+ private static class SentenceIteratorWrapper {
+ private BreakIterator mSentenceIterator;
+ private int mStartOffset;
+ private int mEndOffset;
+
+ SentenceIteratorWrapper(BreakIterator sentenceIterator) {
+ mSentenceIterator = sentenceIterator;
+ }
+
+ /**
+ * Set the char sequence and the text window to process.
+ */
+ public void setCharSequence(CharSequence sequence, int start, int end) {
+ mStartOffset = Math.max(0, start);
+ mEndOffset = Math.min(end, sequence.length());
+ mSentenceIterator.setText(sequence.subSequence(mStartOffset, mEndOffset).toString());
+ }
+
+ /**
+ * See {@link BreakIterator#preceding(int)}
+ */
+ public int preceding(int offset) {
+ if (offset < mStartOffset) {
+ return BreakIterator.DONE;
+ }
+ int result = mSentenceIterator.preceding(offset - mStartOffset);
+ return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+ }
+
+ /**
+ * See {@link BreakIterator#following(int)}
+ */
+ public int following(int offset) {
+ if (offset > mEndOffset) {
+ return BreakIterator.DONE;
+ }
+ int result = mSentenceIterator.following(offset - mStartOffset);
+ return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+ }
+
+ /**
+ * See {@link BreakIterator#isBoundary(int)}
+ */
+ public boolean isBoundary(int offset) {
+ if (offset < mStartOffset || offset > mEndOffset) {
+ return false;
+ }
+ return mSentenceIterator.isBoundary(offset - mStartOffset);
+ }
+ }
+
private class SpellParser {
private Object mRange = new Object();
@@ -582,27 +660,15 @@
public void parse() {
Editable editable = (Editable) mTextView.getText();
- // Iterate over the newly added text and schedule new SpellCheckSpans
- final int start = Math.max(
- 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
+ final int textChangeStart = editable.getSpanStart(mRange);
+ final int textChangeEnd = editable.getSpanEnd(mRange);
- final int end = editable.getSpanEnd(mRange);
+ Range<Integer> sentenceBoundary = detectSentenceBoundary(editable, textChangeStart,
+ textChangeEnd);
+ int sentenceStart = sentenceBoundary.getLower();
+ int sentenceEnd = sentenceBoundary.getUpper();
- int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
- mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
-
- // Move back to the beginning of the current word, if any
- int wordStart = mWordIterator.preceding(start);
- int wordEnd;
- if (wordStart == BreakIterator.DONE) {
- wordEnd = mWordIterator.following(start);
- if (wordEnd != BreakIterator.DONE) {
- wordStart = mWordIterator.getBeginning(wordEnd);
- }
- } else {
- wordEnd = mWordIterator.getEnd(wordStart);
- }
- if (wordEnd == BreakIterator.DONE) {
+ if (sentenceStart == sentenceEnd) {
if (DBG) {
Log.i(TAG, "No more spell check.");
}
@@ -612,29 +678,16 @@
boolean scheduleOtherSpellCheck = false;
- if (wordIteratorWindowEnd < end) {
+ if (sentenceEnd < textChangeEnd) {
if (DBG) {
Log.i(TAG, "schedule other spell check.");
}
// Several batches needed on that region. Cut after last previous word
scheduleOtherSpellCheck = true;
}
- int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
- boolean correct = spellCheckEnd != BreakIterator.DONE;
- if (correct) {
- spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
- correct = spellCheckEnd != BreakIterator.DONE;
- }
- if (!correct) {
- if (DBG) {
- Log.i(TAG, "Incorrect range span.");
- }
- stop();
- return;
- }
+ int spellCheckEnd = sentenceEnd;
do {
- // TODO: Find the start position of the sentence.
- int spellCheckStart = wordStart;
+ int spellCheckStart = sentenceStart;
boolean createSpellCheckSpan = true;
// Cancel or merge overlapped spell check spans
for (int i = 0; i < mLength; ++i) {
@@ -671,27 +724,23 @@
}
// Stop spell checking when there are no characters in the range.
- if (spellCheckEnd < start) {
- break;
- }
if (spellCheckEnd <= spellCheckStart) {
Log.w(TAG, "Trying to spellcheck invalid region, from "
- + start + " to " + end);
+ + sentenceStart + " to " + spellCheckEnd);
break;
}
if (createSpellCheckSpan) {
addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
}
} while (false);
- wordStart = spellCheckEnd;
-
- if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
+ sentenceStart = spellCheckEnd;
+ if (scheduleOtherSpellCheck && sentenceStart != BreakIterator.DONE
+ && sentenceStart <= textChangeEnd) {
// Update range span: start new spell check from last wordStart
- setRangeSpan(editable, wordStart, end);
+ setRangeSpan(editable, sentenceStart, textChangeEnd);
} else {
removeRangeSpan(editable);
}
-
spellCheck(mForceCheckWhenEditingWord);
}
@@ -708,6 +757,94 @@
}
}
+ private Range<Integer> detectSentenceBoundary(CharSequence sequence,
+ int textChangeStart, int textChangeEnd) {
+ // Only process a substring of the full text due to performance concern.
+ final int iteratorWindowStart = findSeparator(sequence,
+ Math.max(0, textChangeStart - MAX_SENTENCE_LENGTH),
+ Math.max(0, textChangeStart - 2 * MAX_SENTENCE_LENGTH));
+ final int iteratorWindowEnd = findSeparator(sequence,
+ Math.min(textChangeStart + 2 * MAX_SENTENCE_LENGTH, textChangeEnd),
+ Math.min(textChangeStart + 3 * MAX_SENTENCE_LENGTH, sequence.length()));
+ if (DBG) {
+ Log.d(TAG, "Set iterator window as [" + iteratorWindowStart + ", " + iteratorWindowEnd
+ + ").");
+ }
+ mSentenceIterator.setCharSequence(sequence, iteratorWindowStart, iteratorWindowEnd);
+
+ // Detect the offset of sentence begin/end on the substring.
+ int sentenceStart = mSentenceIterator.isBoundary(textChangeStart) ? textChangeStart
+ : mSentenceIterator.preceding(textChangeStart);
+ int sentenceEnd = mSentenceIterator.following(sentenceStart);
+ if (sentenceEnd == BreakIterator.DONE) {
+ sentenceEnd = iteratorWindowEnd;
+ }
+ if (DBG) {
+ if (sentenceStart != sentenceEnd) {
+ Log.d(TAG, "Sentence detected [" + sentenceStart + ", " + sentenceEnd + ").");
+ }
+ }
+
+ if (sentenceEnd - sentenceStart <= MAX_SENTENCE_LENGTH) {
+ // Add more sentences until the MAX_SENTENCE_LENGTH limitation is reached.
+ while (sentenceEnd < textChangeEnd) {
+ int nextEnd = mSentenceIterator.following(sentenceEnd);
+ if (nextEnd == BreakIterator.DONE
+ || nextEnd - sentenceStart > MAX_SENTENCE_LENGTH) {
+ break;
+ }
+ sentenceEnd = nextEnd;
+ }
+ } else {
+ // If the sentence containing `textChangeStart` is longer than MAX_SENTENCE_LENGTH,
+ // the sentence will be sliced into sub-sentences of about MAX_SENTENCE_LENGTH
+ // characters each. This is done by processing the unchecked part of that sentence :
+ // [textChangeStart, sentenceEnd)
+ //
+ // - If the `uncheckedLength` is bigger than MAX_SENTENCE_LENGTH, then check the
+ // [textChangeStart, textChangeStart + MAX_SENTENCE_LENGTH), and leave the rest
+ // part for the next check.
+ //
+ // - If the `uncheckedLength` is smaller than or equal to MAX_SENTENCE_LENGTH,
+ // then check [sentenceEnd - MAX_SENTENCE_LENGTH, sentenceEnd).
+ //
+ // The offset should be rounded up to word boundary.
+ int uncheckedLength = sentenceEnd - textChangeStart;
+ if (uncheckedLength > MAX_SENTENCE_LENGTH) {
+ sentenceEnd = findSeparator(sequence, sentenceStart + MAX_SENTENCE_LENGTH,
+ sentenceEnd);
+ sentenceStart = roundUpToWordStart(sequence, textChangeStart, sentenceStart);
+ } else {
+ sentenceStart = roundUpToWordStart(sequence, sentenceEnd - MAX_SENTENCE_LENGTH,
+ sentenceStart);
+ }
+ }
+ return new Range(sentenceStart, sentenceEnd);
+ }
+
+ private int roundUpToWordStart(CharSequence sequence, int position, int frontBoundary) {
+ if (isSeparator(sequence.charAt(position))) {
+ return position;
+ }
+ int separator = findSeparator(sequence, position, frontBoundary);
+ return separator != frontBoundary ? separator + 1 : frontBoundary;
+ }
+
+ /**
+ * Search the range [start, end) of sequence and returns the position of the first separator.
+ * If end is smaller than start, do a reverse search.
+ * Returns `end` if no separator is found.
+ */
+ private static int findSeparator(CharSequence sequence, int start, int end) {
+ final int step = start < end ? 1 : -1;
+ for (int i = start; i != end; i += step) {
+ if (isSeparator(sequence.charAt(i))) {
+ return i;
+ }
+ }
+ return end;
+ }
+
public static boolean haveWordBoundariesChanged(final Editable editable, final int start,
final int end, final int spanStart, final int spanEnd) {
final boolean haveWordBoundariesChanged;
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index c2e1426..7baa53b 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -16,6 +16,9 @@
package com.android.internal.accessibility.util;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -28,6 +31,10 @@
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_FULL_SCREEN;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_WINDOW;
import android.content.ComponentName;
import android.view.accessibility.AccessibilityManager;
@@ -113,6 +120,19 @@
UNKNOWN_STATUS);
}
+ /**
+ * Logs the magnification activated mode and its duration of the usage.
+ * Calls this when the magnification is disabled.
+ *
+ * @param mode The activated magnification mode.
+ * @param duration The duration in milliseconds during the magnification is activated.
+ */
+ public static void logMagnificationUsageState(int mode, long duration) {
+ FrameworkStatsLog.write(FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED,
+ convertToLoggingMagnificationMode(mode),
+ duration);
+ }
+
private static int convertToLoggingShortcutType(@ShortcutType int shortcutType) {
switch (shortcutType) {
case ACCESSIBILITY_BUTTON:
@@ -127,4 +147,18 @@
return enabled ? ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED
: ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
}
+
+ private static int convertToLoggingMagnificationMode(int mode) {
+ switch (mode) {
+ case ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_FULL_SCREEN;
+ case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_WINDOW;
+ case ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
+
+ default:
+ return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index d0c807d..783d088 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -221,10 +221,6 @@
dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args));
}
- public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
- // no-op
- }
-
public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
dispatchMessage(
mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
index 6cc5a4a..83345da 100644
--- a/core/java/com/android/internal/widget/CallLayout.java
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -100,7 +100,6 @@
}
// TODO(b/179178086): crop/clip the icon to a circle?
mConversationIconView.setImageIcon(icon);
- mConversationText.setText(callerName);
}
@RemotableViewMethod
diff --git a/core/jni/android_net_NetworkUtils.cpp b/core/jni/android_net_NetworkUtils.cpp
index 7508108..a781a37 100644
--- a/core/jni/android_net_NetworkUtils.cpp
+++ b/core/jni/android_net_NetworkUtils.cpp
@@ -52,27 +52,6 @@
// FrameworkListener limits the size of commands to 4096 bytes.
constexpr int MAXCMDSIZE = 4096;
-static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
- ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
- if (detailMessage.get() == NULL) {
- // Not really much we can do here. We're probably dead in the water,
- // but let's try to stumble on...
- env->ExceptionClear();
- }
- static jclass errnoExceptionClass =
- MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
-
- static jmethodID errnoExceptionCtor =
- GetMethodIDOrDie(env, errnoExceptionClass,
- "<init>", "(Ljava/lang/String;I)V");
-
- jobject exception = env->NewObject(errnoExceptionClass,
- errnoExceptionCtor,
- detailMessage.get(),
- error);
- env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
struct sock_filter filter_code[] = {
@@ -150,7 +129,7 @@
int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
if (fd < 0) {
- throwErrnoException(env, "resNetworkQuery", -fd);
+ jniThrowErrnoException(env, "resNetworkQuery", -fd);
return nullptr;
}
@@ -165,7 +144,7 @@
int fd = resNetworkSend(netId, data, msgLen, flags);
if (fd < 0) {
- throwErrnoException(env, "resNetworkSend", -fd);
+ jniThrowErrnoException(env, "resNetworkSend", -fd);
return nullptr;
}
@@ -180,13 +159,13 @@
int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
jniSetFileDescriptorOfFD(env, javaFd, -1);
if (res < 0) {
- throwErrnoException(env, "resNetworkResult", -res);
+ jniThrowErrnoException(env, "resNetworkResult", -res);
return nullptr;
}
jbyteArray answer = env->NewByteArray(res);
if (answer == nullptr) {
- throwErrnoException(env, "resNetworkResult", ENOMEM);
+ jniThrowErrnoException(env, "resNetworkResult", ENOMEM);
return nullptr;
} else {
env->SetByteArrayRegion(answer, 0, res,
@@ -208,7 +187,7 @@
static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
unsigned dnsNetId = 0;
if (int res = getNetworkForDns(&dnsNetId) < 0) {
- throwErrnoException(env, "getDnsNetId", -res);
+ jniThrowErrnoException(env, "getDnsNetId", -res);
return nullptr;
}
bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
@@ -233,8 +212,8 @@
// Obtain the parameters of the TCP repair window.
int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
if (rc == -1) {
- throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
- return NULL;
+ jniThrowErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
+ return NULL;
}
struct tcp_info tcpinfo = {};
@@ -244,8 +223,8 @@
// should be applied to the window size.
rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
if (rc == -1) {
- throwErrnoException(env, "getsockopt : TCP_INFO", errno);
- return NULL;
+ jniThrowErrnoException(env, "getsockopt : TCP_INFO", errno);
+ return NULL;
}
jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
diff --git a/core/jni/android_os_SharedMemory.cpp b/core/jni/android_os_SharedMemory.cpp
index dc86187..fe375f4 100644
--- a/core/jni/android_os_SharedMemory.cpp
+++ b/core/jni/android_os_SharedMemory.cpp
@@ -35,21 +35,6 @@
jclass errnoExceptionClass;
jmethodID errnoExceptionCtor; // MethodID for ErrnoException.<init>(String,I)
-void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
- ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
- if (detailMessage.get() == NULL) {
- // Not really much we can do here. We're probably dead in the water,
- // but let's try to stumble on...
- env->ExceptionClear();
- }
-
- jobject exception = env->NewObject(errnoExceptionClass,
- errnoExceptionCtor,
- detailMessage.get(),
- error);
- env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
// Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null
@@ -65,7 +50,7 @@
}
if (fd < 0) {
- throwErrnoException(env, "SharedMemory_create", err);
+ jniThrowErrnoException(env, "SharedMemory_create", err);
return nullptr;
}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index dcfa950..d4b5c2b 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1316,24 +1316,6 @@
return removeAllProcessGroups();
}
-static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
- ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
- if (detailMessage.get() == NULL) {
- // Not really much we can do here. We're probably dead in the water,
- // but let's try to stumble on...
- env->ExceptionClear();
- }
- static jclass errnoExceptionClass =
- MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
-
- static jmethodID errnoExceptionCtor =
- GetMethodIDOrDie(env, errnoExceptionClass, "<init>", "(Ljava/lang/String;I)V");
-
- jobject exception =
- env->NewObject(errnoExceptionClass, errnoExceptionCtor, detailMessage.get(), error);
- env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
// Wrapper function to the syscall pidfd_open, which creates a file
// descriptor that refers to the process whose PID is specified in pid.
static inline int sys_pidfd_open(pid_t pid, unsigned int flags) {
@@ -1343,7 +1325,7 @@
static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
int fd = sys_pidfd_open(pid, flags);
if (fd < 0) {
- throwErrnoException(env, "nativePidFdOpen", errno);
+ jniThrowErrnoException(env, "nativePidFdOpen", errno);
return -1;
}
return fd;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0bed29b..be17d92 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -825,7 +825,7 @@
PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
- bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
+ bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, true);
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 632d372..dca6002 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -192,6 +192,12 @@
optional Camera camera = 12;
optional SettingProto carrier_apps_handled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ message Clipboard {
+ optional SettingProto show_access_notifications = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional Clipboard clipboard = 89;
+
optional SettingProto cmas_additional_broadcast_pkg = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
repeated SettingProto completed_categories = 15;
optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -647,5 +653,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 89;
+ // Next tag = 90;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 021d691..99ef943 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2775,11 +2775,11 @@
The app can check whether it has this authorization by calling
{@link android.provider.Settings#canDrawOverlays
Settings.canDrawOverlays()}.
- <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
+ <p>Protection level: signature|setup|appop|installer|appPredictor|pre23|development -->
<permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
android:label="@string/permlab_systemAlertWindow"
android:description="@string/permdesc_systemAlertWindow"
- android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
+ android:protectionLevel="signature|setup|appop|installer|appPredictor|pre23|development" />
<!-- @SystemApi @hide Allows an application to create windows using the type
{@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index f92e6d6..b969fa4 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -39,7 +39,7 @@
android:layout_height="@dimen/notification_expand_button_pill_height"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:gravity="center_vertical"
- android:paddingLeft="8dp"
+ android:paddingStart="8dp"
/>
<ImageView
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 2d1c342..b9a3625 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -27,7 +27,7 @@
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_action_list_height"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
diff --git a/core/res/res/layout/notification_template_material_big_call.xml b/core/res/res/layout/notification_template_material_big_call.xml
new file mode 100644
index 0000000..1d50467
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_big_call.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.internal.widget.CallLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="call"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+ <include layout="@layout/notification_template_conversation_icon_container" />
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/notification_content_margin"
+ android:orientation="vertical"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:orientation="vertical"
+ android:minHeight="68dp"
+ >
+
+ <include
+ layout="@layout/notification_template_conversation_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <include layout="@layout/notification_template_text_multiline" />
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_progress_bar_height"
+ android:layout_marginTop="@dimen/notification_progress_margin_top"
+ layout="@layout/notification_template_progress"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include
+ layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_material_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+
+ </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 2954ba2..86e7dec 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -31,7 +31,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="@dimen/notification_content_margin_top"
- android:layout_marginBottom="@dimen/notification_action_list_height"
+ android:layout_marginBottom="@dimen/notification_content_margin"
android:clipToPadding="false"
android:orientation="vertical"
>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 7b52ec3..5d9e761 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +18,6 @@
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
android:clipChildren="false"
android:tag="call"
android:theme="@style/Theme.DeviceDefault.Notification"
@@ -29,58 +27,46 @@
<include layout="@layout/notification_template_conversation_icon_container" />
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_action_list_height"
- android:orientation="vertical"
+ android:layout_height="80dp"
+ android:orientation="horizontal"
>
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_gravity="top"
- android:orientation="horizontal"
+ android:layout_marginStart="@dimen/conversation_content_start"
+ android:orientation="vertical"
+ android:minHeight="68dp"
>
- <LinearLayout
- android:id="@+id/notification_main_column"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginStart="@dimen/conversation_content_start"
- android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:orientation="vertical"
- android:minHeight="68dp"
- >
-
- <include
- layout="@layout/notification_template_conversation_header"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
-
- <include layout="@layout/notification_template_text" />
-
- <include
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_progress_bar_height"
- android:layout_marginTop="@dimen/notification_progress_margin_top"
- layout="@layout/notification_template_progress"
- />
- </LinearLayout>
-
- <!-- TODO(b/179178086): remove padding from main column when this is visible -->
- <include layout="@layout/notification_expand_button"
+ <include
+ layout="@layout/notification_template_conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="top|end"
/>
+ <include layout="@layout/notification_template_text" />
+
</LinearLayout>
- <include layout="@layout/notification_material_action_list" />
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include
+ layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ />
+
+ </FrameLayout>
</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59fd042..054d108 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5574,6 +5574,8 @@
<string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label">On-screen Accessibility Shortcut Chooser</string>
<!-- Label for triggering hardware accessibility shortcut action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
+ <!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
+ <string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
<!-- Accessibility description of caption view -->
<string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5a7b1fa..b935788 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3115,6 +3115,7 @@
<!-- Notifications: CallStyle -->
<java-symbol type="layout" name="notification_template_material_call" />
+ <java-symbol type="layout" name="notification_template_material_big_call" />
<java-symbol type="string" name="call_notification_answer_action" />
<java-symbol type="string" name="call_notification_decline_action" />
<java-symbol type="string" name="call_notification_hang_up_action" />
@@ -3986,6 +3987,7 @@
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_label" />
<java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
<java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
+ <java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
<java-symbol type="string" name="accessibility_freeform_caption" />
diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java
index 8895f9b..b282064 100644
--- a/core/tests/coretests/src/android/content/ContentProviderTest.java
+++ b/core/tests/coretests/src/android/content/ContentProviderTest.java
@@ -23,6 +23,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.net.Uri;
+import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
@@ -86,4 +87,11 @@
mCp.validateIncomingUri(
Uri.parse("content://com.example/foo/bar?foo=b//ar#foo=b//ar")));
}
+
+ @Test
+ public void testCreateContentUriAsUser() {
+ Uri uri = Uri.parse("content://com.example/foo/bar");
+ Uri expectedUri = Uri.parse("content://7@com.example/foo/bar");
+ assertEquals(expectedUri, ContentProvider.createContentUriAsUser(uri, UserHandle.of(7)));
+ }
}
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
index 8eb13bc..4349021 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -62,6 +62,13 @@
}
@Test
+ public void testIsEmpty_negativeCenter() {
+ RoundedCorner roundedCorner =
+ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT, 1, -2, -3);
+ assertThat(roundedCorner.isEmpty(), is(true));
+ }
+
+ @Test
public void testEquals() {
RoundedCorner roundedCorner = new RoundedCorner(
RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
similarity index 98%
rename from core/tests/mockingcoretests/src/android/view/DisplayTests.java
rename to core/tests/mockingcoretests/src/android/view/DisplayTest.java
index a036db2..f8dd153 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTests.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -56,12 +56,15 @@
*
* <p>Build/Install/Run:
*
- * atest FrameworksMockingCoreTests:android.view.DisplayTests
+ * atest FrameworksMockingCoreTests:android.view.DisplayTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
-public class DisplayTests {
+public class DisplayTest {
private static final int APP_WIDTH = 272;
private static final int APP_HEIGHT = 700;
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index b3a180d..b171599 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -155,6 +155,7 @@
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
<assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="media" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" />
<assign-permission name="android.permission.INTERNET" uid="media" />
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 11cb2b7..7c80f70 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -601,7 +601,7 @@
}
/**
- * Check whether the caller is the credential management app {@link CredentialManagementApp}.
+ * Check whether the caller is the credential management app {@code CredentialManagementApp}.
* The credential management app has the ability to manage the user's KeyChain credentials
* on unmanaged devices.
*
@@ -611,6 +611,7 @@
*
* @return {@code true} if the caller is the credential management app.
*/
+ @WorkerThread
public static boolean isCredentialManagementApp(@NonNull Context context) {
boolean isCredentialManagementApp = false;
try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
@@ -634,6 +635,7 @@
* @return the credential management app's authentication policy.
* @throws SecurityException if the caller is not the credential management app.
*/
+ @WorkerThread
@NonNull
public static AppUriAuthenticationPolicy getCredentialManagementAppPolicy(
@NonNull Context context) throws SecurityException {
@@ -665,6 +667,7 @@
* @hide
*/
@TestApi
+ @WorkerThread
@RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
public static boolean setCredentialManagementApp(@NonNull Context context,
@NonNull String packageName, @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
@@ -680,13 +683,21 @@
}
/**
- * Remove the user's KeyChain credentials on unmanaged devices.
+ * Called by the credential management app {@code CredentialManagementApp} to unregister as
+ * the credential management app and stop managing the user's credentials.
+ *
+ * <p> All credentials previously installed by the credential management app will be removed
+ * from the user's device.
+ *
+ * <p> An app holding {@code MANAGE_CREDENTIAL_MANAGEMENT_APP} permission can also call this
+ * method to remove the current credential management app, even if it's not the current
+ * credential management app itself.
*
* @return {@code true} if the credential management app was successfully removed.
- * @hide
*/
- @TestApi
- @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
+ @WorkerThread
+ @RequiresPermission(value = Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP,
+ conditional = true)
public static boolean removeCredentialManagementApp(@NonNull Context context) {
try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
keyChainConnection.getService().removeCredentialManagementApp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index cb54021..3708e15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -119,7 +119,7 @@
listeners.get(i).onDisplayAreaAppeared(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
@Override
@@ -161,24 +161,27 @@
listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo);
}
}
- applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+ applyConfigChangesToContext(displayAreaInfo);
}
/**
- * Applies the {@link Configuration} to the {@link DisplayAreaContext} specified by
- * {@code displayId}.
- *
- * @param displayId The ID of the {@link Display} which the {@link DisplayAreaContext} is
- * associated with
- * @param newConfig The propagated configuration
+ * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
+ * {@link DisplayAreaInfo#displayId}.
*/
- private void applyConfigChangesToContext(int displayId, @NonNull Configuration newConfig) {
+ private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final int displayId = displayAreaInfo.displayId;
+ final Display display = mContext.getSystemService(DisplayManager.class)
+ .getDisplay(displayId);
+ if (display == null) {
+ throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
+ + "displayAreaInfo:" + displayAreaInfo);
+ }
DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
if (daContext == null) {
- daContext = new DisplayAreaContext(mContext, displayId);
+ daContext = new DisplayAreaContext(mContext, display);
mDisplayAreaContexts.put(displayId, daContext);
}
- daContext.updateConfigurationChanges(newConfig);
+ daContext.updateConfigurationChanges(displayAreaInfo.configuration);
}
/**
@@ -228,10 +231,8 @@
private final IBinder mToken = new Binder();
private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
- public DisplayAreaContext(@NonNull Context context, int displayId) {
+ public DisplayAreaContext(@NonNull Context context, @NonNull Display display) {
super(null);
- final Display display = context.getSystemService(DisplayManager.class)
- .getDisplay(displayId);
attachBaseContext(context.createTokenContext(mToken, display));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 14fbaac..50d8098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -36,6 +36,7 @@
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -149,11 +150,12 @@
context = displayContext;
if (theme != context.getThemeResId() || labelRes != 0) {
try {
- context = context.createPackageContext(
- activityInfo.packageName, CONTEXT_RESTRICTED);
+ context = context.createPackageContextAsUser(activityInfo.packageName,
+ CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
- // Ignore
+ Slog.w(TAG, "Failed creating package context with package name "
+ + activityInfo.packageName + " for user " + taskInfo.userId, e);
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
index afaf33a..c7a1c9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -51,40 +51,40 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- @Postsubmit
+ @Presubmit
@Test
override fun noUncoveredRegions() = super.noUncoveredRegions()
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 3309e10..bf148bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -78,7 +78,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowInsideDisplayBounds() {
testSpec.assertWm {
@@ -86,7 +86,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun bothAppWindowsVisible() {
testSpec.assertWmEnd {
@@ -96,15 +96,15 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerInsideDisplayBounds() {
testSpec.assertLayers {
@@ -112,7 +112,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun bothAppLayersVisible() {
testSpec.assertLayersEnd {
@@ -121,11 +121,11 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e93824d..0112686 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -336,6 +336,7 @@
GET_DEV_PROC(ResetCommandBuffer);
GET_DEV_PROC(ResetFences);
GET_DEV_PROC(WaitForFences);
+ GET_DEV_PROC(FrameBoundaryANDROID);
}
void VulkanManager::initialize() {
@@ -516,6 +517,25 @@
if (semaphore != VK_NULL_HANDLE) {
if (submitted == GrSemaphoresSubmitted::kYes) {
mSwapSemaphore = semaphore;
+ if (mFrameBoundaryANDROID) {
+ // retrieve VkImage used as render target
+ VkImage image = VK_NULL_HANDLE;
+ GrBackendRenderTarget backendRenderTarget =
+ surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
+ if (backendRenderTarget.isValid()) {
+ GrVkImageInfo info;
+ if (backendRenderTarget.getVkImageInfo(&info)) {
+ image = info.fImage;
+ } else {
+ ALOGE("Frame boundary: backend is not vulkan");
+ }
+ } else {
+ ALOGE("Frame boundary: invalid backend render target");
+ }
+ // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+ // it won't wait on it.
+ mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
+ }
} else {
destroy_semaphore(mDestroySemaphoreContext);
mDestroySemaphoreContext = nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 0912369..7b5fe19 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -31,6 +31,21 @@
#include <vk/GrVkExtensions.h>
#include <vulkan/vulkan.h>
+// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
+// (https://github.com/google/agi) to enable profiling of apps rendering via
+// HWUI. This extension is not defined in Khronos, hence the need to declare it
+// manually here. There's a superseding extension (VK_EXT_frame_boundary) being
+// discussed in Khronos, but in the meantime we use the bespoke
+// VK_ANDROID_frame_boundary. This is a device extension that is implemented by
+// AGI's Vulkan capture layer, such that it is only supported by devices when
+// AGI is doing a capture of the app.
+//
+// TODO(b/182165045): use the Khronos blessed VK_EXT_frame_boudary once it has
+// landed in the spec.
+typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore semaphore,
+ VkImage image);
+#define VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME "VK_ANDROID_frame_boundary"
+
#include "Frame.h"
#include "IRenderPipeline.h"
#include "VulkanSurface.h"
@@ -160,6 +175,7 @@
VkPtr<PFN_vkDestroyFence> mDestroyFence;
VkPtr<PFN_vkWaitForFences> mWaitForFences;
VkPtr<PFN_vkResetFences> mResetFences;
+ VkPtr<PFN_vkFrameBoundaryANDROID> mFrameBoundaryANDROID;
VkInstance mInstance = VK_NULL_HANDLE;
VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 548b415..ae64c02 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
-import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
@@ -2937,9 +2936,7 @@
* @return a {@link PlaybackComponent} associated with the session,
* or {@code null} if the session is closed or does not exist.
* @see PlaybackComponent
- * @hide
*/
- @TestApi
@Nullable
public PlaybackComponent getPlaybackComponent(@NonNull byte[] sessionId) {
if (sessionId == null) {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b4db305..5f44b62 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -21,6 +21,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -89,6 +90,7 @@
private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
new CopyOnWriteArrayList<>();
+ // TODO: Specify the fields that are only used (or not used) by system media router.
private final String mClientPackageName;
private final ManagerCallback mManagerCallback;
@@ -132,18 +134,34 @@
}
/**
- * Gets an instance of the media router which controls the app's media routing.
+ * Gets an instance of the system media router which controls the app's media routing.
* Returns {@code null} if the given package name is invalid.
+ * There are several things to note when using the media routers created with this method.
* <p>
- * Note: For media routers created with this method, the discovery preference passed to
- * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
- * with the client app's discovery preference. Therefore, it is recommended to pass
+ * First of all, the discovery preference passed to {@link #registerRouteCallback}
+ * will have no effect. The callback will be called accordingly with the client app's
+ * discovery preference. Therefore, it is recommended to pass
* {@link RouteDiscoveryPreference#EMPTY} there.
+ * <p>
+ * Also, do not keep/compare the instances of the {@link RoutingController}, since they are
+ * always newly created with the latest session information whenever below methods are called:
+ * <ul>
+ * <li> {@link #getControllers()} </li>
+ * <li> {@link #getController(String)}} </li>
+ * <li> {@link TransferCallback#onTransfer(RoutingController, RoutingController)} </li>
+ * <li> {@link TransferCallback#onStop(RoutingController)} </li>
+ * <li> {@link ControllerCallback#onControllerUpdated(RoutingController)} </li>
+ * </ul>
+ * Therefore, in order to track the current routing status, keep the controller's ID instead,
+ * and use {@link #getController(String)} and {@link #getSystemController()} for
+ * getting controllers.
+ * <p>
+ * Finally, it will have no effect to call {@link #setOnGetControllerHintsListener}.
*
* @param clientPackageName the package name of the app to control
* @hide
*/
- //@SystemApi
+ @SystemApi
@Nullable
public static MediaRouter2 getInstance(@NonNull Context context,
@NonNull String clientPackageName) {
@@ -168,12 +186,40 @@
instance = new MediaRouter2(context, clientPackageName);
sSystemMediaRouter2Map.put(clientPackageName, instance);
// TODO: Remove router instance once it is not needed.
- instance.registerManagerCallback();
+ instance.registerManagerCallbackForSystemRouter();
}
return instance;
}
}
+ /**
+ * Starts scanning remote routes.
+ * Note that calling start/stopScan is applied to all system routers in the same process.
+ *
+ * @see #stopScan()
+ * @hide
+ */
+ @SystemApi
+ public void startScan() {
+ if (isSystemRouter()) {
+ sManager.startScan();
+ }
+ }
+
+ /**
+ * Stops scanning remote routes to reduce resource consumption.
+ * Note that calling start/stopScan is applied to all system routers in the same process.
+ *
+ * @see #startScan()
+ * @hide
+ */
+ @SystemApi
+ public void stopScan() {
+ if (isSystemRouter()) {
+ sManager.stopScan();
+ }
+ }
+
private MediaRouter2(Context appContext) {
mContext = appContext;
mMediaRouterService = IMediaRouterService.Stub.asInterface(
@@ -209,13 +255,15 @@
}
private MediaRouter2(Context context, String clientPackageName) {
+ mContext = context;
mClientPackageName = clientPackageName;
mManagerCallback = new ManagerCallback();
- mContext = context;
- mMediaRouterService = null;
- mPackageName = null;
mHandler = new Handler(Looper.getMainLooper());
- mSystemController = null;
+ mSystemController = new SystemRoutingController(sManager.getSystemRoutingSession());
+ mMediaRouterService = null; // TODO: Make this non-null and check permission.
+
+ // Only used by non-system MediaRouter2.
+ mPackageName = null;
}
/**
@@ -240,7 +288,7 @@
* @see #getInstance(Context, String)
* @hide
*/
- //@SystemApi
+ @SystemApi
@Nullable
public String getClientPackageName() {
return mClientPackageName;
@@ -358,7 +406,8 @@
*
* @hide
*/
- //@SystemApi
+ @SystemApi
+ @NonNull
public List<MediaRoute2Info> getAllRoutes() {
if (isSystemRouter()) {
return sManager.getAllRoutes();
@@ -377,10 +426,6 @@
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
- if (isSystemRouter()) {
- return sManager.getAvailableRoutes(mClientPackageName);
- }
-
synchronized (mLock) {
if (mShouldUpdateRoutes) {
mShouldUpdateRoutes = false;
@@ -474,6 +519,9 @@
* {@code null} for unset.
*/
public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) {
+ if (isSystemRouter()) {
+ return;
+ }
mOnGetControllerHintsListener = listener;
}
@@ -519,7 +567,7 @@
* @param route the route you want to transfer the media to.
* @hide
*/
- //@SystemApi
+ @SystemApi
public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
if (isSystemRouter()) {
sManager.transfer(controller.getRoutingSessionInfo(), route);
@@ -606,6 +654,23 @@
}
/**
+ * Gets a {@link RoutingController} whose ID is equal to the given ID.
+ * Returns {@code null} if there is no matching controller.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public RoutingController getController(@NonNull String id) {
+ Objects.requireNonNull(id, "id must not be null");
+ for (RoutingController controller : getControllers()) {
+ if (TextUtils.equals(id, controller.getId())) {
+ return controller;
+ }
+ }
+ return null;
+ }
+
+ /**
* Gets the list of currently active {@link RoutingController routing controllers} on which
* media can be played.
* <p>
@@ -614,15 +679,25 @@
*/
@NonNull
public List<RoutingController> getControllers() {
- // TODO: Do not create the controller instances every time,
- // Instead, update the list using the sessions' ID and session related callbacks.
+ List<RoutingController> result = new ArrayList<>();
+
if (isSystemRouter()) {
- return sManager.getRoutingSessions(mClientPackageName).stream()
- .map(info -> new RoutingController(info))
- .collect(Collectors.toList());
+ // Unlike non-system MediaRouter2, controller instances cannot be kept,
+ // since the transfer events initiated from other apps will not come through manager.
+ List<RoutingSessionInfo> sessions = sManager.getRoutingSessions(mClientPackageName);
+ for (RoutingSessionInfo session : sessions) {
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(session);
+ controller = mSystemController;
+ } else {
+ controller = new RoutingController(session);
+ }
+ result.add(controller);
+ }
+ return result;
}
- List<RoutingController> result = new ArrayList<>();
result.add(0, mSystemController);
synchronized (mLock) {
result.addAll(mNonSystemRoutingControllers.values());
@@ -639,9 +714,15 @@
* @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
* @hide
*/
+ @SystemApi
public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(route, "route must not be null");
+ if (isSystemRouter()) {
+ sManager.setRouteVolume(route, volume);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -928,8 +1009,9 @@
/**
* Registers {@link MediaRouter2Manager.Callback} for getting events.
+ * Should only used for system media routers.
*/
- private void registerManagerCallback() {
+ private void registerManagerCallbackForSystemRouter() {
// Using direct executor here, since MediaRouter2Manager also posts to the main handler.
sManager.registerCallback(Runnable::run, mManagerCallback);
}
@@ -941,6 +1023,16 @@
.collect(Collectors.toList());
}
+ private void updateAllRoutesFromManager() {
+ synchronized (mLock) {
+ mRoutes.clear();
+ for (MediaRoute2Info route : sManager.getAllRoutes()) {
+ mRoutes.put(route.getId(), route);
+ }
+ mShouldUpdateRoutes = true;
+ }
+ }
+
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
@@ -971,6 +1063,13 @@
}
}
+ private void notifyPreferredFeaturesChanged(List<String> features) {
+ for (RouteCallbackRecord record: mRouteCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onPreferredFeaturesChanged(features));
+ }
+ }
+
private void notifyTransfer(RoutingController oldController, RoutingController newController) {
for (TransferCallbackRecord record: mTransferCallbackRecords) {
record.mExecutor.execute(
@@ -1024,6 +1123,17 @@
* @param routes the list of routes that have been changed. It's never empty.
*/
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+
+ /**
+ * Called when the client app's preferred features are changed.
+ * When this is called, it is recommended to {@link #getRoutes()} to get the routes
+ * that are currently available to the app.
+ *
+ * @param preferredFeatures the new preferred features set by the application
+ * @hide
+ */
+ @SystemApi
+ public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
/**
@@ -1131,6 +1241,11 @@
mState = CONTROLLER_STATE_ACTIVE;
}
+ RoutingController(@NonNull RoutingSessionInfo sessionInfo, int state) {
+ mSessionInfo = sessionInfo;
+ mState = state;
+ }
+
/**
* @return the ID of the controller. It is globally unique.
*/
@@ -1291,6 +1406,11 @@
return;
}
+ if (isSystemRouter()) {
+ sManager.selectRoute(getRoutingSessionInfo(), route);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -1338,6 +1458,11 @@
return;
}
+ if (isSystemRouter()) {
+ sManager.deselectRoute(getRoutingSessionInfo(), route);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -1407,6 +1532,12 @@
Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
return;
}
+
+ if (isSystemRouter()) {
+ sManager.setSessionVolume(getRoutingSessionInfo(), volume);
+ return;
+ }
+
MediaRouter2Stub stub;
synchronized (mLock) {
stub = mStub;
@@ -1471,6 +1602,11 @@
mState = CONTROLLER_STATE_RELEASED;
}
+ if (isSystemRouter()) {
+ sManager.releaseSession(getRoutingSessionInfo());
+ return;
+ }
+
synchronized (mLock) {
mNonSystemRoutingControllers.remove(getId(), this);
@@ -1539,6 +1675,12 @@
}
private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
+ if (isSystemRouter()) {
+ return getRoutes().stream()
+ .filter(r -> routeIds.contains(r.getId()))
+ .collect(Collectors.toList());
+ }
+
synchronized (mLock) {
return routeIds.stream().map(mRoutes::get)
.filter(Objects::nonNull)
@@ -1722,12 +1864,17 @@
}
}
+ // Note: All methods are run on main thread.
class ManagerCallback implements MediaRouter2Manager.Callback {
@Override
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> filteredRoutes =
- sManager.filterRoutesForPackage(routes, mClientPackageName);
+ updateAllRoutesFromManager();
+
+ List<MediaRoute2Info> filteredRoutes;
+ synchronized (mLock) {
+ filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+ }
if (filteredRoutes.isEmpty()) {
return;
}
@@ -1739,8 +1886,12 @@
@Override
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> filteredRoutes =
- sManager.filterRoutesForPackage(routes, mClientPackageName);
+ updateAllRoutesFromManager();
+
+ List<MediaRoute2Info> filteredRoutes;
+ synchronized (mLock) {
+ filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+ }
if (filteredRoutes.isEmpty()) {
return;
}
@@ -1752,8 +1903,12 @@
@Override
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> filteredRoutes =
- sManager.filterRoutesForPackage(routes, mClientPackageName);
+ updateAllRoutesFromManager();
+
+ List<MediaRoute2Info> filteredRoutes;
+ synchronized (mLock) {
+ filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+ }
if (filteredRoutes.isEmpty()) {
return;
}
@@ -1764,31 +1919,98 @@
}
@Override
- public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
- // TODO: Call ControllerCallback.onControllerUpdated
- }
-
- @Override
public void onTransferred(@NonNull RoutingSessionInfo oldSession,
- @Nullable RoutingSessionInfo newSession) {
- // TODO: Call TransferCallback.onTransfer
+ @NonNull RoutingSessionInfo newSession) {
+ if (!oldSession.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, oldSession.getClientPackageName())) {
+ return;
+ }
+
+ if (!newSession.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, newSession.getClientPackageName())) {
+ return;
+ }
+
+ // For successful in-session transfer, onControllerUpdated() handles it.
+ if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+ return;
+ }
+
+
+ RoutingController oldController;
+ if (oldSession.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(oldSession);
+ oldController = mSystemController;
+ } else {
+ oldController = new RoutingController(oldSession);
+ }
+
+ RoutingController newController;
+ if (oldSession.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(newSession);
+ newController = mSystemController;
+ } else {
+ newController = new RoutingController(newSession);
+ }
+
+ notifyTransfer(oldController, newController);
}
@Override
public void onTransferFailed(@NonNull RoutingSessionInfo session,
@NonNull MediaRoute2Info route) {
- // TODO: Call TransferCallback.onTransferFailure
+ if (!session.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+ return;
+ }
+ notifyTransferFailure(route);
+ }
+
+ @Override
+ public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
+ if (!session.isSystemSession()
+ && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+ return;
+ }
+
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ mSystemController.setRoutingSessionInfo(session);
+ controller = mSystemController;
+ } else {
+ controller = new RoutingController(session);
+ }
+ notifyControllerUpdated(controller);
}
@Override
public void onSessionReleased(@NonNull RoutingSessionInfo session) {
- // TODO: Call TransferCallback.onStop()
+ if (session.isSystemSession()) {
+ Log.e(TAG, "onSessionReleased: Called on system session. Ignoring.");
+ return;
+ }
+
+ if (!TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+ return;
+ }
+
+ notifyStop(new RoutingController(session, RoutingController.CONTROLLER_STATE_RELEASED));
}
@Override
public void onPreferredFeaturesChanged(@NonNull String packageName,
@NonNull List<String> preferredFeatures) {
- // Does nothing.
+ if (!TextUtils.equals(mClientPackageName, packageName)) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+ preferredFeatures, true).build();
+ }
+
+ updateAllRoutesFromManager();
+ notifyPreferredFeaturesChanged(preferredFeatures);
}
@Override
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index ca619d4..20e3573 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -148,7 +148,7 @@
/**
* Starts scanning remote routes.
- * @see #stopScan(String)
+ * @see #stopScan()
*/
public void startScan() {
Client client = getOrCreateClient();
@@ -163,7 +163,7 @@
/**
* Stops scanning remote routes to reduce resource consumption.
- * @see #startScan(String)
+ * @see #startScan()
*/
public void stopScan() {
Client client = getOrCreateClient();
@@ -788,8 +788,8 @@
* Requests releasing a session.
* <p>
* If a session is released, any operation on the session will be ignored.
- * {@link Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)} with {@code null}
- * session will be called when the session is released.
+ * {@link Callback#onSessionReleased(RoutingSessionInfo)} will be called
+ * when the session is released.
* </p>
*
* @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
@@ -945,10 +945,10 @@
* Called when media is transferred.
*
* @param oldSession the previous session
- * @param newSession the new session or {@code null} if the session is released.
+ * @param newSession the new session
*/
default void onTransferred(@NonNull RoutingSessionInfo oldSession,
- @Nullable RoutingSessionInfo newSession) { }
+ @NonNull RoutingSessionInfo newSession) { }
/**
* Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 4b33366..a045714 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -8,9 +8,13 @@
public class ConnectivityManager {
method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshot();
method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
+ method @NonNull public static String getPrivateDnsMode(@NonNull android.content.ContentResolver);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
+ field public static final String PRIVATE_DNS_MODE_OFF = "off";
+ field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+ field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
}
public final class NetworkAgentConfig implements android.os.Parcelable {
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 7189be1..66c45ed 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -23,6 +23,8 @@
import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
import static android.net.QosCallback.QosCallbackRegistrationException;
+import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
+import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -31,11 +33,13 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -63,6 +67,7 @@
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
@@ -802,24 +807,27 @@
/**
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final String PRIVATE_DNS_MODE_OFF = "off";
/**
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
/**
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
- /**
- * The default Private DNS mode.
- *
- * This may change from release to release or may become dependent upon
- * the capabilities of the underlying platform.
- *
- * @hide
- */
- public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(value = {
+ PRIVATE_DNS_MODE_OFF,
+ PRIVATE_DNS_MODE_OPPORTUNISTIC,
+ PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
+ })
+ public @interface PrivateDnsMode {}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
@@ -5128,4 +5136,24 @@
public static Range<Integer> getIpSecNetIdRange() {
return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1);
}
+
+ /**
+ * Get private DNS mode from settings.
+ *
+ * @param cr The ContentResolver to query private DNS mode from settings.
+ * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ @NonNull
+ @PrivateDnsMode
+ public static String getPrivateDnsMode(@NonNull ContentResolver cr) {
+ String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
+ if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
+ // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
+ // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
+ if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+ return mode;
+ }
}
diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.java b/packages/Connectivity/framework/src/android/net/IpPrefix.java
index d2ee7d1..bf4481a 100644
--- a/packages/Connectivity/framework/src/android/net/IpPrefix.java
+++ b/packages/Connectivity/framework/src/android/net/IpPrefix.java
@@ -113,7 +113,7 @@
// first statement in constructor". We could factor out setting the member variables to an
// init() method, but if we did, then we'd have to make the members non-final, or "error:
// cannot assign a value to final variable address". So we just duplicate the code here.
- Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix);
+ Pair<InetAddress, Integer> ipAndMask = NetworkUtils.legacyParseIpAndMask(prefix);
this.address = ipAndMask.first.getAddress();
this.prefixLength = ipAndMask.second;
checkAndMaskAddressAndPrefixLength();
diff --git a/packages/Connectivity/framework/src/android/net/LinkAddress.java b/packages/Connectivity/framework/src/android/net/LinkAddress.java
index d1bdaa0..d48b8c7 100644
--- a/packages/Connectivity/framework/src/android/net/LinkAddress.java
+++ b/packages/Connectivity/framework/src/android/net/LinkAddress.java
@@ -325,7 +325,7 @@
public LinkAddress(@NonNull String address, int flags, int scope) {
// This may throw an IllegalArgumentException; catching it is the caller's responsibility.
// TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24".
- Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
+ Pair<InetAddress, Integer> ipAndMask = NetworkUtils.legacyParseIpAndMask(address);
init(ipAndMask.first, ipAndMask.second, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN);
}
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 9e42bbe..c0f2628 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -27,8 +27,10 @@
import java.io.FileDescriptor;
import java.math.BigInteger;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.Locale;
import java.util.TreeSet;
@@ -212,7 +214,7 @@
@Deprecated
public static InetAddress numericToInetAddress(String addrString)
throws IllegalArgumentException {
- return InetAddress.parseNumericAddress(addrString);
+ return InetAddresses.parseNumericAddress(addrString);
}
/**
@@ -234,7 +236,7 @@
try {
String[] pieces = ipAndMaskString.split("/", 2);
prefixLength = Integer.parseInt(pieces[1]);
- address = InetAddress.parseNumericAddress(pieces[0]);
+ address = InetAddresses.parseNumericAddress(pieces[0]);
} catch (NullPointerException e) { // Null string.
} catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
} catch (NumberFormatException e) { // Non-numeric prefix.
@@ -249,6 +251,47 @@
}
/**
+ * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64".
+ * @hide
+ *
+ * @deprecated This method is used only for IpPrefix and LinkAddress. Since Android S, use
+ * {@link #parseIpAndMask(String)}, if possible.
+ */
+ @Deprecated
+ public static Pair<InetAddress, Integer> legacyParseIpAndMask(String ipAndMaskString) {
+ InetAddress address = null;
+ int prefixLength = -1;
+ try {
+ String[] pieces = ipAndMaskString.split("/", 2);
+ prefixLength = Integer.parseInt(pieces[1]);
+ if (pieces[0] == null || pieces[0].isEmpty()) {
+ final byte[] bytes = new byte[16];
+ bytes[15] = 1;
+ return new Pair<InetAddress, Integer>(Inet6Address.getByAddress(
+ "ip6-localhost"/* host */, bytes, 0 /* scope_id */), prefixLength);
+ }
+
+ if (pieces[0].startsWith("[")
+ && pieces[0].endsWith("]")
+ && pieces[0].indexOf(':') != -1) {
+ pieces[0] = pieces[0].substring(1, pieces[0].length() - 1);
+ }
+ address = InetAddresses.parseNumericAddress(pieces[0]);
+ } catch (NullPointerException e) { // Null string.
+ } catch (ArrayIndexOutOfBoundsException e) { // No prefix length.
+ } catch (NumberFormatException e) { // Non-numeric prefix.
+ } catch (IllegalArgumentException e) { // Invalid IP address.
+ } catch (UnknownHostException e) { // IP address length is illegal
+ }
+
+ if (address == null || prefixLength == -1) {
+ throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString);
+ }
+
+ return new Pair<InetAddress, Integer>(address, prefixLength);
+ }
+
+ /**
* Convert a 32 char hex string into a Inet6Address.
* throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
* made into an Inet6Address
diff --git a/packages/Connectivity/framework/src/android/net/ProxyInfo.java b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
index 229db0d..745e20f 100644
--- a/packages/Connectivity/framework/src/android/net/ProxyInfo.java
+++ b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
@@ -129,7 +129,7 @@
}
/**
- * Only used in PacProxyInstaller after Local Proxy is bound.
+ * Only used in PacProxyService after Local Proxy is bound.
* @hide
*/
public ProxyInfo(@NonNull Uri pacFileUrl, int localProxyPort) {
diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 340141b..c510079 100644
--- a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -42,9 +42,9 @@
MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
/** Type of this VPN. */
- @VpnManager.VpnType public final int type;
+ public final int type;
- public VpnTransportInfo(@VpnManager.VpnType int type) {
+ public VpnTransportInfo(int type) {
this.type = type;
}
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
index 781bfcd..1ccf417 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
@@ -51,6 +51,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
+ setSingleLineTitle(true);
super.onBindViewHolder(holder);
final View switchView = holder.findViewById(android.R.id.switch_widget);
if (switchView != null) {
diff --git a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
index 541a246..281269ec 100644
--- a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
@@ -223,7 +223,7 @@
*
* @return the uri with added query parameters
*/
- private static Uri uriWithAddedParameters(Context context, Uri baseUri) {
+ public static Uri uriWithAddedParameters(Context context, Uri baseUri) {
Uri.Builder builder = baseUri.buildUpon();
// Add in the preferred language
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 8fd1910..129aca4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -386,7 +386,6 @@
case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
- case BluetoothDevice.UNBOND_REASON_REMOVED:
errorMsg = R.string.bluetooth_pairing_error_message;
break;
default:
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
index e7a20b3..c88ed8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
@@ -1,6 +1,7 @@
# Default reviewers for this and subdirectories.
andychou@google.com
arcwang@google.com
+changbetty@google.com
goldmanj@google.com
qal@google.com
wengsu@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
index 0bde5c0..f3b600c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -2,6 +2,7 @@
andychou@google.com
arcwang@google.com
asapperstein@google.com
+changbetty@google.com
goldmanj@google.com
qal@google.com
wengsu@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 6a4d650e..4bff78f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -353,21 +353,6 @@
}
@Test
- public void showUnbondMessage_reasonRemoved_showCorrectedErrorCode() {
- mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
- mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
- mIntent.putExtra(BluetoothDevice.EXTRA_REASON, BluetoothDevice.UNBOND_REASON_REMOVED);
- when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
- when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
-
- mContext.sendBroadcast(mIntent);
-
- verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
- eq(R.string.bluetooth_pairing_error_message));
- }
-
- @Test
public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e8e10a4..db9b83e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -281,5 +281,6 @@
VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
new InclusiveFloatRangeValidator(0.0f, 1.0f));
VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a0b9528..7288371 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1990,6 +1990,13 @@
dumpSetting(s, p,
Settings.Secure.CARRIER_APPS_HANDLED,
SecureSettingsProto.CARRIER_APPS_HANDLED);
+
+ final long clipboardToken = p.start(SecureSettingsProto.CLIPBOARD);
+ dumpSetting(s, p,
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
+ SecureSettingsProto.Clipboard.SHOW_ACCESS_NOTIFICATIONS);
+ p.end(clipboardToken);
+
dumpSetting(s, p,
Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
SecureSettingsProto.CMAS_ADDITIONAL_BROADCAST_PKG);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b6fd286..6574353 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -54,7 +54,7 @@
name: "SystemUI-sensors",
srcs: [
"src/com/android/systemui/util/sensors/ThresholdSensor.java",
- ]
+ ],
}
android_library {
@@ -99,7 +99,7 @@
"SystemUI-tags",
"SystemUI-proto",
"dagger2",
- "jsr330"
+ "jsr330",
],
manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 9e67e4b..d6204db 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -25,7 +25,10 @@
name: "SystemUIPluginLib",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "bcsmartspace/src/**/*.java",
+ ],
static_libs: [
"PluginCoreLib",
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
new file mode 100644
index 0000000..f8a9a045
--- /dev/null
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.os.Parcelable;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.util.List;
+
+/**
+ * Interface to provide SmartspaceTargets to BcSmartspace.
+ */
+@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
+public interface BcSmartspaceDataPlugin extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
+ int VERSION = 1;
+
+ /** Register a listener to get Smartspace data. */
+ void registerListener(SmartspaceTargetListener listener);
+
+ /** Unregister a listener. */
+ void unregisterListener(SmartspaceTargetListener listener);
+
+ /** Provides Smartspace data to registered listeners. */
+ interface SmartspaceTargetListener {
+ /** Each Parcelable is a SmartspaceTarget that represents a card. */
+ void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
+ }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 70be7c6..a8083f1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -851,8 +851,6 @@
<string name="quick_settings_wifi_label">Wi-Fi</string>
<!-- QuickSettings: Internet [CHAR LIMIT=NONE] -->
<string name="quick_settings_internet_label">Internet</string>
- <!-- QuickSettings: Airplane-safe [CHAR LIMIT=NONE] -->
- <string name="quick_settings_airplane_safe_label">Airplane-safe</string>
<!-- QuickSettings: networks available [CHAR LIMIT=NONE] -->
<string name="quick_settings_networks_available">Networks available</string>
<!-- QuickSettings: networks unavailable [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index a580663..9f32c03 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -185,8 +185,8 @@
if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
}
+ mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
}
- mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
}
protected void verifyPasswordAndUnlock() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index de64f07..40190c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -31,6 +31,7 @@
* A Base class for all Keyguard password/pattern/pin related inputs.
*/
public abstract class KeyguardInputView extends LinearLayout {
+ private Runnable mOnFinishImeAnimationRunnable;
public KeyguardInputView(Context context) {
super(context);
@@ -47,7 +48,8 @@
abstract CharSequence getTitle();
- void animateForIme(float interpolatedFraction) {
+ void animateForIme(float interpolatedFraction, boolean appearingAnim) {
+ return;
}
boolean disallowInterceptTouch(MotionEvent event) {
@@ -85,4 +87,14 @@
};
}
+ public void setOnFinishImeAnimationRunnable(Runnable onFinishImeAnimationRunnable) {
+ mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
+ }
+
+ public void runOnFinishImeAnimationRunnable() {
+ if (mOnFinishImeAnimationRunnable != null) {
+ mOnFinishImeAnimationRunnable.run();
+ mOnFinishImeAnimationRunnable = null;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 533bec1..cd9627f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.view.WindowInsets.Type.ime;
+
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
@@ -23,15 +25,25 @@
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
@@ -41,6 +53,8 @@
private final int mDisappearYTranslation;
+ private static final long IME_DISAPPEAR_DURATION_MS = 125;
+
// A delay constant to be used in a workaround for the situation where InputMethodManagerService
// is not switched to the new user yet.
// TODO: Remove this by ensuring such a race condition never happens.
@@ -150,9 +164,63 @@
}
@Override
- public void animateForIme(float interpolatedFraction) {
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ getWindowInsetsController().controlWindowInsetsAnimation(ime(),
+ 100,
+ Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
+
+ @Override
+ public void onReady(@NonNull WindowInsetsAnimationController controller,
+ int types) {
+ ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
+ anim.addUpdateListener(animation -> {
+ if (controller.isCancelled()) {
+ return;
+ }
+ Insets shownInsets = controller.getShownStateInsets();
+ Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
+ (int) (-shownInsets.bottom / 4
+ * anim.getAnimatedFraction())));
+ controller.setInsetsAndAlpha(insets,
+ (float) animation.getAnimatedValue(),
+ anim.getAnimatedFraction());
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ controller.finish(false);
+ runOnFinishImeAnimationRunnable();
+ finishRunnable.run();
+ }
+ });
+ anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ anim.start();
+ }
+
+ @Override
+ public void onFinished(
+ @NonNull WindowInsetsAnimationController controller) {
+ }
+
+ @Override
+ public void onCancelled(
+ @Nullable WindowInsetsAnimationController controller) {
+ }
+ });
+ return true;
+ }
+
+
+ @Override
+ public void animateForIme(float interpolatedFraction, boolean appearingAnim) {
animate().cancel();
- setAlpha(Math.max(interpolatedFraction, getAlpha()));
+ setAlpha(appearingAnim
+ ? Math.max(interpolatedFraction, getAlpha())
+ : 1 - interpolatedFraction);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 57b8cf0..e45dd8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -189,23 +189,22 @@
return;
}
if (wasDisabled) {
- mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ showInput();
}
}
@Override
public void onResume(int reason) {
super.onResume(reason);
-
- mPasswordEntry.requestFocus();
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
}
private void showInput() {
- mPasswordEntry.post(() -> {
- if (mPasswordEntry.isFocused() && mView.isShown()) {
+ mView.post(() -> {
+ if (mView.isShown()) {
+ mPasswordEntry.requestFocus();
mInputMethodManager.showSoftInput(
mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
}
@@ -214,7 +213,18 @@
@Override
public void onPause() {
- super.onPause();
+ if (!mPasswordEntry.isVisibleToUser()) {
+ // Reset all states directly and then hide IME when the screen turned off.
+ super.onPause();
+ } else {
+ // In order not to break the IME hide animation by resetting states too early after
+ // the password checked, make sure resetting states after the IME hiding animation
+ // finished.
+ mView.setOnFinishImeAnimationRunnable(() -> {
+ mPasswordEntry.clearFocus();
+ super.onPause();
+ });
+ }
mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index eaf8516..4887767 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -23,11 +23,9 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -42,12 +40,9 @@
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
-import android.view.WindowInsetsAnimationControlListener;
-import android.view.WindowInsetsAnimationController;
import android.view.WindowManager;
import android.widget.FrameLayout;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -130,6 +125,9 @@
WindowInsetsAnimation.Bounds bounds) {
if (!mDisappearAnimRunning) {
beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
+ } else {
+ beginJankInstrument(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
return bounds;
@@ -138,25 +136,28 @@
@Override
public WindowInsets onProgress(WindowInsets windowInsets,
List<WindowInsetsAnimation> list) {
- if (mDisappearAnimRunning) {
- mSecurityViewFlipper.setTranslationY(
- mInitialBounds.bottom - mFinalBounds.bottom);
- } else {
- int translationY = 0;
- float interpolatedFraction = 1f;
- for (WindowInsetsAnimation animation : list) {
- if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
- continue;
- }
- interpolatedFraction = animation.getInterpolatedFraction();
-
- final int paddingBottom = (int) MathUtils.lerp(
- mInitialBounds.bottom - mFinalBounds.bottom, 0,
- interpolatedFraction);
- translationY += paddingBottom;
+ float start = mDisappearAnimRunning
+ ? -(mFinalBounds.bottom - mInitialBounds.bottom)
+ : mInitialBounds.bottom - mFinalBounds.bottom;
+ float end = mDisappearAnimRunning
+ ? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f)
+ : 0f;
+ int translationY = 0;
+ float interpolatedFraction = 1f;
+ for (WindowInsetsAnimation animation : list) {
+ if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
+ continue;
}
- mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction);
+ interpolatedFraction = animation.getInterpolatedFraction();
+
+ final int paddingBottom = (int) MathUtils.lerp(
+ start, end,
+ interpolatedFraction);
+ translationY += paddingBottom;
}
+ mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction,
+ !mDisappearAnimRunning);
+
return windowInsets;
}
@@ -164,7 +165,10 @@
public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
- mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f);
+ mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f,
+ true /* appearingAnim */);
+ } else {
+ endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
}
};
@@ -522,63 +526,6 @@
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
- if (securitySelection == SecurityMode.Password) {
- mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
- IME_DISAPPEAR_DURATION_MS,
- Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
-
-
- @Override
- public void onReady(@NonNull WindowInsetsAnimationController controller,
- int types) {
- ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
- anim.addUpdateListener(animation -> {
- if (controller.isCancelled()) {
- return;
- }
- Insets shownInsets = controller.getShownStateInsets();
- Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
- (int) (-shownInsets.bottom / 4
- * anim.getAnimatedFraction())));
- controller.setInsetsAndAlpha(insets,
- (float) animation.getAnimatedValue(),
- anim.getAnimatedFraction());
- });
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- beginJankInstrument(
- InteractionJankMonitor
- .CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- endJankInstrument(
- InteractionJankMonitor
- .CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
- controller.finish(false);
- }
- });
- anim.setDuration(IME_DISAPPEAR_DURATION_MS);
- anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- anim.start();
- }
-
- @Override
- public void onFinished(
- @NonNull WindowInsetsAnimationController controller) {
- mDisappearAnimRunning = false;
- }
-
- @Override
- public void onCancelled(
- @Nullable WindowInsetsAnimationController controller) {
- cancelJankInstrument(
- InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
- }
- });
- }
}
private void beginJankInstrument(int cuj) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 75ef4b32..e01e17d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -87,10 +87,10 @@
* Translate the entire view, and optionally inform the wrapped view of the progress
* so it can animate with the parent.
*/
- public void animateForIme(int translationY, float interpolatedFraction) {
+ public void animateForIme(int translationY, float interpolatedFraction, boolean appearingAnim) {
super.setTranslationY(translationY);
KeyguardInputView v = getSecurityView();
- if (v != null) v.animateForIme(interpolatedFraction);
+ if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 97d6e97..14b9691 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -15,6 +15,7 @@
*/
package com.android.keyguard;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -34,7 +35,9 @@
* Provides background color and radius animations for key pad buttons.
*/
class NumPadAnimator {
- private ValueAnimator mAnimator;
+ private AnimatorSet mAnimator;
+ private ValueAnimator mExpandAnimator;
+ private ValueAnimator mContractAnimator;
private GradientDrawable mBackground;
private RippleDrawable mRipple;
private GradientDrawable mRippleMask;
@@ -55,18 +58,28 @@
mMargin = context.getResources().getDimensionPixelSize(R.dimen.num_pad_key_margin);
// Actual values will be updated later, usually during an onLayout() call
- mAnimator = ValueAnimator.ofFloat(0f);
- mAnimator.setDuration(100);
- mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- mAnimator.setRepeatMode(ValueAnimator.REVERSE);
- mAnimator.setRepeatCount(1);
- mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ mAnimator = new AnimatorSet();
+ mExpandAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mExpandAnimator.setDuration(50);
+ mExpandAnimator.setInterpolator(Interpolators.LINEAR);
+ mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator anim) {
mBackground.setCornerRadius((float) anim.getAnimatedValue());
mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
}
});
+ mContractAnimator = ValueAnimator.ofFloat(1f, 0f);
+ mContractAnimator.setStartDelay(33);
+ mContractAnimator.setDuration(417);
+ mContractAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mContractAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator anim) {
+ mBackground.setCornerRadius((float) anim.getAnimatedValue());
+ mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
+ }
+ });
+ mAnimator.playSequentially(mExpandAnimator, mContractAnimator);
}
void updateMargin(ViewGroup.MarginLayoutParams lp) {
@@ -77,7 +90,8 @@
float startRadius = height / 2f;
float endRadius = height / 4f;
mBackground.setCornerRadius(startRadius);
- mAnimator.setFloatValues(startRadius, endRadius);
+ mExpandAnimator.setFloatValues(startRadius, endRadius);
+ mContractAnimator.setFloatValues(endRadius, startRadius);
}
void start() {
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 5db3349..1635c49 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.VectorDrawable;
import android.util.AttributeSet;
@@ -24,6 +25,8 @@
import android.view.MotionEvent;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -32,13 +35,19 @@
*/
public class NumPadButton extends AlphaOptimizedImageButton {
+ @Nullable
private NumPadAnimator mAnimator;
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
- attrs.getStyleAttribute());
+ Drawable background = getBackground();
+ if (background instanceof LayerDrawable) {
+ mAnimator = new NumPadAnimator(context, (LayerDrawable) background,
+ attrs.getStyleAttribute());
+ } else {
+ mAnimator = null;
+ }
}
@Override
@@ -93,9 +102,11 @@
* By default, the new layout will be enabled. Invoking will revert to the old style
*/
public void disableNewLayout() {
- mAnimator = null;
- ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
- setBackground(getContext().getResources().getDrawable(
- R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ if (mAnimator != null) {
+ mAnimator = null;
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+ setBackground(getContext().getResources().getDrawable(
+ R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index e6a9d4f..96fceee 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -31,6 +32,8 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -48,6 +51,7 @@
private int mTextViewResId;
private PasswordTextView mTextView;
+ @Nullable
private NumPadAnimator mAnimator;
private View.OnClickListener mListener = new View.OnClickListener() {
@@ -127,8 +131,13 @@
setContentDescription(mDigitText.getText().toString());
- mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
- R.style.NumPadKey);
+ Drawable background = getBackground();
+ if (background instanceof LayerDrawable) {
+ mAnimator = new NumPadAnimator(context, (LayerDrawable) background,
+ R.style.NumPadKey);
+ } else {
+ mAnimator = null;
+ }
}
/**
@@ -136,10 +145,12 @@
*/
public void disableNewLayout() {
findViewById(R.id.klondike_text).setVisibility(View.VISIBLE);
- mAnimator = null;
- ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
- setBackground(getContext().getResources().getDrawable(
- R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ if (mAnimator != null) {
+ mAnimator = null;
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+ setBackground(getContext().getResources().getDrawable(
+ R.drawable.ripple_drawable_pin, ctw.getTheme()));
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 5ffc283..b80f8bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -121,9 +121,19 @@
public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setFocusableInTouchMode(true);
- setFocusable(true);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
+ TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.View);
+ try {
+ // If defined, use the provided values. If not, set them to true by default.
+ boolean isFocusable = a.getBoolean(android.R.styleable.View_focusable,
+ /* defValue= */ true);
+ boolean isFocusableInTouchMode = a.getBoolean(
+ android.R.styleable.View_focusableInTouchMode, /* defValue= */ true);
+ setFocusable(isFocusable);
+ setFocusableInTouchMode(isFocusableInTouchMode);
+ } finally {
+ a.recycle();
+ }
+ a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
try {
mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 1bf7474..c051b69 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -53,12 +53,18 @@
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.util.Assert;
import java.util.Locale;
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Class to register system actions with accessibility framework.
*/
@@ -128,23 +134,39 @@
public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
+ public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
+ AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
+
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- private SystemActionsBroadcastReceiver mReceiver;
+ private final SystemActionsBroadcastReceiver mReceiver;
private Locale mLocale;
- private AccessibilityManager mA11yManager;
+ private final AccessibilityManager mA11yManager;
+ private final Lazy<StatusBar> mStatusBar;
+ private final NotificationShadeWindowController mNotificationShadeController;
+ private final StatusBarWindowCallback mNotificationShadeCallback;
+ private boolean mDismissNotificationShadeActionRegistered;
@Inject
- public SystemActions(Context context) {
+ public SystemActions(Context context,
+ NotificationShadeWindowController notificationShadeController,
+ Lazy<StatusBar> statusBar) {
super(context);
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
mA11yManager = (AccessibilityManager) mContext.getSystemService(
Context.ACCESSIBILITY_SERVICE);
+ mNotificationShadeController = notificationShadeController;
+ // Saving in instance variable since to prevent GC since
+ // NotificationShadeWindowController.registerCallback() only keeps weak references.
+ mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing) ->
+ registerOrUnregisterDismissNotificationShadeAction();
+ mStatusBar = statusBar;
}
@Override
public void start() {
+ mNotificationShadeController.registerCallback(mNotificationShadeCallback);
mContext.registerReceiverForAllUsers(
mReceiver,
mReceiver.createIntentFilter(),
@@ -210,6 +232,32 @@
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
mA11yManager.registerSystemAction(
actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
+ registerOrUnregisterDismissNotificationShadeAction();
+ }
+
+ private void registerOrUnregisterDismissNotificationShadeAction() {
+ Assert.isMainThread();
+
+ // Saving state in instance variable since this callback is called quite often to avoid
+ // binder calls
+ StatusBar statusBar = mStatusBar.get();
+ if (statusBar.isPanelExpanded() && !statusBar.isKeyguardShowing()) {
+ if (!mDismissNotificationShadeActionRegistered) {
+ mA11yManager.registerSystemAction(
+ createRemoteAction(
+ R.string.accessibility_system_action_dismiss_notification_shade,
+ SystemActionsBroadcastReceiver
+ .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE),
+ SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+ mDismissNotificationShadeActionRegistered = true;
+ }
+ } else {
+ if (mDismissNotificationShadeActionRegistered) {
+ mA11yManager.unregisterSystemAction(
+ SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+ mDismissNotificationShadeActionRegistered = false;
+ }
+ }
}
/**
@@ -261,10 +309,15 @@
R.string.accessibility_system_action_on_screen_a11y_shortcut_chooser_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER;
break;
- case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
+ case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
labelId = R.string.accessibility_system_action_hardware_a11y_shortcut_label;
intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT;
break;
+ case SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
+ labelId = R.string.accessibility_system_action_dismiss_notification_shade;
+ intent = SystemActionsBroadcastReceiver
+ .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
+ break;
default:
return;
}
@@ -369,6 +422,10 @@
mA11yManager.performAccessibilityShortcut();
}
+ private void handleAccessibilityDismissNotificationShade() {
+ mStatusBar.get().animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, false /* force */);
+ }
+
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -384,6 +441,8 @@
"SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU";
private static final String INTENT_ACTION_ACCESSIBILITY_SHORTCUT =
"SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
+ private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
+ "SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
private PendingIntent createPendingIntent(Context context, String intentAction) {
switch (intentAction) {
@@ -397,7 +456,8 @@
case INTENT_ACTION_TAKE_SCREENSHOT:
case INTENT_ACTION_ACCESSIBILITY_BUTTON:
case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
- case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
+ case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
+ case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
Intent intent = new Intent(intentAction);
intent.setPackage(context.getPackageName());
return PendingIntent.getBroadcast(context, 0, intent,
@@ -422,6 +482,7 @@
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
+ intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
return intentFilter;
}
@@ -473,6 +534,10 @@
handleAccessibilityShortcut();
break;
}
+ case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
+ handleAccessibilityDismissNotificationShade();
+ break;
+ }
default:
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 45cefcc..efb7992 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -31,7 +31,6 @@
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.sensors.ThresholdSensor;
import java.io.FileDescriptor;
@@ -68,7 +67,6 @@
private final SingleTapClassifier mSingleTapClassifier;
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
- private final KeyguardStateController mKeyguardStateController;
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
@@ -115,8 +113,7 @@
DockManager dockManager, MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
- HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
- @TestHarness boolean testHarness) {
+ HistoryTracker historyTracker, @TestHarness boolean testHarness) {
mDataProvider = falsingDataProvider;
mDockManager = dockManager;
mMetricsLogger = metricsLogger;
@@ -124,7 +121,6 @@
mSingleTapClassifier = singleTapClassifier;
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
- mKeyguardStateController = keyguardStateController;
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
@@ -138,10 +134,6 @@
@Override
public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
- if (skipFalsing()) {
- return false;
- }
-
boolean result;
mDataProvider.setInteractionType(interactionType);
@@ -203,10 +195,6 @@
@Override
public boolean isFalseTap(boolean robustCheck, double falsePenalty) {
- if (skipFalsing()) {
- return false;
- }
-
FalsingClassifier.Result singleTapResult =
mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents());
mPriorResults = Collections.singleton(singleTapResult);
@@ -245,10 +233,6 @@
@Override
public boolean isFalseDoubleTap() {
- if (skipFalsing()) {
- return false;
- }
-
FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture();
mPriorResults = Collections.singleton(result);
if (result.isFalse()) {
@@ -262,10 +246,6 @@
return result.isFalse();
}
- private boolean skipFalsing() {
- return !mKeyguardStateController.isShowing();
- }
-
@Override
public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
// TODO: some of these classifiers might allow us to abort early, meaning we don't have to
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index b359860..e090006 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -27,7 +27,6 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.SystemClock;
@@ -49,7 +48,6 @@
private final HistoryTracker mHistoryTracker;
private final ProximitySensor mProximitySensor;
private final StatusBarStateController mStatusBarStateController;
- private final KeyguardStateController mKeyguardStateController;
private final SystemClock mSystemClock;
private int mState;
@@ -89,14 +87,13 @@
FalsingCollectorImpl(FalsingDataProvider falsingDataProvider, FalsingManager falsingManager,
KeyguardUpdateMonitor keyguardUpdateMonitor, HistoryTracker historyTracker,
ProximitySensor proximitySensor, StatusBarStateController statusBarStateController,
- KeyguardStateController keyguardStateController, SystemClock systemClock) {
+ SystemClock systemClock) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mHistoryTracker = historyTracker;
mProximitySensor = proximitySensor;
mStatusBarStateController = statusBarStateController;
- mKeyguardStateController = keyguardStateController;
mSystemClock = systemClock;
@@ -258,10 +255,6 @@
@Override
public void onTouchEvent(MotionEvent ev) {
- if (!mKeyguardStateController.isShowing()) {
- avoidGesture();
- return;
- }
// We delay processing down events to see if another component wants to process them.
// If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
// will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
@@ -283,8 +276,8 @@
@Override
public void avoidGesture() {
- mAvoidGesture = true;
if (mPendingDownEvent != null) {
+ mAvoidGesture = true;
mPendingDownEvent.recycle();
mPendingDownEvent = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
new file mode 100644
index 0000000..3bfdcae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.service.controls.DeviceTypes.DeviceType
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.controls.ui.ControlViewHolder
+
+/**
+ * Interface for logging UI events related to controls
+ */
+interface ControlsMetricsLogger {
+
+ /**
+ * Assign a new instance id for this controls session, defined as when the controls area is
+ * made visible to when it is closed.
+ */
+ fun assignInstanceId()
+
+ fun touch(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_TOUCH.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ fun drag(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_DRAG.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ fun longPress(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_LONG_PRESS.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ fun refreshBegin(uid: Int, isLocked: Boolean) {
+ assignInstanceId()
+ log(ControlsEvents.CONTROL_REFRESH_BEGIN.id, 0, uid, isLocked)
+ }
+
+ fun refreshEnd(cvh: ControlViewHolder, isLocked: Boolean) {
+ log(ControlsEvents.CONTROL_REFRESH_END.id, cvh.deviceType, cvh.uid, isLocked)
+ }
+
+ /**
+ * Logs a controls-related event
+ *
+ * @param eventId Main UIEvent to capture
+ * @param deviceType One of {@link android.service.controls.DeviceTypes}
+ * @param packageName Package name of the service that receives the request
+ * @param isLocked Is the device locked at the start of the action?
+ */
+ fun log(
+ eventId: Int,
+ @DeviceType deviceType: Int,
+ uid: Int,
+ isLocked: Boolean
+ )
+
+ private enum class ControlsEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "User touched a control")
+ CONTROL_TOUCH(714),
+
+ @UiEvent(doc = "User dragged a control")
+ CONTROL_DRAG(713),
+
+ @UiEvent(doc = "User long-pressed a control")
+ CONTROL_LONG_PRESS(715),
+
+ @UiEvent(doc = "User has opened controls, and a state refresh has begun")
+ CONTROL_REFRESH_BEGIN(716),
+
+ @UiEvent(doc = "User has opened controls, and a state refresh has ended")
+ CONTROL_REFRESH_END(717);
+
+ override fun getId() = metricId
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
new file mode 100644
index 0000000..c165632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.service.controls.DeviceTypes.DeviceType
+
+import com.android.internal.logging.InstanceIdSequence
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+
+import javax.inject.Inject
+
+/**
+ * Implementation for logging UI events related to controls
+ */
+@SysUISingleton
+class ControlsMetricsLoggerImpl @Inject constructor() : ControlsMetricsLogger {
+
+ companion object {
+ private const val INSTANCE_ID_MAX = 1 shl 13
+ }
+
+ private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+ private var instanceId = 0
+
+ override fun assignInstanceId() {
+ instanceId = instanceIdSequence.newInstanceId().id
+ }
+
+ /**
+ * {@see ControlsMetricsLogger#log}
+ */
+ override fun log(
+ eventId: Int,
+ @DeviceType deviceType: Int,
+ uid: Int,
+ isLocked: Boolean
+ ) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.DEVICE_CONTROL_CHANGED,
+ eventId,
+ instanceId,
+ deviceType,
+ uid,
+ isLocked
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index ed625de..a165bb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -18,6 +18,8 @@
import android.app.Activity
import android.content.pm.PackageManager
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsMetricsLoggerImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -80,6 +82,9 @@
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
@Binds
+ abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
+
+ @Binds
abstract fun provideControlActionCoordinator(
coordinator: ControlActionCoordinatorImpl
): ControlActionCoordinator
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 58a5981..477c220 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -18,7 +18,6 @@
import android.annotation.MainThread
import android.app.Dialog
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@@ -33,6 +32,7 @@
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsComponent
@@ -54,13 +54,15 @@
private val globalActionsComponent: GlobalActionsComponent,
private val taskViewFactory: Optional<TaskViewFactory>,
private val broadcastDispatcher: BroadcastDispatcher,
- private val lazyUiController: Lazy<ControlsUiController>
+ private val lazyUiController: Lazy<ControlsUiController>,
+ private val controlsMetricsLogger: ControlsMetricsLogger
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
-
+ private val isLocked: Boolean
+ get() = !keyguardStateController.isUnlocked()
override var activityContext: Context? = null
companion object {
@@ -73,6 +75,7 @@
}
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
+ controlsMetricsLogger.touch(cvh, isLocked)
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
cvh.action(BooleanAction(templateId, !isChecked))
@@ -80,6 +83,7 @@
}
override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
+ controlsMetricsLogger.touch(cvh, isLocked)
val blockable = cvh.usePanel()
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
@@ -100,12 +104,14 @@
}
override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
+ controlsMetricsLogger.drag(cvh, isLocked)
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
cvh.action(FloatAction(templateId, newValue))
}, false /* blockable */))
}
override fun longPress(cvh: ControlViewHolder) {
+ controlsMetricsLogger.longPress(cvh, isLocked)
bouncerOrRun(createAction(cvh.cws.ci.controlId, {
// Long press snould only be called when there is valid control state, otherwise ignore
cvh.cws.control?.let {
@@ -116,7 +122,7 @@
}
override fun runPendingAction(controlId: String) {
- if (!keyguardStateController.isUnlocked()) return
+ if (isLocked) return
if (pendingAction?.controlId == controlId) {
pendingAction?.invoke()
pendingAction = null
@@ -141,28 +147,17 @@
@VisibleForTesting
fun bouncerOrRun(action: Action) {
if (keyguardStateController.isShowing()) {
- var closeDialog = !keyguardStateController.isUnlocked()
- if (closeDialog) {
+ if (isLocked) {
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// pending actions will only run after the control state has been refreshed
pendingAction = action
}
-
+ val wasLocked = isLocked
activityStarter.dismissKeyguardThenExecute({
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
- if (closeDialog) {
- activityContext?.let {
- val i = Intent().apply {
- component = ComponentName(context, ControlsActivity::class.java)
- addFlags(
- Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
- }
- it.startActivity(i)
- } ?: run {
- globalActionsComponent.handleShowGlobalActionsMenu()
- }
+ if (wasLocked && activityContext == null) {
+ globalActionsComponent.handleShowGlobalActionsMenu()
} else {
action.invoke()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 9d92a40..3e02890 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -49,6 +49,7 @@
import com.android.internal.graphics.ColorUtils
import com.android.systemui.Interpolators
import com.android.systemui.R
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
import kotlin.reflect.KClass
@@ -63,7 +64,9 @@
val controlsController: ControlsController,
val uiExecutor: DelayableExecutor,
val bgExecutor: DelayableExecutor,
- val controlActionCoordinator: ControlActionCoordinator
+ val controlActionCoordinator: ControlActionCoordinator,
+ val controlsMetricsLogger: ControlsMetricsLogger,
+ val uid: Int
) {
companion object {
@@ -141,7 +144,7 @@
status.setSelected(true)
}
- fun bindData(cws: ControlWithState) {
+ fun bindData(cws: ControlWithState, isLocked: Boolean) {
// If an interaction is in progress, the update may visually interfere with the action the
// action the user wants to make. Don't apply the update, and instead assume a new update
// will coming from when the user interaction is complete.
@@ -171,10 +174,16 @@
controlActionCoordinator.runPendingAction(cws.ci.controlId)
}
+ val wasLoading = isLoading
isLoading = false
behavior = bindBehavior(behavior,
findBehaviorClass(controlStatus, controlTemplate, deviceType))
updateContentDescription()
+
+ // Only log one event per control, at the moment we have determined that the control
+ // switched from the loading to done state
+ val doneLoading = wasLoading && !isLoading
+ if (doneLoading) controlsMetricsLogger.refreshEnd(this, isLocked)
}
fun actionResponse(@ControlAction.ResponseResult response: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 0f7f48f..d08882b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -43,6 +43,7 @@
import android.widget.Space
import android.widget.TextView
import com.android.systemui.R
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlInfo
@@ -58,6 +59,7 @@
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.text.Collator
@@ -77,7 +79,9 @@
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
private val shadeController: ShadeController,
- private val iconCache: CustomIconCache
+ private val iconCache: CustomIconCache,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val keyguardStateController: KeyguardStateController
) : ControlsUiController {
companion object {
@@ -133,7 +137,8 @@
return object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
val lastItems = serviceInfos.map {
- SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName)
+ val uid = it.serviceInfo.applicationInfo.uid
+ SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
}
uiExecutor.execute {
parent.removeAllViews()
@@ -282,8 +287,19 @@
private fun showControlsView(items: List<SelectionItem>) {
controlViewsById.clear()
- createListView()
- createDropDown(items)
+ val itemsByComponent = items.associateBy { it.componentName }
+ val itemsWithStructure = mutableListOf<SelectionItem>()
+ allStructures.mapNotNullTo(itemsWithStructure) {
+ itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
+ }
+ itemsWithStructure.sortWith(localeComparator)
+
+ val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
+
+ controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+
+ createListView(selectionItem)
+ createDropDown(itemsWithStructure, selectionItem)
createMenu()
}
@@ -325,22 +341,13 @@
})
}
- private fun createDropDown(items: List<SelectionItem>) {
+ private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
items.forEach {
RenderInfo.registerComponentIcon(it.componentName, it.icon)
}
- val itemsByComponent = items.associateBy { it.componentName }
- val itemsWithStructure = mutableListOf<SelectionItem>()
- allStructures.mapNotNullTo(itemsWithStructure) {
- itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
- }
- itemsWithStructure.sortWith(localeComparator)
-
- val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
-
var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
- addAll(itemsWithStructure)
+ addAll(items)
}
/*
@@ -349,13 +356,13 @@
* a similar effect
*/
val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply {
- setText(selectionItem.getTitle())
+ setText(selected.getTitle())
// override the default color on the dropdown drawable
(getBackground() as LayerDrawable).getDrawable(0)
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
}
- if (itemsWithStructure.size == 1) {
+ if (items.size == 1) {
spinner.setBackground(null)
return
}
@@ -388,7 +395,7 @@
})
}
- private fun createListView() {
+ private fun createListView(selected: SelectionItem) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
@@ -421,9 +428,11 @@
controlsController.get(),
uiExecutor,
bgExecutor,
- controlActionCoordinator
+ controlActionCoordinator,
+ controlsMetricsLogger,
+ selected.uid
)
- cvh.bindData(it)
+ cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
controlViewsById.put(key, cvh)
}
}
@@ -528,6 +537,7 @@
}
override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+ val isLocked = !keyguardStateController.isUnlocked()
controls.forEach { c ->
controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
@@ -536,8 +546,8 @@
val key = ControlKey(componentName, c.getControlId())
controlsById.put(key, cws)
- uiExecutor.execute {
- controlViewsById.get(key)?.bindData(cws)
+ controlViewsById.get(key)?.let {
+ uiExecutor.execute { it.bindData(cws, isLocked) }
}
}
}
@@ -566,7 +576,8 @@
val appName: CharSequence,
val structure: CharSequence,
val icon: Drawable,
- val componentName: ComponentName
+ val componentName: ComponentName,
+ val uid: Int
) {
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index baf35f2..45ecae0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -364,13 +364,6 @@
/** */
@Provides
@SysUISingleton
- public SystemActions providesSystemActions(Context context) {
- return new SystemActions(context);
- }
-
- /** */
- @Provides
- @SysUISingleton
public Choreographer providesChoreographer() {
return Choreographer.getInstance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 17f7ccf..97803c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -149,9 +149,14 @@
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) {
- // TODO(bc-unlock): Calls KeyguardViewMediator#setOccluded to update the state and
- // run animation.
try {
+ if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
+ mBinder.setOccluded(true /* isOccluded */, true /* animate */);
+ } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) {
+ mBinder.setOccluded(false /* isOccluded */, true /* animate */);
+ }
+ // TODO(bc-unlock): Implement occlude/unocclude animation applied on apps,
+ // wallpapers and nonApps.
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 14a3fc0..1a17e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -416,7 +416,6 @@
}
} else {
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
- state.label = r.getString(R.string.quick_settings_airplane_safe_label);
}
} else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
@@ -480,9 +479,6 @@
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
} else {
- if (cb.mAirplaneModeEnabled) {
- state.label = r.getString(R.string.quick_settings_airplane_safe_label);
- }
state.icon = new SignalIcon(cb.mMobileSignalIconId);
state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
getMobileDataContentName(cb));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index d3065aa..8c21e76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -349,7 +349,9 @@
invalidateOutline();
selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
mForceSelectNextLayout = false;
- updateExpandButtons(mExpandable);
+ // TODO(b/182314698): move this to onMeasure. This requires switching to getMeasuredHeight,
+ // and also requires revisiting all of the logic called earlier in this method.
+ updateExpandButtonsDuringLayout(mExpandable, true /* duringLayout */);
}
@Override
@@ -1344,9 +1346,7 @@
}
ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- LinearLayout actionContainerLayout =
- layout.findViewById(com.android.internal.R.id.actions_container_layout);
- if (bubbleButton == null || actionContainer == null || actionContainerLayout == null) {
+ if (bubbleButton == null || actionContainer == null) {
return;
}
boolean isPersonWithShortcut =
@@ -1589,6 +1589,10 @@
}
public void updateExpandButtons(boolean expandable) {
+ updateExpandButtonsDuringLayout(expandable, false /* duringLayout */);
+ }
+
+ private void updateExpandButtonsDuringLayout(boolean expandable, boolean duringLayout) {
mExpandable = expandable;
// if the expanded child has the same height as the collapsed one we hide it.
if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
@@ -1602,14 +1606,15 @@
expandable = false;
}
}
+ boolean requestLayout = duringLayout && mIsContentExpandable != expandable;
if (mExpandedChild != null) {
- mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
+ mExpandedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
}
if (mContractedChild != null) {
- mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
+ mContractedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
}
if (mHeadsUpChild != null) {
- mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener);
+ mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
}
mIsContentExpandable = expandable;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index fb0fdcc..383bb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -147,8 +147,11 @@
override fun setRemoteInputVisible(visible: Boolean) =
conversationLayout.showHistoricMessages(visible)
- override fun updateExpandability(expandable: Boolean, onClickListener: View.OnClickListener?) =
- conversationLayout.updateExpandability(expandable, onClickListener)
+ override fun updateExpandability(
+ expandable: Boolean,
+ onClickListener: View.OnClickListener,
+ requestLayout: Boolean
+ ) = conversationLayout.updateExpandability(expandable, onClickListener)
override fun disallowSingleClick(x: Float, y: Float): Boolean {
val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index bdafd23..5a55545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -261,7 +261,8 @@
}
@Override
- public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+ boolean requestLayout) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mAltExpandTarget != null) {
@@ -273,6 +274,13 @@
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
+ // Unfortunately, the NotificationContentView has to layout its children in order to
+ // determine their heights, and that affects the button visibility. If that happens
+ // (thankfully it is rare) then we need to request layout of the expand button's parent
+ // in order to ensure it gets laid out correctly.
+ if (requestLayout) {
+ mExpandButton.getParent().requestLayout();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 9ced12d..3a7b461 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -291,8 +291,10 @@
*
* @param expandable should this view be expandable
* @param onClickListener the listener to invoke when the expand affordance is clicked on
+ * @param requestLayout the expandability changed during onLayout, so a requestLayout required
*/
- public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+ boolean requestLayout) {}
/** Set the expanded state on the view wrapper */
public void setExpanded(boolean expanded) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index f65ae0c..1d30736 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -324,7 +324,7 @@
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
- RemoteViews header = builder.makeNotificationHeader();
+ RemoteViews header = builder.makeNotificationGroupHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 1d18750..ddfa63a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -480,7 +480,7 @@
@WMSingleton
@Provides
static StartingWindowController provideStartingWindowController(Context context,
- @ShellAnimationThread ShellExecutor executor, TransactionPool pool) {
+ @ShellSplashscreenThread ShellExecutor executor, TransactionPool pool) {
return new StartingWindowController(context, executor, pool);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 49ba646..6272277 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -16,13 +16,19 @@
package com.android.keyguard;
+import static android.view.WindowInsets.Type.ime;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowInsetsController;
@@ -33,6 +39,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.FalsingManager;
@@ -84,18 +91,34 @@
@Mock
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@Mock
+ private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
+ @Mock
+ private KeyguardMessageArea mKeyguardMessageArea;
+ @Mock
private ConfigurationController mConfigurationController;
@Mock
private KeyguardViewController mKeyguardViewController;
private FalsingManager mFalsingManager = new FalsingManagerFake();
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
+ private KeyguardPasswordViewController mKeyguardPasswordViewController;
+ private KeyguardPasswordView mKeyguardPasswordView;
@Before
public void setup() {
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+ mKeyguardPasswordView = spy(new KeyguardPasswordView(getContext()));
+ when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
+ when(mKeyguardPasswordView.findViewById(R.id.keyguard_message_area))
+ .thenReturn(mKeyguardMessageArea);
+ when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+ mKeyguardPasswordViewController = new KeyguardPasswordViewController(
+ (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
+ SecurityMode.Password, mLockPatternUtils, null,
+ mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class),
+ null);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
@@ -125,14 +148,13 @@
public void startDisappearAnimation_animatesKeyboard() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
SecurityMode.Password);
- when(mInputViewController.getSecurityMode()).thenReturn(
- SecurityMode.Password);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
- .thenReturn(mInputViewController);
- mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+ mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
mKeyguardSecurityContainerController.startDisappearAnimation(null);
- verify(mInputViewController).startDisappearAnimation(eq(null));
+ verify(mWindowInsetsController).controlWindowInsetsAnimation(
+ eq(ime()), anyLong(), any(), any(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 1783fa4..104318e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -19,9 +19,6 @@
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -90,13 +87,6 @@
}
@Test
- public void startDisappearAnimation_animatesKeyboard() {
- mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
- verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
- any(), any());
- }
-
- @Test
public void onMeasure_usesFullWidthWithoutOneHandedMode() {
setUpKeyguard(
/* deviceConfigCanUseOneHandedKeyguard= */false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index d015f51..b232850 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -35,7 +35,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
import com.android.systemui.dock.DockManagerFake;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,10 +71,7 @@
private FalsingClassifier mClassifierB;
private final List<MotionEvent> mMotionEventList = new ArrayList<>();
@Mock
- private HistoryTracker mHistoryTracker;
- @Mock
- private KeyguardStateController mKeyguardStateController;
-
+ private HistoryTracker mHistoryTracker;;
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
@@ -92,10 +88,9 @@
mClassifiers.add(mClassifierA);
mClassifiers.add(mClassifierB);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
- when(mKeyguardStateController.isShowing()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
- mHistoryTracker, mKeyguardStateController, false);
+ mHistoryTracker, false);
ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
@@ -125,7 +120,7 @@
}
@Test
- public void testIsFalseTouch_ClassifiersPass() {
+ public void testIsFalseTouch_ClassffiersPass() {
assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
}
@@ -238,18 +233,4 @@
assertThat(mFakeExecutor.numPending()).isEqualTo(0);
}
-
- @Test
- public void testNoFalsingUnlocked() {
- when(mKeyguardStateController.isShowing()).thenReturn(false);
-
- when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mFalsedResult);
- assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
-
- when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mFalsedResult);
- assertThat(mBrightLineFalsingManager.isFalseTap(false, 0)).isFalse();
-
- when(mDoubleTapClassifier.classifyGesture()).thenReturn(mFalsedResult);
- assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse();
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index e6aeee7..dc79b88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -34,7 +34,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -63,19 +62,16 @@
private ProximitySensor mProximitySensor;
@Mock
private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- when(mKeyguardStateController.isShowing()).thenReturn(true);
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
- mStatusBarStateController, mKeyguardStateController, new FakeSystemClock());
+ mStatusBarStateController, new FakeSystemClock());
}
@Test
@@ -163,20 +159,4 @@
mFalsingCollector.onTouchEvent(up);
verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
}
-
- @Test
- public void testAvoidUnlocked() {
- MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
- MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-
- when(mKeyguardStateController.isShowing()).thenReturn(false);
-
- // Nothing passed initially
- mFalsingCollector.onTouchEvent(down);
- verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
-
- // Up event would normally flush the up event.
- mFalsingCollector.onTouchEvent(up);
- verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 9278570..17eb15b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -19,6 +19,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -63,6 +64,8 @@
private lateinit var taskViewFactory: Optional<TaskViewFactory>
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var cvh: ControlViewHolder
+ @Mock
+ private lateinit var metricsLogger: ControlsMetricsLogger
companion object {
fun <T> any(): T = Mockito.any<T>()
@@ -86,7 +89,8 @@
globalActionsComponent,
taskViewFactory,
getFakeBroadcastDispatcher(),
- lazyUiController
+ lazyUiController,
+ metricsLogger
))
`when`(cvh.cws.ci.controlId).thenReturn(ID)
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
index f8b9309..f0de811 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
@@ -360,7 +360,7 @@
try {
mCallback.setProxyPort(port);
} catch (RemoteException e) {
- Log.w(TAG, "Proxy failed to report port to PacProxyInstaller", e);
+ Log.w(TAG, "Proxy failed to report port to PacProxyService", e);
}
}
mPort = port;
@@ -371,7 +371,7 @@
try {
callback.setProxyPort(mPort);
} catch (RemoteException e) {
- Log.w(TAG, "Proxy failed to report port to PacProxyInstaller", e);
+ Log.w(TAG, "Proxy failed to report port to PacProxyService", e);
}
}
mCallback = callback;
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
index bdf478d..a8e2622 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
@@ -30,7 +30,7 @@
private static ProxyServer server = null;
- /** Keep these values up-to-date with PacProxyInstaller.java */
+ /** Keep these values up-to-date with PacProxyService.java */
public static final String KEY_PROXY = "keyProxy";
public static final String HOST = "localhost";
public static final String EXCL_LIST = "";
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index c7f0efa..3d07da5 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@
private static final int INVALID_ID = -1;
private int mIdOfLastServiceToMagnify = INVALID_ID;
+ private boolean mMagnificationActivated = false;
DisplayMagnification(int displayId) {
mDisplayId = displayId;
@@ -322,6 +323,13 @@
mSpecAnimationBridge, spec, animationCallback);
mControllerCtx.getHandler().sendMessage(m);
}
+
+ final boolean lastMagnificationActivated = mMagnificationActivated;
+ mMagnificationActivated = spec.scale > 1.0f;
+ if (mMagnificationActivated != lastMagnificationActivated) {
+ mMagnificationRequestObserver.onFullScreenMagnificationActivationState(
+ mMagnificationActivated);
+ }
}
/**
@@ -1506,5 +1514,14 @@
* @param serviceId the ID of the service requesting the change
*/
void onRequestMagnificationSpec(int displayId, int serviceId);
+
+ /**
+ * Called when the state of the magnification activation is changed.
+ * It is for the logging data of the magnification activation state.
+ *
+ * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
+ */
+ @GuardedBy("mLock")
+ void onFullScreenMagnificationActivationState(boolean activated);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 2a65b64..17a7d39 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.magnification;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
@@ -25,11 +26,13 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.SystemClock;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilityManagerService;
@@ -51,7 +54,8 @@
* </ol>
*/
public class MagnificationController implements WindowMagnificationManager.Callback,
- MagnificationGestureHandler.Callback {
+ MagnificationGestureHandler.Callback,
+ FullScreenMagnificationController.MagnificationRequestObserver {
private static final boolean DEBUG = false;
private static final String TAG = "MagnificationController";
@@ -66,6 +70,9 @@
private WindowMagnificationManager mWindowMagnificationMgr;
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+ private long mWindowModeEnabledTime = 0;
+ private long mFullScreenModeEnabledTime = 0;
+
/**
* A callback to inform the magnification transition result.
*/
@@ -187,7 +194,8 @@
setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
}
- void onRequestMagnificationSpec(int displayId, int serviceId) {
+ @Override
+ public void onRequestMagnificationSpec(int displayId, int serviceId) {
synchronized (mLock) {
if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) {
return;
@@ -200,6 +208,39 @@
}
}
+ // TODO : supporting multi-display (b/182227245).
+ @Override
+ public void onWindowMagnificationActivationState(boolean activated) {
+ if (activated) {
+ mWindowModeEnabledTime = SystemClock.uptimeMillis();
+ } else {
+ logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ SystemClock.uptimeMillis() - mWindowModeEnabledTime);
+ }
+ }
+
+ @Override
+ public void onFullScreenMagnificationActivationState(boolean activated) {
+ if (activated) {
+ mFullScreenModeEnabledTime = SystemClock.uptimeMillis();
+ } else {
+ logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
+ SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
+ }
+ }
+
+ /**
+ * Wrapper method of logging the magnification activated mode and its duration of the usage
+ * when the magnification is disabled.
+ *
+ * @param mode The activated magnification mode.
+ * @param duration The duration in milliseconds during the magnification is activated.
+ */
+ @VisibleForTesting
+ public void logMagnificationUsageState(int mode, long duration) {
+ AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration);
+ }
+
/**
* Updates the active user ID of {@link FullScreenMagnificationController} and {@link
* WindowMagnificationManager}.
@@ -260,7 +301,7 @@
synchronized (mLock) {
if (mFullScreenMagnificationController == null) {
mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
- mAms, mLock, this::onRequestMagnificationSpec);
+ mAms, mLock, this);
mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked());
}
}
@@ -340,7 +381,7 @@
mTransitionCallBack = transitionCallBack;
mDisplayId = displayId;
mTargetMode = targetMode;
- mCurrentMode = mTargetMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+ mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mCurrentScale = scale;
mCurrentCenter.set(currentCenter);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 40668d8..ded601e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -93,6 +93,13 @@
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
*/
void onPerformScaleAction(int displayId, float scale);
+
+ /**
+ * Called when the state of the magnification activation is changed.
+ *
+ * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
+ */
+ void onWindowMagnificationActivationState(boolean activated);
}
private final Callback mCallback;
@@ -264,6 +271,7 @@
*/
void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback) {
+ final boolean enabled;
synchronized (mLock) {
if (mConnectionWrapper == null) {
return;
@@ -272,9 +280,13 @@
if (magnifier == null) {
magnifier = createWindowMagnifier(displayId);
}
- magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
+ enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
animationCallback);
}
+
+ if (enabled) {
+ mCallback.onWindowMagnificationActivationState(true);
+ }
}
/**
@@ -296,16 +308,21 @@
*/
void disableWindowMagnification(int displayId, boolean clear,
MagnificationAnimationCallback animationCallback) {
+ final boolean disabled;
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null || mConnectionWrapper == null) {
return;
}
- magnifier.disableWindowMagnificationInternal(animationCallback);
+ disabled = magnifier.disableWindowMagnificationInternal(animationCallback);
if (clear) {
mWindowMagnifiers.delete(displayId);
}
}
+
+ if (disabled) {
+ mCallback.onWindowMagnificationActivationState(false);
+ }
}
/**
@@ -560,26 +577,35 @@
}
@GuardedBy("mLock")
- void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+ boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback) {
if (mEnabled) {
- return;
+ return false;
}
final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
centerX, centerY, animationCallback)) {
mScale = normScale;
mEnabled = true;
+
+ return true;
}
+ return false;
}
@GuardedBy("mLock")
- void disableWindowMagnificationInternal(
+ boolean disableWindowMagnificationInternal(
@Nullable MagnificationAnimationCallback animationResultCallback) {
- if (mEnabled && mWindowMagnificationManager.disableWindowMagnificationInternal(
+ if (!mEnabled) {
+ return false;
+ }
+ if (mWindowMagnificationManager.disableWindowMagnificationInternal(
mDisplayId, animationResultCallback)) {
mEnabled = false;
+
+ return true;
}
+ return false;
}
@GuardedBy("mLock")
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index f07bd2d..148df09 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1001,7 +1001,8 @@
private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
return CollectionUtils.filter(
getAllAssociations(userId),
- a -> Objects.equals(packageFilter, a.getPackageName()));
+ // Null filter == get all associations
+ a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName()));
}
private Set<Association> getAllAssociations() {
@@ -1022,8 +1023,10 @@
int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
return CollectionUtils.filter(
getAllAssociations(userId),
- a -> Objects.equals(packageFilter, a.getPackageName())
- && Objects.equals(addressFilter, a.getDeviceMacAddress()));
+ // Null filter == get all associations
+ a -> (packageFilter == null || Objects.equals(packageFilter, a.getPackageName()))
+ && (addressFilter == null
+ || Objects.equals(addressFilter, a.getDeviceMacAddress())));
}
private Set<Association> readAllAssociations(int userId) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b4fcaee..688b33e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4425,7 +4425,8 @@
break;
}
case EVENT_PROXY_HAS_CHANGED: {
- handleApplyDefaultProxy((ProxyInfo)msg.obj);
+ final Pair<Network, ProxyInfo> arg = (Pair<Network, ProxyInfo>) msg.obj;
+ handleApplyDefaultProxy(arg.second);
break;
}
case EVENT_REGISTER_NETWORK_PROVIDER: {
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index a353519..96ff900 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.util.Log;
@@ -51,4 +53,9 @@
publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
}
}
+
+ @Override
+ public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+ mContextHubService.onUserChanged();
+ }
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 35b730a..ebe5dd2 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -962,7 +962,7 @@
}
int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
- ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0);
+ ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 5000);
Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms");
return delay;
}
@@ -1791,7 +1791,7 @@
public StorageManagerService(Context context) {
sSelf = this;
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mContext = context;
mResolver = mContext.getContentResolver();
mCallbacks = new Callbacks(FgThread.get().getLooper());
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index ee61067..f566277 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -90,7 +90,12 @@
mCm = mContext.getSystemService(ConnectivityManager.class);
mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
TEST_NETWORK_PROVIDER_NAME);
- mCm.registerNetworkProvider(mNetworkProvider);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCm.registerNetworkProvider(mNetworkProvider);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ed8d696..4972aa7 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -786,7 +786,7 @@
mAppDataIsolationEnabled =
SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
- ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mAppDataIsolationAllowlistedApps = new ArrayList<>(
SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 6712c54..5bf15dc 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -43,6 +44,8 @@
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.Parcel;
@@ -55,10 +58,15 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.autofill.AutofillManagerInternal;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
import android.widget.Toast;
import com.android.internal.R;
@@ -170,6 +178,8 @@
// DeviceConfig properties
private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications";
private static final boolean DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true;
+ private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
+ private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
private final ActivityManagerInternal mAmInternal;
private final IUriGrantsManager mUgm;
@@ -180,8 +190,10 @@
private final AppOpsManager mAppOps;
private final ContentCaptureManagerInternal mContentCaptureInternal;
private final AutofillManagerInternal mAutofillInternal;
+ private final TextClassificationManager mTextClassificationManager;
private final IBinder mPermissionOwner;
private final HostClipboardMonitor mHostClipboardMonitor;
+ private final Handler mWorkerHandler;
@GuardedBy("mLock")
private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
@@ -189,6 +201,9 @@
@GuardedBy("mLock")
private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
+ @GuardedBy("mLock")
+ private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;
+
private final Object mLock = new Object();
/**
@@ -206,6 +221,8 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
+ mTextClassificationManager = (TextClassificationManager)
+ getContext().getSystemService(Context.TEXT_CLASSIFICATION_SERVICE);
final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
mPermissionOwner = permOwner;
if (IS_EMULATOR) {
@@ -232,6 +249,10 @@
updateConfig();
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
getContext().getMainExecutor(), properties -> updateConfig());
+
+ HandlerThread workerThread = new HandlerThread(TAG);
+ workerThread.start();
+ mWorkerHandler = workerThread.getThreadHandler();
}
@Override
@@ -250,6 +271,8 @@
synchronized (mLock) {
mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
+ mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
+ PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
}
}
@@ -592,6 +615,10 @@
}
}
+ if (clip != null) {
+ startClassificationLocked(clip);
+ }
+
// Update this user
final int userId = UserHandle.getUserId(uid);
setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
@@ -691,6 +718,68 @@
}
}
+ @GuardedBy("mLock")
+ private void startClassificationLocked(@NonNull ClipData clip) {
+ TextClassifier classifier;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ classifier = mTextClassificationManager.createTextClassificationSession(
+ new TextClassificationContext.Builder(
+ getContext().getPackageName(),
+ TextClassifier.WIDGET_TYPE_CLIPBOARD
+ ).build()
+ );
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (clip.getItemCount() == 0) {
+ clip.getDescription().setClassificationStatus(
+ ClipDescription.CLASSIFICATION_NOT_PERFORMED);
+ return;
+ }
+ CharSequence text = clip.getItemAt(0).getText();
+ if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength
+ || text.length() > classifier.getMaxGenerateLinksTextLength()) {
+ clip.getDescription().setClassificationStatus(
+ ClipDescription.CLASSIFICATION_NOT_PERFORMED);
+ return;
+ }
+
+ mWorkerHandler.post(() -> doClassification(text, clip, classifier));
+ }
+
+ @WorkerThread
+ private void doClassification(
+ CharSequence text, ClipData clip, TextClassifier classifier) {
+ TextLinks.Request request = new TextLinks.Request.Builder(text).build();
+ TextLinks links;
+ try {
+ links = classifier.generateLinks(request);
+ } finally {
+ classifier.destroy();
+ }
+
+ // Find the highest confidence for each entity in the text.
+ ArrayMap<String, Float> confidences = new ArrayMap<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ for (int i = 0; i < link.getEntityCount(); i++) {
+ String entity = link.getEntity(i);
+ float conf = link.getConfidenceScore(entity);
+ if (conf > confidences.getOrDefault(entity, 0f)) {
+ confidences.put(entity, conf);
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ clip.getDescription().setConfidenceScores(confidences);
+ if (!links.getLinks().isEmpty()) {
+ clip.getItemAt(0).setTextLinks(links);
+ }
+ }
+ }
+
private boolean isDeviceLocked(@UserIdInt int userId) {
final long token = Binder.clearCallingIdentity();
try {
@@ -922,6 +1011,10 @@
if (!mShowAccessNotifications) {
return;
}
+ if (Settings.Secure.getInt(getContext().getContentResolver(),
+ Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 1) == 0) {
+ return;
+ }
// Don't notify if the app accessing the clipboard is the same as the current owner.
if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
return;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 4f6b530..7a5abf8 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -16,7 +16,6 @@
package com.android.server.connectivity;
-import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
@@ -33,6 +32,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
import android.net.IDnsResolver;
import android.net.InetAddresses;
import android.net.LinkProperties;
@@ -128,7 +128,7 @@
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
- final String mode = getPrivateDnsMode(cr);
+ final String mode = ConnectivityManager.getPrivateDnsMode(cr);
final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
@@ -479,13 +479,6 @@
return result;
}
- private static String getPrivateDnsMode(ContentResolver cr) {
- String mode = getStringSetting(cr, PRIVATE_DNS_MODE);
- if (TextUtils.isEmpty(mode)) mode = getStringSetting(cr, PRIVATE_DNS_DEFAULT_MODE);
- if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
- return mode;
- }
-
private static String getStringSetting(ContentResolver cr, String which) {
return Settings.Global.getString(cr, which);
}
diff --git a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java b/services/core/java/com/android/server/connectivity/PacProxyService.java
similarity index 75%
rename from services/core/java/com/android/server/connectivity/PacProxyInstaller.java
rename to services/core/java/com/android/server/connectivity/PacProxyService.java
index aadaf4d..d23b488 100644
--- a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
+++ b/services/core/java/com/android/server/connectivity/PacProxyService.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -26,12 +28,16 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.net.IPacProxyInstalledListener;
+import android.net.IPacProxyManager;
import android.net.ProxyInfo;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -44,6 +50,7 @@
import com.android.net.IProxyCallback;
import com.android.net.IProxyPortListener;
import com.android.net.IProxyService;
+import com.android.net.module.util.PermissionUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -53,7 +60,7 @@
/**
* @hide
*/
-public class PacProxyInstaller {
+public class PacProxyService extends IPacProxyManager.Stub {
private static final String PAC_PACKAGE = "com.android.pacprocessor";
private static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
private static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
@@ -61,7 +68,7 @@
private static final String PROXY_PACKAGE = "com.android.proxyhandler";
private static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
- private static final String TAG = "PacProxyInstaller";
+ private static final String TAG = "PacProxyService";
private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH";
@@ -71,10 +78,6 @@
private static final int DELAY_LONG = 4;
private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
- // Return values for #setCurrentProxyScriptUrl
- public static final boolean DONT_SEND_BROADCAST = false;
- public static final boolean DO_SEND_BROADCAST = true;
-
private String mCurrentPac;
@GuardedBy("mProxyLock")
private volatile Uri mPacUrl = Uri.EMPTY;
@@ -93,8 +96,8 @@
private volatile boolean mHasSentBroadcast;
private volatile boolean mHasDownloaded;
- private Handler mConnectivityHandler;
- private final int mProxyMessage;
+ private final RemoteCallbackList<IPacProxyInstalledListener>
+ mCallbacks = new RemoteCallbackList<>();
/**
* Used for locking when setting mProxyService and all references to mCurrentPac.
@@ -102,6 +105,13 @@
private final Object mProxyLock = new Object();
/**
+ * Lock ensuring consistency between the values of mHasSentBroadcast, mHasDownloaded, the
+ * last URL and port, and the broadcast message being sent with the correct arguments.
+ * TODO : this should probably protect all instances of these variables
+ */
+ private final Object mBroadcastStateLock = new Object();
+
+ /**
* Runnable to download PAC script.
* The behavior relies on the assumption it always runs on mNetThread to guarantee that the
* latest data fetched from mPacUrl is stored in mProxyService.
@@ -146,10 +156,10 @@
}
}
- public PacProxyInstaller(Context context, Handler handler, int proxyMessage) {
+ public PacProxyService(@NonNull Context context) {
mContext = context;
mLastPort = -1;
- final HandlerThread netThread = new HandlerThread("android.pacproxyinstaller",
+ final HandlerThread netThread = new HandlerThread("android.pacproxyservice",
android.os.Process.THREAD_PRIORITY_DEFAULT);
netThread.start();
mNetThreadHandler = new Handler(netThread.getLooper());
@@ -158,8 +168,6 @@
context, 0, new Intent(ACTION_PAC_REFRESH), PendingIntent.FLAG_IMMUTABLE);
context.registerReceiver(new PacRefreshIntentReceiver(),
new IntentFilter(ACTION_PAC_REFRESH));
- mConnectivityHandler = handler;
- mProxyMessage = proxyMessage;
}
private AlarmManager getAlarmManager() {
@@ -169,38 +177,52 @@
return mAlarmManager;
}
+ @Override
+ public void addListener(IPacProxyInstalledListener listener) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mCallbacks.register(listener);
+ }
+
+ @Override
+ public void removeListener(IPacProxyInstalledListener listener) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ mCallbacks.unregister(listener);
+ }
+
/**
* Updates the PAC Proxy Installer with current Proxy information. This is called by
- * the ProxyTracker directly before a broadcast takes place to allow
- * the PacProxyInstaller to indicate that the broadcast should not be sent and the
- * PacProxyInstaller will trigger a new broadcast when it is ready.
+ * the ProxyTracker through PacProxyManager before a broadcast takes place to allow
+ * the PacProxyService to indicate that the broadcast should not be sent and the
+ * PacProxyService will trigger a new broadcast when it is ready.
*
* @param proxy Proxy information that is about to be broadcast.
- * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST
*/
- public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
- if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
- if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
- // Allow to send broadcast, nothing to do.
- return DO_SEND_BROADCAST;
- }
- mPacUrl = proxy.getPacFileUrl();
- mCurrentDelay = DELAY_1;
- mHasSentBroadcast = false;
- mHasDownloaded = false;
- getAlarmManager().cancel(mPacRefreshIntent);
- bind();
- return DONT_SEND_BROADCAST;
- } else {
- getAlarmManager().cancel(mPacRefreshIntent);
- synchronized (mProxyLock) {
- mPacUrl = Uri.EMPTY;
- mCurrentPac = null;
- if (mProxyService != null) {
- unbind();
+ @Override
+ public void setCurrentProxyScriptUrl(@Nullable ProxyInfo proxy) {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+ android.Manifest.permission.NETWORK_SETTINGS);
+
+ synchronized (mBroadcastStateLock) {
+ if (proxy != null && !Uri.EMPTY.equals(proxy.getPacFileUrl())) {
+ if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) return;
+ mPacUrl = proxy.getPacFileUrl();
+ mCurrentDelay = DELAY_1;
+ mHasSentBroadcast = false;
+ mHasDownloaded = false;
+ getAlarmManager().cancel(mPacRefreshIntent);
+ bind();
+ } else {
+ getAlarmManager().cancel(mPacRefreshIntent);
+ synchronized (mProxyLock) {
+ mPacUrl = Uri.EMPTY;
+ mCurrentPac = null;
+ if (mProxyService != null) {
+ unbind();
+ }
}
}
- return DO_SEND_BROADCAST;
}
}
@@ -275,6 +297,7 @@
getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
}
+ @GuardedBy("mProxyLock")
private void setCurrentProxyScript(String script) {
if (mProxyService == null) {
Log.e(TAG, "setCurrentProxyScript: no proxy service");
@@ -347,6 +370,9 @@
public void setProxyPort(int port) {
if (mLastPort != -1) {
// Always need to send if port changed
+ // TODO: Here lacks synchronization because this write cannot
+ // guarantee that it's visible from sendProxyIfNeeded() when
+ // it's called by a Runnable which is post by mNetThread.
mHasSentBroadcast = false;
}
mLastPort = port;
@@ -365,8 +391,9 @@
}
}
};
- mContext.bindService(intent, mProxyConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
+ mContext.bindService(intent,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
+ new HandlerExecutor(mNetThreadHandler), mProxyConnection);
}
private void unbind() {
@@ -383,16 +410,28 @@
}
private void sendPacBroadcast(ProxyInfo proxy) {
- mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
+ final int length = mCallbacks.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final IPacProxyInstalledListener listener = mCallbacks.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onPacProxyInstalled(null /* network */, proxy);
+ } catch (RemoteException ignored) { }
+ }
+ }
+ mCallbacks.finishBroadcast();
}
- private synchronized void sendProxyIfNeeded() {
- if (!mHasDownloaded || (mLastPort == -1)) {
- return;
- }
- if (!mHasSentBroadcast) {
- sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
- mHasSentBroadcast = true;
+ // This method must be called on mNetThreadHandler.
+ private void sendProxyIfNeeded() {
+ synchronized (mBroadcastStateLock) {
+ if (!mHasDownloaded || (mLastPort == -1)) {
+ return;
+ }
+ if (!mHasSentBroadcast) {
+ sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
+ mHasSentBroadcast = true;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index d83ff83..8b9c836 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -27,15 +27,19 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.net.Network;
+import android.net.PacProxyManager;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.net.module.util.ProxyUtils;
@@ -67,7 +71,7 @@
// is not set. Individual networks have their own settings that override this. This member
// is set through setDefaultProxy, which is called when the default network changes proxies
// in its LinkProperties, or when ConnectivityService switches to a new default network, or
- // when PacProxyInstaller resolves the proxy.
+ // when PacProxyService resolves the proxy.
@Nullable
@GuardedBy("mProxyLock")
private volatile ProxyInfo mDefaultProxy = null;
@@ -77,16 +81,31 @@
private final Handler mConnectivityServiceHandler;
- // The object responsible for Proxy Auto Configuration (PAC).
- @NonNull
- private final PacProxyInstaller mPacProxyInstaller;
+ private final PacProxyManager mPacProxyManager;
+
+ private class PacProxyInstalledListener implements PacProxyManager.PacProxyInstalledListener {
+ private final int mEvent;
+
+ PacProxyInstalledListener(int event) {
+ mEvent = event;
+ }
+
+ public void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy) {
+ mConnectivityServiceHandler
+ .sendMessage(mConnectivityServiceHandler
+ .obtainMessage(mEvent, new Pair<>(network, proxy)));
+ }
+ }
public ProxyTracker(@NonNull final Context context,
@NonNull final Handler connectivityServiceInternalHandler, final int pacChangedEvent) {
mContext = context;
mConnectivityServiceHandler = connectivityServiceInternalHandler;
- mPacProxyInstaller = new PacProxyInstaller(
- context, connectivityServiceInternalHandler, pacChangedEvent);
+ mPacProxyManager = context.getSystemService(PacProxyManager.class);
+
+ PacProxyInstalledListener listener = new PacProxyInstalledListener(pacChangedEvent);
+ mPacProxyManager.addPacProxyInstalledListener(
+ new HandlerExecutor(mConnectivityServiceHandler), listener);
}
// Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
@@ -182,7 +201,7 @@
if (!TextUtils.isEmpty(pacFileUrl)) {
mConnectivityServiceHandler.post(
- () -> mPacProxyInstaller.setCurrentProxyScriptUrl(proxyProperties));
+ () -> mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));
}
}
}
@@ -226,9 +245,9 @@
final ProxyInfo defaultProxy = getDefaultProxy();
final ProxyInfo proxyInfo = null != defaultProxy ?
defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
+ mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);
- if (mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo)
- == PacProxyInstaller.DONT_SEND_BROADCAST) {
+ if (!shouldSendBroadcast(proxyInfo)) {
return;
}
if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
@@ -244,6 +263,10 @@
}
}
+ private boolean shouldSendBroadcast(ProxyInfo proxy) {
+ return Uri.EMPTY.equals(proxy.getPacFileUrl()) || proxy.getPort() > 0;
+ }
+
/**
* Sets the global proxy in memory. Also writes the values to the global settings of the device.
*
@@ -308,10 +331,10 @@
return;
}
- // This call could be coming from the PacProxyInstaller, containing the port of the
+ // This call could be coming from the PacProxyService, containing the port of the
// local proxy. If this new proxy matches the global proxy then copy this proxy to the
// global (to get the correct local port), and send a broadcast.
- // TODO: Switch PacProxyInstaller to have its own message to send back rather than
+ // TODO: Switch PacProxyService to have its own message to send back rather than
// reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
if ((mGlobalProxy != null) && (proxyInfo != null)
&& (!Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a2b9b96..91b96dc 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -46,7 +46,6 @@
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
import java.io.PrintWriter;
@@ -220,12 +219,14 @@
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) {
+ HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context,
+ HighBrightnessModeController hbmController) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
- ambientBrightnessThresholds, screenBrightnessThresholds, display, context
+ ambientBrightnessThresholds, screenBrightnessThresholds, display, context,
+ hbmController
);
}
@@ -236,7 +237,8 @@
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) {
+ HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context,
+ HighBrightnessModeController hbmController) {
mInjector = injector;
mContext = context;
mCallbacks = callbacks;
@@ -273,20 +275,7 @@
mPendingForegroundAppPackageName = null;
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
-
- final DisplayDeviceConfig ddConfig =
- display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
- HighBrightnessModeData hbmData =
- ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-
- final Runnable hbmChangeCallback = () -> {
- updateAutoBrightness(true /*sendUpdate*/, false /*userInitiatedChange*/);
- // TODO: b/175937645 - Callback to DisplayManagerService to indicate a change to the HBM
- // allowance has been made so that the brightness limits can be calculated
- // appropriately.
- };
- mHbmController = new HighBrightnessModeController(mHandler, brightnessMin, brightnessMax,
- hbmData, hbmChangeCallback);
+ mHbmController = hbmController;
}
/**
@@ -327,6 +316,7 @@
public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
float brightness, boolean userChangedBrightness, float adjustment,
boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
+ mHbmController.setAutoBrightnessEnabled(enable);
// While dozing, the application processor may be suspended which will prevent us from
// receiving new information from the light sensor. On some devices, we may be able to
// switch to a wake-up light sensor instead but for now we will simply disable the sensor
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7b107b85..e44ecac 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -350,12 +350,15 @@
private final ColorDisplayServiceInternal mCdsi;
private final float[] mNitsRange;
+ private final HighBrightnessModeController mHbmController;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
// The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
private float mInitialAutoBrightness;
+
// The controller for the automatic brightness level.
private AutomaticBrightnessController mAutomaticBrightnessController;
@@ -433,6 +436,7 @@
mBlanker = blanker;
mContext = context;
+
PowerManager pm = context.getSystemService(PowerManager.class);
final Resources resources = context.getResources();
@@ -476,6 +480,8 @@
mSkipScreenOnBrightnessRamp = resources.getBoolean(
com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
+ mHbmController = createHbmController();
+
if (mUseSoftwareAutoBrightnessConfig) {
final float dozeScaleFactor = resources.getFraction(
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
@@ -535,7 +541,7 @@
PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
- screenBrightnessThresholds, logicalDisplay, context);
+ screenBrightnessThresholds, logicalDisplay, context, mHbmController);
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
@@ -1081,6 +1087,8 @@
mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
}
+ boolean updateScreenBrightnessSetting = false;
+
// Apply auto-brightness.
boolean slowChange = false;
if (Float.isNaN(brightnessState)) {
@@ -1097,11 +1105,7 @@
if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
slowChange = true; // slowly adapt to auto-brightness
}
- // Tell the rest of the system about the new brightness. Note that we do this
- // before applying the low power or dim transformations so that the slider
- // accurately represents the full possible range, even if they range changes what
- // it means in absolute terms.
- putScreenBrightnessSetting(brightnessState);
+ updateScreenBrightnessSetting = true;
mAppliedAutoBrightness = true;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
} else {
@@ -1119,6 +1123,7 @@
mAppliedAutoBrightness = false;
brightnessAdjustmentFlags = 0;
}
+
// Use default brightness when dozing unless overridden.
if ((Float.isNaN(brightnessState))
&& Display.isDozeState(state)) {
@@ -1129,9 +1134,24 @@
// Apply manual brightness.
if (Float.isNaN(brightnessState)) {
brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
+ if (brightnessState != mCurrentScreenBrightnessSetting) {
+ // The manually chosen screen brightness is outside of the currently allowed
+ // range (i.e., high-brightness-mode), make sure we tell the rest of the system
+ // by updating the setting.
+ updateScreenBrightnessSetting = true;
+ }
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
}
+ if (updateScreenBrightnessSetting) {
+ // Tell the rest of the system about the new brightness in case we had to change it
+ // for things like auto-brightness or high-brightness-mode. Note that we do this
+ // before applying the low power or dim transformations so that the slider
+ // accurately represents the full possible range, even if they range changes what
+ // it means in absolute terms.
+ putScreenBrightnessSetting(brightnessState);
+ }
+
// Apply dimming by at least some minimum amount when user activity
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1209,9 +1229,10 @@
// animate to. To avoid this, we check the value first.
// If the brightnessState is off (-1.0f) we still want to animate to the minimum
// brightness (0.0f) to accommodate for LED displays, which can appear bright to the
- // user even when the display is all black.
- float animateValue = brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT
- ? PowerManager.BRIGHTNESS_MIN : brightnessState;
+ // user even when the display is all black. We also clamp here in case some
+ // transformations to the brightness have pushed it outside of the currently
+ // allowed range.
+ float animateValue = clampScreenBrightness(brightnessState);
final float currentBrightness = mPowerState.getScreenBrightness();
if (isValidBrightnessValue(animateValue)
&& !BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)) {
@@ -1354,6 +1375,15 @@
msg.sendToTarget();
}
+ private HighBrightnessModeController createHbmController() {
+ final DisplayDeviceConfig ddConfig =
+ mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
+ final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+ ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
+ return new HighBrightnessModeController(mHandler, PowerManager.BRIGHTNESS_MIN,
+ PowerManager.BRIGHTNESS_MAX, hbmData, () -> sendUpdatePowerStateLocked());
+ }
+
private void blockScreenOn() {
if (mPendingScreenOnUnblocker == null) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -1469,10 +1499,10 @@
private float clampScreenBrightness(float value) {
if (Float.isNaN(value)) {
- return PowerManager.BRIGHTNESS_MIN;
+ value = PowerManager.BRIGHTNESS_MIN;
}
- return MathUtils.constrain(
- value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+ return MathUtils.constrain(value,
+ mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
}
// Checks whether the brightness is within the valid brightness range, not including the off or
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 12b810f..2e5561d 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -37,7 +37,7 @@
class HighBrightnessModeController {
private static final String TAG = "HighBrightnessModeController";
- private static final boolean DEBUG_HBM = false;
+ private static final boolean DEBUG = false;
private final float mBrightnessMin;
private final float mBrightnessMax;
@@ -48,6 +48,7 @@
private boolean mIsInAllowedAmbientRange = false;
private boolean mIsTimeAvailable = false;
+ private boolean mIsAutoBrightnessEnabled = false;
private float mAutoBrightness;
/**
@@ -84,6 +85,17 @@
};
}
+ void setAutoBrightnessEnabled(boolean isEnabled) {
+ if (isEnabled == mIsAutoBrightnessEnabled) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "setAutoBrightness( " + isEnabled + " )");
+ }
+ mIsAutoBrightnessEnabled = isEnabled;
+ mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
+ }
+
float getCurrentBrightnessMin() {
return mBrightnessMin;
}
@@ -102,7 +114,7 @@
}
void onAmbientLuxChange(float ambientLux) {
- if (!deviceSupportsHbm()) {
+ if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
return;
}
@@ -132,7 +144,7 @@
mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
mRunningStartTimeMillis = -1;
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "New HBM event: " + mEvents.getFirst());
}
}
@@ -142,7 +154,7 @@
}
private boolean isCurrentlyAllowed() {
- return mIsTimeAvailable && mIsInAllowedAmbientRange;
+ return mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange;
}
private boolean deviceSupportsHbm() {
@@ -167,7 +179,7 @@
timeAlreadyUsed = currentTime - mRunningStartTimeMillis;
}
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed);
}
@@ -187,7 +199,7 @@
timeAlreadyUsed += event.endTimeMillis - startTimeMillis;
}
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed);
}
@@ -220,7 +232,7 @@
nextTimeout = timeWhenMinIsGainedBack;
}
- if (DEBUG_HBM) {
+ if (DEBUG) {
Slog.d(TAG, "HBM recalculated. IsAllowedWithoutRestrictions: "
+ isAllowedWithoutRestrictions
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index 947ee24..f6828d1 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -100,18 +100,22 @@
// Wake-up on <Set Stream Path> was not mandatory before CEC 2.0.
// The message is re-sent at the end of the action for devices that don't support 2.0.
sendSetStreamPath();
- int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
- HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(
- getTargetAddress());
- if (targetDevice != null) {
- targetPowerStatus = targetDevice.getDevicePowerStatus();
- }
- if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ if (!mIsCec20) {
queryDevicePowerStatus();
- } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
+ } else {
+ int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
+ HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork()
+ .getCecDeviceInfo(getTargetAddress());
+ if (targetDevice != null) {
+ targetPowerStatus = targetDevice.getDevicePowerStatus();
+ }
+ if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ queryDevicePowerStatus();
+ } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ return true;
+ }
}
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 9d2db94..979e7a4 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -89,7 +89,7 @@
mSource = source();
sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
- boolean targetOnBefore = getTargetDevicePowerStatus(mSource, mTargetAddress,
+ boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
HdmiControlManager.POWER_STATUS_UNKNOWN) == HdmiControlManager.POWER_STATUS_ON;
broadcastActiveSource();
// If the device is not an audio system itself, request the connected audio system to
@@ -98,18 +98,23 @@
sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(),
Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true));
}
- int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress,
- HdmiControlManager.POWER_STATUS_UNKNOWN);
- if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+
+ if (!mIsCec20) {
queryDevicePowerStatus();
- } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
- if (!targetOnBefore) {
- // Suppress 2nd <Active Source> message if the target device was already on when
- // the 1st one was sent.
- broadcastActiveSource();
+ } else {
+ int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress,
+ HdmiControlManager.POWER_STATUS_UNKNOWN);
+ if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+ queryDevicePowerStatus();
+ } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
+ if (!is20TargetOnBefore) {
+ // Suppress 2nd <Active Source> message if the target device was already on when
+ // the 1st one was sent.
+ broadcastActiveSource();
+ }
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+ return true;
}
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- return true;
}
mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index dde45c4..c44089b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -17,13 +17,16 @@
package com.android.server.location.contexthub;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
+import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.contexthub.V1_0.AsyncEventType;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -59,6 +62,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
import com.android.server.location.ContextHubServiceProto;
import java.io.FileDescriptor;
@@ -127,6 +131,8 @@
// Lock object for sendWifiSettingUpdate()
private final Object mSendWifiSettingUpdateLock = new Object();
+ private final SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
+
/**
* Class extending the callback to register with a Context Hub.
*/
@@ -186,6 +192,7 @@
if (mContextHubWrapper == null) {
mTransactionManager = null;
mClientManager = null;
+ mSensorPrivacyManagerInternal = null;
mDefaultClientMap = Collections.emptyMap();
mContextHubIdToInfoMap = Collections.emptyMap();
mSupportedContextHubPerms = Collections.emptyList();
@@ -208,6 +215,8 @@
mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
mTransactionManager = new ContextHubTransactionManager(
mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
+ mSensorPrivacyManagerInternal =
+ LocalServices.getService(SensorPrivacyManagerInternal.class);
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
@@ -284,18 +293,16 @@
}
if (mContextHubWrapper.supportsMicrophoneDisableSettingNotifications()) {
- sendMicrophoneDisableSettingUpdate();
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
- SensorPrivacyManager.OnSensorPrivacyChangedListener listener =
- new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
- @Override
- public void onSensorPrivacyChanged(boolean enabled) {
- sendMicrophoneDisableSettingUpdate();
- }
- };
- SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
- manager.addSensorPrivacyListener(
- SensorPrivacyManager.Sensors.MICROPHONE, listener);
+ mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
+ SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
+ if (userId == getCurrentUserId()) {
+ Log.d(TAG, "User: " + userId + " enabled: " + enabled);
+ sendMicrophoneDisableSettingUpdate(enabled);
+ }
+ });
+
}
}
@@ -1074,19 +1081,48 @@
}
/**
- * Obtains the latest microphone disable setting value and notifies the
- * Context Hub.
+ * Notifies a microphone disable settings change to the Context Hub.
*/
- private void sendMicrophoneDisableSettingUpdate() {
- SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
- boolean disabled = manager.isSensorPrivacyEnabled(
- SensorPrivacyManager.Sensors.MICROPHONE);
- Log.d(TAG, "Mic Disabled Setting: " + disabled);
- mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled);
+ private void sendMicrophoneDisableSettingUpdate(boolean enabled) {
+ Log.d(TAG, "Mic Disabled Setting: " + enabled);
+ mContextHubWrapper.onMicrophoneDisableSettingChanged(enabled);
+ }
+
+ /**
+ * Obtains the latest microphone disabled setting for the current user
+ * and notifies the Context Hub.
+ */
+ private void sendMicrophoneDisableSettingUpdateForCurrentUser() {
+ boolean isEnabled = mSensorPrivacyManagerInternal.isSensorPrivacyEnabled(
+ getCurrentUserId(), SensorPrivacyManager.Sensors.MICROPHONE);
+ sendMicrophoneDisableSettingUpdate(isEnabled);
}
private String getCallingPackageName() {
return mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
}
+
+ private int getCurrentUserId() {
+ final long id = Binder.clearCallingIdentity();
+ try {
+ UserInfo currentUser = ActivityManager.getService().getCurrentUser();
+ return currentUser.id;
+ } catch (RemoteException e) {
+ // Activity manager not running, nothing we can do - assume user 0.
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ return UserHandle.USER_SYSTEM;
+ }
+
+ /**
+ * Send a microphone disable settings update whenever the foreground user changes.
+ * We always send a settings update regardless of the previous state for the same user
+ * since the CHRE framework is expected to handle repeated identical setting update.
+ */
+ public void onUserChanged() {
+ Log.d(TAG, "User changed to id: " + getCurrentUserId());
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
+ }
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e3ccb75..27bf8a13 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -737,10 +737,12 @@
}
}
if (locationExtraPackageNames != null) {
- // Also grant location permission to location extra packages.
+ // Also grant location and activity recognition permission to location extra packages.
for (String packageName : locationExtraPackageNames) {
grantPermissionsToSystemPackage(pm, packageName, userId,
ALWAYS_LOCATION_PERMISSIONS);
+ grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId,
+ ACTIVITY_RECOGNITION_PERMISSIONS);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ffea6a7..1e49071 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2235,9 +2235,9 @@
/** {@inheritDoc} */
@Override
- public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
- CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+ public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+ int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
if (!SHOW_SPLASH_SCREENS) {
return null;
}
@@ -2264,10 +2264,12 @@
if (theme != context.getThemeResId() || labelRes != 0) {
try {
- context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
+ context = context.createPackageContextAsUser(packageName, CONTEXT_RESTRICTED,
+ UserHandle.of(userId));
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
- // Ignore
+ Slog.w(TAG, "Failed creating package context with package name "
+ + packageName + " for user " + userId, e);
}
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 0735977b..d512edf 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -714,9 +714,9 @@
* @return The starting surface.
*
*/
- public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
- CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig, int displayId);
+ StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+ int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId);
/**
* Set or clear a window which can behave as the keyguard.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index a95628f..44f14b4 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -258,8 +258,12 @@
}
}
+ /**
+ * @deprecated Notify occlude status change via remote animation.
+ */
+ @Deprecated
public void setOccluded(boolean isOccluded, boolean animate) {
- if (mKeyguardService != null) {
+ if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
mKeyguardService.setOccluded(isOccluded, animate);
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a1d2f8a..3ae67ae 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3298,7 +3298,11 @@
values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
sessionToken.toString());
- mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ try{
+ mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ }catch(IllegalArgumentException ex){
+ Slog.w(TAG, "error in insert db for MSG_LOG_WATCH_START", ex);
+ }
args.recycle();
break;
}
@@ -3313,7 +3317,11 @@
values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
sessionToken.toString());
- mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ try{
+ mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+ }catch(IllegalArgumentException ex){
+ Slog.w(TAG, "error in insert db for MSG_LOG_WATCH_END", ex);
+ }
args.recycle();
break;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 3893267..b90408f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,6 +16,7 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.IVibratorManager;
import android.os.CombinedVibrationEffect;
@@ -35,9 +36,9 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.FrameworkStatsLog;
-import com.google.android.collect.Lists;
-
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
@@ -47,11 +48,16 @@
private static final boolean DEBUG = false;
/**
- * Extra timeout added to the end of each synced vibration step as a timeout for the callback
- * wait, to ensure it finishes even when callbacks from individual vibrators are lost.
+ * Extra timeout added to the end of each vibration step to ensure it finishes even when
+ * vibrator callbacks are lost.
*/
private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
+ /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
+ private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
+
+ private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+
/** Callbacks for playing a {@link Vibration}. */
interface VibrationCallbacks {
@@ -83,13 +89,10 @@
private final IBatteryStats mBatteryStatsService;
private final Vibration mVibration;
private final VibrationCallbacks mCallbacks;
- private final SparseArray<VibratorController> mVibrators;
+ private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
+ private final StepQueue mStepQueue = new StepQueue();
- @GuardedBy("mLock")
- @Nullable
- private VibrateStep mCurrentVibrateStep;
- @GuardedBy("mLock")
- private boolean mForceStop;
+ private volatile boolean mForceStop;
VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
@@ -102,7 +105,6 @@
mBatteryStatsService = batteryStatsService;
CombinedVibrationEffect effect = vib.getEffect();
- mVibrators = new SparseArray<>();
for (int i = 0; i < availableVibrators.size(); i++) {
if (effect.hasVibrator(availableVibrators.keyAt(i))) {
mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
@@ -110,6 +112,15 @@
}
}
+ Vibration getVibration() {
+ return mVibration;
+ }
+
+ @VisibleForTesting
+ SparseArray<VibratorController> getVibrators() {
+ return mVibrators;
+ }
+
@Override
public void binderDied() {
if (DEBUG) {
@@ -136,8 +147,11 @@
/** Cancel current vibration and shuts down the thread gracefully. */
public void cancel() {
+ mForceStop = true;
synchronized (mLock) {
- mForceStop = true;
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration cancelled");
+ }
mLock.notify();
}
}
@@ -148,11 +162,10 @@
if (DEBUG) {
Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
}
- if (mCurrentVibrateStep != null) {
- for (int i = 0; i < mVibrators.size(); i++) {
- mCurrentVibrateStep.vibratorComplete(mVibrators.keyAt(i));
- }
+ for (int i = 0; i < mVibrators.size(); i++) {
+ mStepQueue.consumeOnVibratorComplete(mVibrators.keyAt(i));
}
+ mLock.notify();
}
}
@@ -162,120 +175,73 @@
if (DEBUG) {
Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
}
- if (mCurrentVibrateStep != null) {
- mCurrentVibrateStep.vibratorComplete(vibratorId);
- }
+ mStepQueue.consumeOnVibratorComplete(vibratorId);
+ mLock.notify();
}
}
- Vibration getVibration() {
- return mVibration;
- }
-
- @VisibleForTesting
- SparseArray<VibratorController> getVibrators() {
- return mVibrators;
- }
-
private Vibration.Status playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try {
- List<Step> steps = generateSteps(mVibration.getEffect());
- if (steps.isEmpty()) {
- // No vibrator matching any incoming vibration effect.
- return Vibration.Status.IGNORED;
- }
- Vibration.Status status = Vibration.Status.FINISHED;
- final int stepCount = steps.size();
- for (int i = 0; i < stepCount; i++) {
- Step step = steps.get(i);
- synchronized (mLock) {
- if (step instanceof VibrateStep) {
- mCurrentVibrateStep = (VibrateStep) step;
+ CombinedVibrationEffect.Sequential effect = toSequential(mVibration.getEffect());
+ int stepsPlayed = 0;
+
+ synchronized (mLock) {
+ mStepQueue.offer(new StartVibrateStep(effect));
+ Step topOfQueue;
+
+ while ((topOfQueue = mStepQueue.peek()) != null) {
+ long waitTime = topOfQueue.calculateWaitTime();
+ if (waitTime <= 0) {
+ stepsPlayed += mStepQueue.consume();
} else {
- mCurrentVibrateStep = null;
+ try {
+ mLock.wait(waitTime);
+ } catch (InterruptedException e) { }
+ }
+ if (mForceStop) {
+ mStepQueue.cancel();
+ return Vibration.Status.CANCELLED;
}
}
- status = step.play();
- if (status != Vibration.Status.FINISHED) {
- // This step was ignored by the vibrators, probably effects were unsupported.
- break;
- }
- if (mForceStop) {
- break;
- }
}
- if (mForceStop) {
- return Vibration.Status.CANCELLED;
- }
- return status;
+
+ // Some effects might be ignored because the specified vibrator don't exist or doesn't
+ // support the effect. We only report ignored here if nothing was played besides the
+ // StartVibrateStep (which means every attempt to turn on the vibrator was ignored).
+ return stepsPlayed > effect.getEffects().size()
+ ? Vibration.Status.FINISHED : Vibration.Status.IGNORED_UNSUPPORTED;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
- private List<Step> generateSteps(CombinedVibrationEffect effect) {
- if (effect instanceof CombinedVibrationEffect.Sequential) {
- CombinedVibrationEffect.Sequential sequential =
- (CombinedVibrationEffect.Sequential) effect;
- List<Step> steps = new ArrayList<>();
- final int sequentialEffectCount = sequential.getEffects().size();
- for (int i = 0; i < sequentialEffectCount; i++) {
- int delay = sequential.getDelays().get(i);
- if (delay > 0) {
- steps.add(new DelayStep(delay));
- }
- steps.addAll(generateSteps(sequential.getEffects().get(i)));
+ private void noteVibratorOn(long duration) {
+ try {
+ if (duration <= 0) {
+ return;
}
- final int stepCount = steps.size();
- for (int i = 0; i < stepCount; i++) {
- if (steps.get(i) instanceof VibrateStep) {
- return steps;
- }
+ if (duration == Long.MAX_VALUE) {
+ // Repeating duration has started. Report a fixed duration here, noteVibratorOff
+ // should be called when this is cancelled.
+ duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
}
- // No valid vibrate step was generated, ignore effect completely.
- return Lists.newArrayList();
+ mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
+ duration);
+ } catch (RemoteException e) {
}
- VibrateStep vibrateStep = null;
- if (effect instanceof CombinedVibrationEffect.Mono) {
- vibrateStep = createVibrateStep(mapToAvailableVibrators(
- ((CombinedVibrationEffect.Mono) effect).getEffect()));
- } else if (effect instanceof CombinedVibrationEffect.Stereo) {
- vibrateStep = createVibrateStep(filterByAvailableVibrators(
- ((CombinedVibrationEffect.Stereo) effect).getEffects()));
- }
- return vibrateStep == null ? Lists.newArrayList() : Lists.newArrayList(vibrateStep);
}
- @Nullable
- private VibrateStep createVibrateStep(SparseArray<VibrationEffect> effects) {
- if (effects.size() == 0) {
- return null;
+ private void noteVibratorOff() {
+ try {
+ mBatteryStatsService.noteVibratorOff(mVibration.uid);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+ /* duration= */ 0);
+ } catch (RemoteException e) {
}
- if (effects.size() == 1) {
- // Create simplified step that handles a single vibrator.
- return new SingleVibrateStep(mVibrators.get(effects.keyAt(0)), effects.valueAt(0));
- }
- return new SyncedVibrateStep(effects);
- }
-
- private SparseArray<VibrationEffect> mapToAvailableVibrators(VibrationEffect effect) {
- SparseArray<VibrationEffect> mappedEffects = new SparseArray<>(mVibrators.size());
- for (int i = 0; i < mVibrators.size(); i++) {
- mappedEffects.put(mVibrators.keyAt(i), effect);
- }
- return mappedEffects;
- }
-
- private SparseArray<VibrationEffect> filterByAvailableVibrators(
- SparseArray<VibrationEffect> effects) {
- SparseArray<VibrationEffect> filteredEffects = new SparseArray<>();
- for (int i = 0; i < effects.size(); i++) {
- if (mVibrators.contains(effects.keyAt(i))) {
- filteredEffects.put(effects.keyAt(i), effects.valueAt(i));
- }
- }
- return filteredEffects;
}
/**
@@ -306,341 +272,240 @@
return timing;
}
- /**
- * Sleeps until given {@code wakeUpTime}.
- *
- * <p>This stops immediately when {@link #cancel()} is called.
- *
- * @return true if waited until wake-up time, false if it was cancelled.
- */
- private boolean waitUntil(long wakeUpTime) {
- synchronized (mLock) {
- long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- while (durationRemaining > 0) {
- try {
- mLock.wait(durationRemaining);
- } catch (InterruptedException e) {
- }
- if (mForceStop) {
- return false;
- }
- durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- }
+ private static CombinedVibrationEffect.Sequential toSequential(CombinedVibrationEffect effect) {
+ if (effect instanceof CombinedVibrationEffect.Sequential) {
+ return (CombinedVibrationEffect.Sequential) effect;
}
- return true;
+ return (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+ .addNext(effect)
+ .combine();
}
- /**
- * Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}.
- *
- * <p>This stops immediately when {@link #cancel()} is called.
- *
- * @return true if finished on vibration complete, false if it was cancelled or timed out.
- */
- private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) {
- synchronized (mLock) {
- long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- while (!step.isVibrationComplete() && durationRemaining > 0) {
- try {
- mLock.wait(durationRemaining);
- } catch (InterruptedException e) {
- }
- if (mForceStop) {
- return false;
- }
- durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
- }
- }
- return step.isVibrationComplete();
- }
-
- private void noteVibratorOn(long duration) {
- try {
- mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
- duration);
- } catch (RemoteException e) {
- }
- }
-
- private void noteVibratorOff() {
- try {
- mBatteryStatsService.noteVibratorOff(mVibration.uid);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
- /* duration= */ 0);
- } catch (RemoteException e) {
- }
- }
-
- /** Represent a single synchronized step while playing a {@link CombinedVibrationEffect}. */
- private interface Step {
- Vibration.Status play();
- }
-
- /** Represent a synchronized vibration step. */
- private interface VibrateStep extends Step {
- /** Callback to notify a vibrator has finished playing a effect. */
- void vibratorComplete(int vibratorId);
-
- /** Returns true if the vibration played by this step is complete. */
- boolean isVibrationComplete();
- }
-
- /** Represent a vibration on a single vibrator. */
- private final class SingleVibrateStep implements VibrateStep {
- private final VibratorController mVibrator;
- private final VibrationEffect mEffect;
+ /** Queue for {@link Step Steps}, sorted by their start time. */
+ private final class StepQueue {
+ @GuardedBy("mLock")
+ private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
@GuardedBy("mLock")
- private boolean mVibrationComplete;
-
- SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) {
- mVibrator = vibrator;
- mEffect = effect;
+ public void offer(@NonNull Step step) {
+ mNextSteps.offer(step);
}
@GuardedBy("mLock")
- @Override
- public boolean isVibrationComplete() {
- return mVibrationComplete;
+ @Nullable
+ public Step peek() {
+ return mNextSteps.peek();
+ }
+
+ /**
+ * Play and remove the step at the top of this queue, and also adds the next steps
+ * generated to be played next.
+ *
+ * @return the number of steps played
+ */
+ @GuardedBy("mLock")
+ public int consume() {
+ Step nextStep = mNextSteps.poll();
+ if (nextStep != null) {
+ mNextSteps.addAll(nextStep.play());
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Play and remove the step in this queue that should be anticipated by the vibrator
+ * completion callback.
+ *
+ * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
+ * first step found is played by this method, in no particular order.
+ */
+ @GuardedBy("mLock")
+ public void consumeOnVibratorComplete(int vibratorId) {
+ Iterator<Step> it = mNextSteps.iterator();
+ List<Step> nextSteps = EMPTY_STEP_LIST;
+ while (it.hasNext()) {
+ Step step = it.next();
+ if (step.shouldPlayWhenVibratorComplete(vibratorId)) {
+ it.remove();
+ nextSteps = step.play();
+ break;
+ }
+ }
+ mNextSteps.addAll(nextSteps);
+ }
+
+ /**
+ * Cancel the current queue, clearing all remaining steps.
+ *
+ * <p>This will remove and trigger {@link Step#cancel()} in all steps, in order.
+ */
+ @GuardedBy("mLock")
+ public void cancel() {
+ Step step;
+ while ((step = mNextSteps.poll()) != null) {
+ step.cancel();
+ }
+ }
+ }
+
+ /**
+ * Represent a single step for playing a vibration.
+ *
+ * <p>Every step has a start time, which can be used to apply delays between steps while
+ * executing them in sequence.
+ */
+ private abstract class Step implements Comparable<Step> {
+ public final long startTime;
+
+ Step(long startTime) {
+ this.startTime = startTime;
+ }
+
+ /** Play this step, returning a (possibly empty) list of next steps. */
+ @NonNull
+ public abstract List<Step> play();
+
+ /** Cancel this pending step. */
+ public void cancel() {
+ }
+
+ /**
+ * Return true to play this step right after a vibrator has notified vibration completed,
+ * used to anticipate steps waiting on vibrator callbacks with a timeout.
+ */
+ public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ return false;
+ }
+
+ /** Returns the time in millis to wait before playing this step. */
+ public long calculateWaitTime() {
+ if (startTime == Long.MAX_VALUE) {
+ // This step don't have a predefined start time, it's just marked to be executed
+ // after all other steps have finished.
+ return 0;
+ }
+ return Math.max(0, startTime - SystemClock.uptimeMillis());
}
@Override
- public void vibratorComplete(int vibratorId) {
- if (mVibrator.getVibratorInfo().getId() != vibratorId) {
- return;
- }
- if (mEffect instanceof VibrationEffect.OneShot
- || mEffect instanceof VibrationEffect.Waveform) {
- // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks.
- return;
- }
- mVibrator.off();
- synchronized (mLock) {
- mVibrationComplete = true;
- mLock.notify();
- }
+ public int compareTo(Step o) {
+ return Long.compare(startTime, o.startTime);
+ }
+ }
+
+ /**
+ * Starts a sync vibration.
+ *
+ * <p>If this step has successfully started playing a vibration on any vibrator, it will always
+ * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
+ * all their individual steps.
+ *
+ * <o>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
+ * sequential effect isn't finished yet.
+ */
+ private final class StartVibrateStep extends Step {
+ public final CombinedVibrationEffect.Sequential sequentialEffect;
+ public final int currentIndex;
+
+ StartVibrateStep(CombinedVibrationEffect.Sequential effect) {
+ this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
+ }
+
+ StartVibrateStep(long startTime, CombinedVibrationEffect.Sequential effect, int index) {
+ super(startTime);
+ sequentialEffect = effect;
+ currentIndex = index;
}
@Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SingleVibrateStep");
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
+ List<Step> nextSteps = new ArrayList<>();
long duration = -1;
try {
if (DEBUG) {
- Slog.d(TAG, "SingleVibrateStep starting...");
+ Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
}
- long startTime = SystemClock.uptimeMillis();
- duration = vibratePredefined(mEffect);
-
- if (duration > 0) {
- noteVibratorOn(duration);
- // Vibration is playing with no need to control amplitudes, just wait for native
- // callback or timeout.
- if (waitForVibrationComplete(this,
- startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) {
- return Vibration.Status.FINISHED;
- }
- // Timed out or vibration cancelled. Stop vibrator anyway.
- mVibrator.off();
- return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
+ CombinedVibrationEffect effect = sequentialEffect.getEffects().get(currentIndex);
+ DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
+ if (effectMapping == null) {
+ // Unable to map effects to vibrators, ignore this step.
+ return nextSteps;
}
- startTime = SystemClock.uptimeMillis();
- AmplitudeStep amplitudeStep = vibrateWithAmplitude(mEffect, startTime);
- if (amplitudeStep == null) {
- // Vibration could not be played with or without amplitude steps.
- return Vibration.Status.IGNORED_UNSUPPORTED;
- }
-
- duration = mEffect instanceof VibrationEffect.Prebaked
- ? ((VibrationEffect.Prebaked) mEffect).getFallbackEffect().getDuration()
- : mEffect.getDuration();
- if (duration < Long.MAX_VALUE) {
- // Only report vibration stats if we know how long we will be vibrating.
- noteVibratorOn(duration);
- }
- while (amplitudeStep != null) {
- if (!waitUntil(amplitudeStep.startTime)) {
- mVibrator.off();
- return Vibration.Status.CANCELLED;
- }
- amplitudeStep.play();
- amplitudeStep = amplitudeStep.nextStep();
- }
-
- return Vibration.Status.FINISHED;
+ duration = startVibrating(effectMapping, nextSteps);
+ noteVibratorOn(duration);
} finally {
- if (duration > 0 && duration < Long.MAX_VALUE) {
- noteVibratorOff();
- }
- if (DEBUG) {
- Slog.d(TAG, "SingleVibrateStep done.");
+ // If this step triggered any vibrator then add a finish step to wait for all
+ // active vibrators to finish their individual steps before going to the next.
+ Step nextStep = duration > 0 ? new FinishVibrateStep(this) : nextStep();
+ if (nextStep != null) {
+ nextSteps.add(nextStep);
}
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
+ return nextSteps;
}
/**
- * Try to vibrate given effect using prebaked or composed predefined effects.
- *
- * @return the duration, in millis, expected for the vibration, or -1 if effect cannot be
- * played with predefined effects.
+ * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
+ * time this method is called, or null if sequence is complete.
*/
- private long vibratePredefined(VibrationEffect effect) {
- if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- long duration = mVibrator.on(prebaked, mVibration.id);
- if (duration > 0) {
- return duration;
- }
- if (prebaked.getFallbackEffect() != null) {
- return vibratePredefined(prebaked.getFallbackEffect());
- }
- } else if (effect instanceof VibrationEffect.Composed) {
- VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
- return mVibrator.on(composed, mVibration.id);
+ @Nullable
+ private Step nextStep() {
+ int nextIndex = currentIndex + 1;
+ if (nextIndex >= sequentialEffect.getEffects().size()) {
+ return null;
}
- // OneShot and Waveform effects require amplitude change after calling vibrator.on.
- return -1;
+ long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
+ long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
+ return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex);
}
- /**
- * Try to vibrate given effect using {@link AmplitudeStep} to control vibration amplitude.
- *
- * @return the {@link AmplitudeStep} to start this vibration, or {@code null} if vibration
- * do not require amplitude control.
- */
- private AmplitudeStep vibrateWithAmplitude(VibrationEffect effect, long startTime) {
- int vibratorId = mVibrator.getVibratorInfo().getId();
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- return new AmplitudeStep(vibratorId, oneShot, startTime, startTime);
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- return new AmplitudeStep(vibratorId, waveform, startTime, startTime);
- } else if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- if (prebaked.getFallbackEffect() != null) {
- return vibrateWithAmplitude(prebaked.getFallbackEffect(), startTime);
- }
+ /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
+ @Nullable
+ private DeviceEffectMap createEffectToVibratorMapping(
+ CombinedVibrationEffect effect) {
+ if (effect instanceof CombinedVibrationEffect.Mono) {
+ return new DeviceEffectMap((CombinedVibrationEffect.Mono) effect);
+ }
+ if (effect instanceof CombinedVibrationEffect.Stereo) {
+ return new DeviceEffectMap((CombinedVibrationEffect.Stereo) effect);
}
return null;
}
- }
-
- /** Represent a synchronized vibration step on multiple vibrators. */
- private final class SyncedVibrateStep implements VibrateStep {
- private final SparseArray<VibrationEffect> mEffects;
- private final long mRequiredCapabilities;
- private final int[] mVibratorIds;
-
- @GuardedBy("mLock")
- private int mActiveVibratorCounter;
-
- SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
- mEffects = effects;
- mActiveVibratorCounter = mEffects.size();
- mRequiredCapabilities = calculateRequiredSyncCapabilities(effects);
- mVibratorIds = new int[effects.size()];
- for (int i = 0; i < effects.size(); i++) {
- mVibratorIds[i] = effects.keyAt(i);
- }
- }
-
- @GuardedBy("mLock")
- @Override
- public boolean isVibrationComplete() {
- return mActiveVibratorCounter <= 0;
- }
-
- @Override
- public void vibratorComplete(int vibratorId) {
- VibrationEffect effect = mEffects.get(vibratorId);
- if (effect == null) {
- return;
- }
- if (effect instanceof VibrationEffect.OneShot
- || effect instanceof VibrationEffect.Waveform) {
- // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks.
- return;
- }
- mVibrators.get(vibratorId).off();
- synchronized (mLock) {
- --mActiveVibratorCounter;
- mLock.notify();
- }
- }
-
- @Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SyncedVibrateStep");
- long duration = -1;
- try {
- if (DEBUG) {
- Slog.d(TAG, "SyncedVibrateStep starting...");
- }
- final PriorityQueue<AmplitudeStep> nextSteps = new PriorityQueue<>(mEffects.size());
- long startTime = SystemClock.uptimeMillis();
- duration = startVibratingSynced(startTime, nextSteps);
-
- if (duration <= 0) {
- // Vibrate step failed, vibrator could not be turned on for this step.
- return Vibration.Status.IGNORED;
- }
-
- noteVibratorOn(duration);
- while (!nextSteps.isEmpty()) {
- AmplitudeStep step = nextSteps.poll();
- if (!waitUntil(step.startTime)) {
- stopAllVibrators();
- return Vibration.Status.CANCELLED;
- }
- step.play();
- AmplitudeStep nextStep = step.nextStep();
- if (nextStep == null) {
- // This vibrator has finished playing the effect for this step.
- synchronized (mLock) {
- mActiveVibratorCounter--;
- }
- } else {
- nextSteps.add(nextStep);
- }
- }
-
- synchronized (mLock) {
- // All OneShot and Waveform effects have finished. Just wait for the other
- // effects to end via native callbacks before finishing this synced step.
- final long wakeUpTime = startTime + duration + CALLBACKS_EXTRA_TIMEOUT;
- if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) {
- return Vibration.Status.FINISHED;
- }
-
- // Timed out or vibration cancelled. Stop all vibrators anyway.
- stopAllVibrators();
- return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
- }
- } finally {
- if (duration > 0) {
- noteVibratorOff();
- }
- if (DEBUG) {
- Slog.d(TAG, "SyncedVibrateStep done.");
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
/**
* Starts playing effects on designated vibrators, in sync.
*
- * @return A positive duration, in millis, to wait for the completion of this effect.
- * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
- * returns the duration of a single run to be used as timeout for callbacks.
+ * @param effectMapping The {@link CombinedVibrationEffect} mapped to this device vibrators
+ * @param nextSteps An output list to accumulate the future {@link Step Steps} created
+ * by this method, typically one for each vibrator that has
+ * successfully started vibrating on this step.
+ * @return The duration, in millis, of the {@link CombinedVibrationEffect}. Repeating
+ * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
+ * have ignored all effects.
*/
- private long startVibratingSynced(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
+ private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
+ int vibratorCount = effectMapping.size();
+ if (vibratorCount == 0) {
+ // No effect was mapped to any available vibrator.
+ return 0;
+ }
+
+ VibratorOnStep[] steps = new VibratorOnStep[vibratorCount];
+ long vibrationStartTime = SystemClock.uptimeMillis();
+ for (int i = 0; i < vibratorCount; i++) {
+ steps[i] = new VibratorOnStep(vibrationStartTime,
+ mVibrators.get(effectMapping.vibratorIdAt(i)), effectMapping.effectAt(i));
+ }
+
+ if (steps.length == 1) {
+ // No need to prepare and trigger sync effects on a single vibrator.
+ return startVibrating(steps[0], nextSteps);
+ }
+
// This synchronization of vibrators should be executed one at a time, even if we are
// vibrating different sets of vibrators in parallel. The manager can only prepareSynced
// one set of vibrators at a time.
@@ -648,17 +513,24 @@
boolean hasPrepared = false;
boolean hasTriggered = false;
try {
- hasPrepared = mCallbacks.prepareSyncedVibration(mRequiredCapabilities,
- mVibratorIds);
- long timeout = startVibrating(startTime, nextSteps);
+ hasPrepared = mCallbacks.prepareSyncedVibration(
+ effectMapping.getRequiredSyncCapabilities(),
+ effectMapping.getVibratorIds());
- // Check if preparation was successful, otherwise devices area already vibrating
- if (hasPrepared) {
+ long duration = 0;
+ for (VibratorOnStep step : steps) {
+ duration = Math.max(duration, startVibrating(step, nextSteps));
+ }
+
+ // Check if sync was prepared and if any step was accepted by a vibrator,
+ // otherwise there is nothing to trigger here.
+ if (hasPrepared && duration > 0) {
hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id);
}
- return timeout;
+ return duration;
} finally {
if (hasPrepared && !hasTriggered) {
+ // Trigger has failed or all steps were ignored by the vibrators.
mCallbacks.cancelSyncedVibration();
return 0;
}
@@ -666,77 +538,365 @@
}
}
- /**
- * Starts playing effects on designated vibrators.
- *
- * <p>This includes the {@link VibrationEffect.OneShot} and {@link VibrationEffect.Waveform}
- * effects, that should start in sync with all other effects in this step. The waveforms are
- * controlled by {@link AmplitudeStep} added to the {@code nextSteps} queue.
- *
- * @return A positive duration, in millis, to wait for the completion of this effect.
- * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
- * returns the duration of a single run to be used as timeout for callbacks.
- */
- private long startVibrating(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
- long maxDuration = 0;
- for (int i = 0; i < mEffects.size(); i++) {
- VibratorController controller = mVibrators.get(mEffects.keyAt(i));
- VibrationEffect effect = mEffects.valueAt(i);
- maxDuration = Math.max(maxDuration,
- startVibrating(controller, effect, startTime, nextSteps));
+ private long startVibrating(VibratorOnStep step, List<Step> nextSteps) {
+ nextSteps.addAll(step.play());
+ return step.getDuration();
+ }
+ }
+
+ /**
+ * Finish a sync vibration started by a {@link StartVibrateStep}.
+ *
+ * <p>This only plays after all active vibrators steps have finished, and adds a {@link
+ * StartVibrateStep} to the queue if the sequential effect isn't finished yet.
+ */
+ private final class FinishVibrateStep extends Step {
+ public final StartVibrateStep startedStep;
+
+ FinishVibrateStep(StartVibrateStep startedStep) {
+ super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue.
+ this.startedStep = startedStep;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
+ try {
+ if (DEBUG) {
+ Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex);
+ }
+ noteVibratorOff();
+ Step nextStep = startedStep.nextStep();
+ return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
- return maxDuration;
+ }
+
+ @Override
+ public void cancel() {
+ noteVibratorOff();
+ }
+ }
+
+ /**
+ * Represent a step turn the vibrator on.
+ *
+ * <p>No other calls to the vibrator is made from this step, so this can be played in between
+ * calls to 'prepare' and 'trigger' for synchronized vibrations.
+ */
+ private final class VibratorOnStep extends Step {
+ public final VibratorController controller;
+ public final VibrationEffect effect;
+ private long mDuration;
+
+ VibratorOnStep(long startTime, VibratorController controller, VibrationEffect effect) {
+ super(startTime);
+ this.controller = controller;
+ this.effect = effect;
}
/**
- * Play a single effect on a single vibrator.
- *
- * @return A positive duration, in millis, to wait for the completion of this effect.
- * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
- * returns the duration of a single run to be used as timeout for callbacks.
+ * Return the duration, in millis, of this effect. Repeating waveforms return {@link
+ * Long#MAX_VALUE}. Zero or negative values indicate the vibrator has ignored this effect.
*/
- private long startVibrating(VibratorController controller, VibrationEffect effect,
- long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
- int vibratorId = controller.getVibratorInfo().getId();
- long duration;
+ public long getDuration() {
+ return mDuration;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOnStep");
+ try {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId());
+ }
+ List<Step> nextSteps = new ArrayList<>();
+ mDuration = startVibrating(effect, nextSteps);
+ return nextSteps;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+ final long duration;
+ final long now = SystemClock.uptimeMillis();
if (effect instanceof VibrationEffect.OneShot) {
VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
duration = oneShot.getDuration();
+ // Do NOT set amplitude here. This might be called between prepareSynced and
+ // triggerSynced, so the vibrator is not actually turned on here.
+ // The next steps will handle the amplitude after the vibrator has turned on.
controller.on(duration, mVibration.id);
- nextSteps.add(
- new AmplitudeStep(vibratorId, oneShot, startTime, startTime + duration));
+ nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot,
+ now + duration + CALLBACKS_EXTRA_TIMEOUT));
} else if (effect instanceof VibrationEffect.Waveform) {
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- duration = getVibratorOnDuration(waveform, 0);
- if (duration > 0) {
- // Waveform starts by turning vibrator on. Do it in this sync vibrate step.
- controller.on(duration, mVibration.id);
+ // Return the full duration of this waveform effect.
+ duration = waveform.getDuration();
+ long onDuration = getVibratorOnDuration(waveform, 0);
+ if (onDuration > 0) {
+ // Do NOT set amplitude here. This might be called between prepareSynced and
+ // triggerSynced, so the vibrator is not actually turned on here.
+ // The next steps will handle the amplitudes after the vibrator has turned on.
+ controller.on(onDuration, mVibration.id);
}
- nextSteps.add(
- new AmplitudeStep(vibratorId, waveform, startTime, startTime + duration));
+ long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now;
+ nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime));
} else if (effect instanceof VibrationEffect.Prebaked) {
VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
duration = controller.on(prebaked, mVibration.id);
- if (duration <= 0 && prebaked.getFallbackEffect() != null) {
- return startVibrating(controller, prebaked.getFallbackEffect(), startTime,
- nextSteps);
+ if (duration > 0) {
+ nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+ controller));
+ } else if (prebaked.getFallbackEffect() != null) {
+ return startVibrating(prebaked.getFallbackEffect(), nextSteps);
}
} else if (effect instanceof VibrationEffect.Composed) {
VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
duration = controller.on(composed, mVibration.id);
+ if (duration > 0) {
+ nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+ controller));
+ }
} else {
duration = 0;
}
return duration;
}
+ }
- private void stopAllVibrators() {
- for (int vibratorId : mVibratorIds) {
- VibratorController controller = mVibrators.get(vibratorId);
- if (controller != null) {
- controller.off();
+ /**
+ * Represents a step to turn the vibrator off.
+ *
+ * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
+ * and can anticipated by vibrator complete callbacks.
+ */
+ private final class VibratorOffStep extends Step {
+ public final VibratorController controller;
+
+ VibratorOffStep(long startTime, VibratorController controller) {
+ super(startTime);
+ this.controller = controller;
+ }
+
+ @Override
+ public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ return controller.getVibratorInfo().getId() == vibratorId;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOffStep");
+ try {
+ stopVibrating();
+ return EMPTY_STEP_LIST;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ stopVibrating();
+ }
+
+ private void stopVibrating() {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
+ }
+ controller.off();
+ }
+ }
+
+ /** Represents a step to change the amplitude of the vibrator. */
+ private final class VibratorAmplitudeStep extends Step {
+ public final VibratorController controller;
+ public final VibrationEffect.Waveform waveform;
+ public final int currentIndex;
+ public final long expectedVibratorStopTime;
+
+ private long mNextVibratorStopTime;
+
+ VibratorAmplitudeStep(long startTime, VibratorController controller,
+ VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) {
+ this(startTime, controller,
+ (VibrationEffect.Waveform) VibrationEffect.createWaveform(
+ new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()},
+ /* repeat= */ -1),
+ expectedVibratorStopTime);
+ }
+
+ VibratorAmplitudeStep(long startTime, VibratorController controller,
+ VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
+ this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime);
+ }
+
+ VibratorAmplitudeStep(long startTime, VibratorController controller,
+ VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) {
+ super(startTime);
+ this.controller = controller;
+ this.waveform = waveform;
+ this.currentIndex = index;
+ this.expectedVibratorStopTime = expectedVibratorStopTime;
+ mNextVibratorStopTime = expectedVibratorStopTime;
+ }
+
+ @Override
+ public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+ if (controller.getVibratorInfo().getId() == vibratorId) {
+ mNextVibratorStopTime = SystemClock.uptimeMillis();
+ }
+ return false;
+ }
+
+ @Override
+ public List<Step> play() {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorAmplitudeStep");
+ try {
+ if (DEBUG) {
+ long latency = SystemClock.uptimeMillis() - startTime;
+ Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
+ }
+ if (waveform.getTimings()[currentIndex] == 0) {
+ // Skip waveform entries with zero timing.
+ return nextSteps();
+ }
+ int amplitude = waveform.getAmplitudes()[currentIndex];
+ if (amplitude == 0) {
+ stopVibrating();
+ return nextSteps();
+ }
+ if (startTime >= mNextVibratorStopTime) {
+ // Vibrator has stopped. Turn vibrator back on for the duration of another
+ // cycle before setting the amplitude.
+ long onDuration = getVibratorOnDuration(waveform, currentIndex);
+ if (onDuration > 0) {
+ startVibrating(onDuration);
+ mNextVibratorStopTime =
+ SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
+ }
+ }
+ changeAmplitude(amplitude);
+ return nextSteps();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ stopVibrating();
+ }
+
+ private void stopVibrating() {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
+ }
+ controller.off();
+ mNextVibratorStopTime = SystemClock.uptimeMillis();
+ }
+
+ private void startVibrating(long duration) {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ + duration + "ms");
+ }
+ controller.on(duration, mVibration.id);
+ }
+
+ private void changeAmplitude(int amplitude) {
+ if (DEBUG) {
+ Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+ + " to " + amplitude);
+ }
+ controller.setAmplitude(amplitude);
+ }
+
+ @NonNull
+ private List<Step> nextSteps() {
+ long nextStartTime = startTime + waveform.getTimings()[currentIndex];
+ int nextIndex = currentIndex + 1;
+ if (nextIndex >= waveform.getTimings().length) {
+ nextIndex = waveform.getRepeatIndex();
+ }
+ if (nextIndex < 0) {
+ return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
+ }
+ return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform,
+ nextIndex, mNextVibratorStopTime));
+ }
+ }
+
+ /**
+ * Map a {@link CombinedVibrationEffect} to the vibrators available on the device.
+ *
+ * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
+ * play all of the effects in sync.
+ */
+ private final class DeviceEffectMap {
+ private final SparseArray<VibrationEffect> mVibratorEffects;
+ private final int[] mVibratorIds;
+ private final long mRequiredSyncCapabilities;
+
+ DeviceEffectMap(CombinedVibrationEffect.Mono mono) {
+ mVibratorEffects = new SparseArray<>(mVibrators.size());
+ mVibratorIds = new int[mVibrators.size()];
+ for (int i = 0; i < mVibrators.size(); i++) {
+ int vibratorId = mVibrators.keyAt(i);
+ mVibratorEffects.put(vibratorId, mono.getEffect());
+ mVibratorIds[i] = vibratorId;
+ }
+ mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+ }
+
+ DeviceEffectMap(CombinedVibrationEffect.Stereo stereo) {
+ SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
+ mVibratorEffects = new SparseArray<>();
+ for (int i = 0; i < stereoEffects.size(); i++) {
+ int vibratorId = stereoEffects.keyAt(i);
+ if (mVibrators.contains(vibratorId)) {
+ mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
}
}
+ mVibratorIds = new int[mVibratorEffects.size()];
+ for (int i = 0; i < mVibratorEffects.size(); i++) {
+ mVibratorIds[i] = mVibratorEffects.keyAt(i);
+ }
+ mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+ }
+
+ /**
+ * Return the number of vibrators mapped to play the {@link CombinedVibrationEffect} on this
+ * device.
+ */
+ public int size() {
+ return mVibratorIds.length;
+ }
+
+ /**
+ * Return all capabilities required to play the {@link CombinedVibrationEffect} in
+ * between calls to {@link IVibratorManager#prepareSynced} and
+ * {@link IVibratorManager#triggerSynced}.
+ */
+ public long getRequiredSyncCapabilities() {
+ return mRequiredSyncCapabilities;
+ }
+
+ /** Return all vibrator ids mapped to play the {@link CombinedVibrationEffect}. */
+ public int[] getVibratorIds() {
+ return mVibratorIds;
+ }
+
+ /** Return the id of the vibrator at given index. */
+ public int vibratorIdAt(int index) {
+ return mVibratorEffects.keyAt(index);
+ }
+
+ /** Return the {@link VibrationEffect} at given index. */
+ public VibrationEffect effectAt(int index) {
+ return mVibratorEffects.valueAt(index);
}
/**
@@ -782,145 +942,4 @@
&& (prepareCapabilities & ~capability) != 0;
}
}
-
- /** Represent a step to set amplitude on a single vibrator. */
- private final class AmplitudeStep implements Step, Comparable<AmplitudeStep> {
- public final int vibratorId;
- public final VibrationEffect.Waveform waveform;
- public final int currentIndex;
- public final long startTime;
- public final long vibratorStopTime;
-
- AmplitudeStep(int vibratorId, VibrationEffect.OneShot oneShot,
- long startTime, long vibratorStopTime) {
- this(vibratorId, (VibrationEffect.Waveform) VibrationEffect.createWaveform(
- new long[]{oneShot.getDuration()},
- new int[]{oneShot.getAmplitude()}, /* repeat= */ -1),
- startTime,
- vibratorStopTime);
- }
-
- AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform,
- long startTime, long vibratorStopTime) {
- this(vibratorId, waveform, /* index= */ 0, startTime, vibratorStopTime);
- }
-
- AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform,
- int index, long startTime, long vibratorStopTime) {
- this.vibratorId = vibratorId;
- this.waveform = waveform;
- this.currentIndex = index;
- this.startTime = startTime;
- this.vibratorStopTime = vibratorStopTime;
- }
-
- @Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
- try {
- if (DEBUG) {
- Slog.d(TAG, "AmplitudeStep starting on vibrator " + vibratorId + "...");
- }
- VibratorController controller = mVibrators.get(vibratorId);
- if (currentIndex < 0) {
- controller.off();
- if (DEBUG) {
- Slog.d(TAG, "Vibrator turned off and finishing");
- }
- return Vibration.Status.FINISHED;
- }
- if (waveform.getTimings()[currentIndex] == 0) {
- // Skip waveform entries with zero timing.
- return Vibration.Status.FINISHED;
- }
- int amplitude = waveform.getAmplitudes()[currentIndex];
- if (amplitude == 0) {
- controller.off();
- if (DEBUG) {
- Slog.d(TAG, "Vibrator turned off");
- }
- return Vibration.Status.FINISHED;
- }
- if (startTime >= vibratorStopTime) {
- // Vibrator has stopped. Turn vibrator back on for the duration of another
- // cycle before setting the amplitude.
- long onDuration = getVibratorOnDuration(waveform, currentIndex);
- if (onDuration > 0) {
- controller.on(onDuration, mVibration.id);
- if (DEBUG) {
- Slog.d(TAG, "Vibrator turned on for " + onDuration + "ms");
- }
- }
- }
- controller.setAmplitude(amplitude);
- if (DEBUG) {
- Slog.d(TAG, "Amplitude changed to " + amplitude);
- }
- return Vibration.Status.FINISHED;
- } finally {
- if (DEBUG) {
- Slog.d(TAG, "AmplitudeStep done.");
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
-
- @Override
- public int compareTo(AmplitudeStep o) {
- return Long.compare(startTime, o.startTime);
- }
-
- /** Return next {@link AmplitudeStep} from this waveform, of {@code null} if finished. */
- @Nullable
- public AmplitudeStep nextStep() {
- if (currentIndex < 0) {
- // Waveform has ended, no more steps to run.
- return null;
- }
- long nextStartTime = startTime + waveform.getTimings()[currentIndex];
- int nextIndex = currentIndex + 1;
- if (nextIndex >= waveform.getTimings().length) {
- nextIndex = waveform.getRepeatIndex();
- }
- return new AmplitudeStep(vibratorId, waveform, nextIndex, nextStartTime,
- nextVibratorStopTime());
- }
-
- /** Return next time the vibrator will stop after this step is played. */
- private long nextVibratorStopTime() {
- if (currentIndex < 0 || waveform.getTimings()[currentIndex] == 0
- || startTime < vibratorStopTime) {
- return vibratorStopTime;
- }
- return startTime + getVibratorOnDuration(waveform, currentIndex);
- }
- }
-
- /** Represent a delay step with fixed duration, that starts counting when it starts playing. */
- private final class DelayStep implements Step {
- private final int mDelay;
-
- DelayStep(int delay) {
- mDelay = delay;
- }
-
- @Override
- public Vibration.Status play() {
- Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "DelayStep");
- try {
- if (DEBUG) {
- Slog.d(TAG, "DelayStep of " + mDelay + "ms starting...");
- }
- if (waitUntil(SystemClock.uptimeMillis() + mDelay)) {
- return Vibration.Status.FINISHED;
- }
- return Vibration.Status.CANCELLED;
- } finally {
- if (DEBUG) {
- Slog.d(TAG, "DelayStep done.");
- }
- Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 426e631..9f60878 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1862,6 +1862,7 @@
// Update application display metrics.
final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+ final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
@@ -1878,6 +1879,7 @@
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}
mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
+ mDisplayInfo.roundedCorners = roundedCorners;
mDisplayInfo.getAppMetrics(mDisplayMetrics);
if (mDisplayScalingDisabled) {
mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
diff --git a/services/core/java/com/android/server/wm/DisplayHashController.java b/services/core/java/com/android/server/wm/DisplayHashController.java
index 7e16c22..1262dee 100644
--- a/services/core/java/com/android/server/wm/DisplayHashController.java
+++ b/services/core/java/com/android/server/wm/DisplayHashController.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.service.displayhash.DisplayHasherService.EXTRA_VERIFIED_DISPLAY_HASH;
-import static android.service.displayhash.DisplayHasherService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
+import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -32,7 +34,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -45,16 +46,21 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.displayhash.DisplayHashParams;
import android.service.displayhash.DisplayHasherService;
import android.service.displayhash.IDisplayHasherService;
+import android.util.Size;
import android.util.Slog;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -78,12 +84,15 @@
private final Context mContext;
/**
- * Lock used for the cached {@link #mHashAlgorithms} array
+ * Lock used for the cached {@link #mDisplayHashAlgorithms} map
*/
- private final Object mHashAlgorithmsLock = new Object();
+ private final Object mDisplayHashAlgorithmsLock = new Object();
- @GuardedBy("mHashingAlgorithmsLock")
- private String[] mHashAlgorithms;
+ /**
+ * The cached map of display hash algorithms to the {@link DisplayHashParams}
+ */
+ @GuardedBy("mDisplayHashAlgorithmsLock")
+ private Map<String, DisplayHashParams> mDisplayHashAlgorithms;
private final Handler mHandler;
@@ -104,34 +113,8 @@
}
String[] getSupportedHashAlgorithms() {
- // We have a separate lock for the hashing algorithm array since it doesn't need to make
- // the request through the service connection. Instead, we have a lock to ensure we can
- // properly cache the hashing algorithms array so we don't need to call into the
- // ExtServices process for each request.
- synchronized (mHashAlgorithmsLock) {
- // Already have cached values
- if (mHashAlgorithms != null) {
- return mHashAlgorithms;
- }
-
- final ServiceInfo serviceInfo = getServiceInfo();
- if (serviceInfo == null) return null;
-
- final PackageManager pm = mContext.getPackageManager();
- final Resources res;
- try {
- res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Error getting application resources for " + serviceInfo, e);
- return null;
- }
-
- final int resourceId = serviceInfo.metaData.getInt(
- SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
- mHashAlgorithms = res.getStringArray(resourceId);
-
- return mHashAlgorithms;
- }
+ Map<String, DisplayHashParams> displayHashAlgorithms = getDisplayHashAlgorithms();
+ return displayHashAlgorithms.keySet().toArray(new String[0]);
}
@Nullable
@@ -148,13 +131,76 @@
return results.getParcelable(EXTRA_VERIFIED_DISPLAY_HASH);
}
- void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
+ private void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
String hashAlgorithm, RemoteCallback callback) {
connectAndRun(
service -> service.generateDisplayHash(mSalt, buffer, bounds, hashAlgorithm,
callback));
}
+ void generateDisplayHash(SurfaceControl.LayerCaptureArgs.Builder args,
+ Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) {
+ final Map<String, DisplayHashParams> displayHashAlgorithmsMap = getDisplayHashAlgorithms();
+ DisplayHashParams displayHashParams = displayHashAlgorithmsMap.get(hashAlgorithm);
+ if (displayHashParams == null) {
+ Slog.w(TAG, "Failed to generateDisplayHash. Invalid hashAlgorithm");
+ sendDisplayHashError(callback, DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM);
+ return;
+ }
+
+ Size size = displayHashParams.getBufferSize();
+ if (size != null && (size.getWidth() > 0 || size.getHeight() > 0)) {
+ args.setFrameScale((float) size.getWidth() / boundsInWindow.width(),
+ (float) size.getHeight() / boundsInWindow.height());
+ }
+
+ args.setGrayscale(displayHashParams.isGrayscaleBuffer());
+
+ SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+ SurfaceControl.captureLayers(args.build());
+ if (screenshotHardwareBuffer == null
+ || screenshotHardwareBuffer.getHardwareBuffer() == null) {
+ Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
+ sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
+ return;
+ }
+
+ generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow,
+ hashAlgorithm, callback);
+ }
+
+ private Map<String, DisplayHashParams> getDisplayHashAlgorithms() {
+ // We have a separate lock for the hashing params to ensure we can properly cache the
+ // hashing params so we don't need to call into the ExtServices process for each request.
+ synchronized (mDisplayHashAlgorithmsLock) {
+ if (mDisplayHashAlgorithms != null) {
+ return mDisplayHashAlgorithms;
+ }
+
+ final SyncCommand syncCommand = new SyncCommand();
+ Bundle results = syncCommand.run((service, remoteCallback) -> {
+ try {
+ service.getDisplayHashAlgorithms(remoteCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke getDisplayHashAlgorithms command", e);
+ }
+ });
+
+ mDisplayHashAlgorithms = new HashMap<>(results.size());
+ for (String key : results.keySet()) {
+ mDisplayHashAlgorithms.put(key, results.getParcelable(key));
+ }
+
+ return mDisplayHashAlgorithms;
+ }
+ }
+
+ void sendDisplayHashError(RemoteCallback callback, int errorCode) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
+ callback.sendResult(bundle);
+ }
+
/**
* Calculate the bounds to generate the hash for. This takes into account window transform,
* magnification, and display bounds.
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 999c585..62e4a85 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -16,12 +16,15 @@
package com.android.server.wm;
+import static java.lang.Integer.toHexString;
+
import android.app.UriGrantsManager;
import android.content.ClipData;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.view.IDragAndDropPermissions;
import com.android.server.LocalServices;
@@ -32,6 +35,9 @@
class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
implements IBinder.DeathRecipient {
+ private static final String TAG = "DragAndDrop";
+ private static final boolean DEBUG = false;
+
private final WindowManagerGlobalLock mGlobalLock;
private final int mSourceUid;
private final String mTargetPackage;
@@ -43,7 +49,7 @@
private IBinder mActivityToken = null;
private IBinder mPermissionOwnerToken = null;
- private IBinder mTransientToken = null;
+ private IBinder mAppToken = null;
DragAndDropPermissionsHandler(WindowManagerGlobalLock lock, ClipData clipData, int sourceUid,
String targetPackage, int mode, int sourceUserId, int targetUserId) {
@@ -62,6 +68,10 @@
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": taking permissions bound to activity: "
+ + toHexString(activityToken.hashCode()));
+ }
mActivityToken = activityToken;
// Will throw if Activity is not found.
@@ -84,14 +94,18 @@
}
@Override
- public void takeTransient(IBinder transientToken) throws RemoteException {
+ public void takeTransient(IBinder appToken) throws RemoteException {
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": taking permissions bound to app process: "
+ + toHexString(appToken.hashCode()));
+ }
mPermissionOwnerToken = LocalServices.getService(UriGrantsManagerInternal.class)
.newUriPermissionOwner("drop");
- mTransientToken = transientToken;
- mTransientToken.linkToDeath(this, 0);
+ mAppToken = appToken;
+ mAppToken.linkToDeath(this, 0);
doTake(mPermissionOwnerToken);
}
@@ -112,11 +126,17 @@
} finally {
mActivityToken = null;
}
+ if (DEBUG) {
+ Log.d(TAG, this + ": releasing activity-bound permissions");
+ }
} else {
permissionOwner = mPermissionOwnerToken;
mPermissionOwnerToken = null;
- mTransientToken.unlinkToDeath(this, 0);
- mTransientToken = null;
+ mAppToken.unlinkToDeath(this, 0);
+ mAppToken = null;
+ if (DEBUG) {
+ Log.d(TAG, this + ": releasing process-bound permissions");
+ }
}
UriGrantsManagerInternal ugm = LocalServices.getService(UriGrantsManagerInternal.class);
@@ -139,6 +159,9 @@
@Override
public void binderDied() {
+ if (DEBUG) {
+ Log.d(TAG, this + ": app process died: " + toHexString(mAppToken.hashCode()));
+ }
try {
release();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 45c4233..35e5491 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -30,7 +30,6 @@
import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
-import static com.android.server.wm.InsetsSourceProviderProto.FINISH_SEAMLESS_ROTATE_FRAME_NUMBER;
import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
@@ -59,6 +58,7 @@
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
+import java.util.function.Consumer;
/**
* Controller for a specific inset source on the server. It's called provider as it provides the
@@ -84,6 +84,16 @@
private final Rect mImeOverrideFrame = new Rect();
private boolean mIsLeashReadyForDispatching;
+ private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
+ if (mControl != null) {
+ final SurfaceControl leash = mControl.getLeash();
+ if (leash != null) {
+ final Point position = mControl.getSurfacePosition();
+ t.setPosition(leash, position.x, position.y);
+ }
+ }
+ };
+
/** The visibility override from the current controlling window. */
private boolean mClientVisible;
@@ -149,7 +159,6 @@
// TODO: Ideally, we should wait for the animation to finish so previous window can
// animate-out as new one animates-in.
mWin.cancelAnimation();
- mWin.mPendingPositionChanged = null;
mWin.mProvidedInsetsSources.remove(mSource.getType());
}
ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
@@ -248,31 +257,16 @@
if (mControl != null) {
final Point position = getWindowFrameSurfacePosition();
if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
- if (!mWin.getWindowFrames().didFrameSizeChange()) {
- updateLeashPosition(-1 /* frameNumber */);
- } else if (mWin.mInRelayout) {
- updateLeashPosition(mWin.getFrameNumber());
+ if (mWin.getWindowFrames().didFrameSizeChange()) {
+ mWin.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
- mWin.mPendingPositionChanged = this;
+ mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
}
mStateController.notifyControlChanged(mControlTarget);
}
}
}
- void updateLeashPosition(long frameNumber) {
- if (mControl == null) {
- return;
- }
- final SurfaceControl leash = mControl.getLeash();
- if (leash != null) {
- final Transaction t = mDisplayContent.getPendingTransaction();
- final Point position = mControl.getSurfacePosition();
- t.setPosition(leash, position.x, position.y);
- deferTransactionUntil(t, leash, frameNumber);
- }
- }
-
private Point getWindowFrameSurfacePosition() {
final Rect frame = mWin.getFrame();
final Point position = new Point();
@@ -280,14 +274,6 @@
return position;
}
- private void deferTransactionUntil(Transaction t, SurfaceControl leash, long frameNumber) {
- if (frameNumber >= 0) {
- final SurfaceControl barrier = mWin.getClientViewRootSurface();
- t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
- t.deferTransactionUntil(leash, barrier, frameNumber);
- }
- }
-
/**
* @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
*/
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1212302..2a768e7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2818,7 +2818,7 @@
* @param launchParams The resolved launch params to use.
* @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
* @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
- * @return The roott task to use for the launch or INVALID_TASK_ID.
+ * @return The root task to use for the launch or INVALID_TASK_ID.
*/
Task getLaunchRootTask(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable Task candidateTask, boolean onTop,
@@ -2887,7 +2887,7 @@
// Falling back to default task container
taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
rootTask = taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
- activityType, onTop);
+ launchParams, activityType, onTop);
if (rootTask != null) {
return rootTask;
}
@@ -2942,7 +2942,8 @@
}
}
- return container.getOrCreateRootTask(r, options, candidateTask, activityType, onTop);
+ return container.getOrCreateRootTask(
+ r, options, candidateTask, launchParams, activityType, onTop);
}
/** @return true if activity record is null or can be launched on provided display. */
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 6c46135..0708569 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -52,8 +52,8 @@
int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
if (!DEBUG_ENABLE_SHELL_DRAWER) {
- return mService.mPolicy.addSplashScreen(activity.token, packageName, theme,
- compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+ return mService.mPolicy.addSplashScreen(activity.token, activity.mUserId, packageName,
+ theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
overrideConfig, displayId);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 91aa48e..498fc5c 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -57,6 +57,7 @@
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.wm.LaunchParamsController.LaunchParams;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -1072,11 +1073,17 @@
* @see #getOrCreateRootTask(int, int, boolean)
*/
Task getOrCreateRootTask(@Nullable ActivityRecord r,
- @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
- boolean onTop) {
- // First preference is the windowing mode in the activity options if set.
- int windowingMode = (options != null)
- ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+ @Nullable ActivityOptions options, @Nullable Task candidateTask,
+ @Nullable LaunchParams launchParams, int activityType, boolean onTop) {
+ int windowingMode = WINDOWING_MODE_UNDEFINED;
+ if (launchParams != null) {
+ // If launchParams isn't null, windowing mode is already resolved.
+ windowingMode = launchParams.mWindowingMode;
+ } else if (options != null) {
+ // If launchParams is null and options isn't let's use the windowing mode in the
+ // options.
+ windowingMode = options.getLaunchWindowingMode();
+ }
// Validate that our desired windowingMode will work under the current conditions.
// UNDEFINED windowing mode is a valid result and means that the new root task will inherit
// it's display's windowing mode.
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index 9245f8c..ffd6d21 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -113,7 +113,7 @@
}
/**
- * @return true if the width or height has changed since last reported to the client.
+ * @return true if the width or height has changed since last updating resizing window.
*/
boolean didFrameSizeChange() {
return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
@@ -135,6 +135,13 @@
}
/**
+ * @return true if the width or height has changed since last reported to the client.
+ */
+ boolean isFrameSizeChangeReported() {
+ return mFrameSizeChanged || didFrameSizeChange();
+ }
+
+ /**
* Resets the size changed flags so they're all set to false again. This should be called
* after the frames are reported to client.
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e70ad6f..f0dc7fe 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -84,8 +84,6 @@
import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
-import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
-import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
@@ -2234,11 +2232,6 @@
final DisplayContent dc = win.getDisplayContent();
- if (win.mPendingPositionChanged != null) {
- win.mPendingPositionChanged.updateLeashPosition(frameNumber);
- win.mPendingPositionChanged = null;
- }
-
if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
win.prepareDrawHandlers();
result |= RELAYOUT_RES_BLAST_SYNC;
@@ -8638,14 +8631,16 @@
final WindowState win = windowForClientLocked(session, window, false);
if (win == null) {
Slog.w(TAG, "Failed to generate DisplayHash. Invalid window");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_MISSING_WINDOW);
+ mDisplayHashController.sendDisplayHashError(callback,
+ DISPLAY_HASH_ERROR_MISSING_WINDOW);
return;
}
DisplayContent displayContent = win.getDisplayContent();
if (displayContent == null) {
Slog.w(TAG, "Failed to generate DisplayHash. Window is not on a display");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+ mDisplayHashController.sendDisplayHashError(callback,
+ DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
return;
}
@@ -8655,7 +8650,8 @@
if (boundsInDisplay.isEmpty()) {
Slog.w(TAG, "Failed to generate DisplayHash. Bounds are not on screen");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+ mDisplayHashController.sendDisplayHashError(callback,
+ DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
return;
}
}
@@ -8665,23 +8661,13 @@
// be covering it with the same uid. We want to make sure we include content that's
// covering to ensure we get as close as possible to what the user sees
final int uid = session.mUid;
- SurfaceControl.LayerCaptureArgs args =
+ SurfaceControl.LayerCaptureArgs.Builder args =
new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl)
.setUid(uid)
- .setSourceCrop(boundsInDisplay)
- .build();
+ .setSourceCrop(boundsInDisplay);
- SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
- SurfaceControl.captureLayers(args);
- if (screenshotHardwareBuffer == null
- || screenshotHardwareBuffer.getHardwareBuffer() == null) {
- Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
- sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
- return;
- }
-
- mDisplayHashController.generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(),
- boundsInWindow, hashAlgorithm, callback);
+ mDisplayHashController.generateDisplayHash(args, boundsInWindow,
+ hashAlgorithm, callback);
}
boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
@@ -8699,10 +8685,4 @@
return snapshot != null && snapshot.hasImeSurface();
}
}
-
- private void sendDisplayHashError(RemoteCallback callback, int errorCode) {
- Bundle bundle = new Bundle();
- bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
- callback.sendResult(bundle);
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2eadcd5..7ebc1cc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -726,8 +726,6 @@
*/
private InsetsState mFrozenInsetsState;
- @Nullable InsetsSourceProvider mPendingPositionChanged;
-
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
@@ -774,6 +772,12 @@
updateSurfacePosition(t);
};
+ private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
+ if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+ t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ }
+ };
+
/**
* @see #setSurfaceTranslationY(int)
*/
@@ -2129,6 +2133,8 @@
: getTask().getWindowConfiguration().hasMovementAnimations();
if (mToken.okToAnimate()
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+ && !mWindowFrames.didFrameSizeChange()
+ && !surfaceInsetsChanging()
&& !isDragResizing()
&& hasMovementAnimation
&& !mWinAnimator.mLastHidden
@@ -5318,13 +5324,17 @@
// prior to the rotation.
if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null
&& !mLastSurfacePosition.equals(mSurfacePosition)) {
- t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ final boolean frameSizeChanged = mWindowFrames.isFrameSizeChangeReported();
+ final boolean surfaceInsetsChanged = surfaceInsetsChanging();
+ final boolean surfaceSizeChanged = frameSizeChanged || surfaceInsetsChanged;
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
- if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
+ if (surfaceInsetsChanged) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
- t.deferTransactionUntil(mSurfaceControl,
- mWinAnimator.mSurfaceController.mSurfaceControl,
- getFrameNumber());
+ }
+ if (surfaceSizeChanged) {
+ applyWithNextDraw(mSetSurfacePositionConsumer);
+ } else {
+ mSetSurfacePositionConsumer.accept(t);
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7cbb1c6..ac2281a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15479,10 +15479,8 @@
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
- String currentMode = mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE);
- if (currentMode == null) {
- currentMode = ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
- }
+ final String currentMode =
+ ConnectivityManager.getPrivateDnsMode(mContext.getContentResolver());
switch (currentMode) {
case ConnectivityManager.PRIVATE_DNS_MODE_OFF:
return PRIVATE_DNS_MODE_OFF;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5fbf1c4..4e23609 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -116,6 +116,7 @@
import com.android.server.clipboard.ClipboardService;
import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
+import com.android.server.connectivity.PacProxyService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -1315,6 +1316,7 @@
ConsumerIrService consumerIr = null;
MmsServiceBroker mmsService = null;
HardwarePropertiesManagerService hardwarePropertiesService = null;
+ PacProxyService pacProxyService = null;
boolean disableSystemTextClassifier = SystemProperties.getBoolean(
"config.disable_systemtextclassifier", false);
@@ -1874,6 +1876,15 @@
t.traceEnd();
}
+ t.traceBegin("StartPacProxyService");
+ try {
+ pacProxyService = new PacProxyService(context);
+ ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+ } catch (Throwable e) {
+ reportWtf("starting PacProxyService", e);
+ }
+ t.traceEnd();
+
t.traceBegin("StartConnectivityService");
// This has to be called after NetworkManagementService, NetworkStatsService
// and NetworkPolicyManager because ConnectivityService needs to take these
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 44b9f44..872b955 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1114,6 +1114,30 @@
argThat(closeTo(newEndSpec)));
}
+ @Test
+ public void testSetScale_toMagnifying_shouldNotifyActivatedState() {
+ setScaleToMagnifying();
+
+ verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(true));
+ }
+
+ @Test
+ public void testReset_afterMagnifying_shouldNotifyDeactivatedState() {
+ setScaleToMagnifying();
+
+ mFullScreenMagnificationController.reset(DISPLAY_0, mAnimationCallback);
+ verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(false));
+ }
+
+ private void setScaleToMagnifying() {
+ register(DISPLAY_0);
+ float scale = 2.0f;
+ PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+
+ mFullScreenMagnificationController.setScale(DISPLAY_0, scale, pivotPoint.x, pivotPoint.y,
+ false, SERVICE_ID_1);
+ }
+
private void initMockWindowManager() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 0c3640c..84c76b7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -16,6 +16,9 @@
package com.android.server.accessibility.magnification;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import static org.junit.Assert.assertEquals;
@@ -24,6 +27,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
@@ -283,6 +287,27 @@
}
@Test
+ public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() {
+ mMagnificationController.onWindowMagnificationActivationState(true);
+
+ mMagnificationController.onWindowMagnificationActivationState(false);
+
+ verify(mMagnificationController).logMagnificationUsageState(
+ eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong());
+ }
+
+ @Test
+ public void
+ onFullScreenMagnificationActivationState_fullScreenActivated_logFullScreenDuration() {
+ mMagnificationController.onFullScreenMagnificationActivationState(true);
+
+ mMagnificationController.onFullScreenMagnificationActivationState(false);
+
+ verify(mMagnificationController).logMagnificationUsageState(
+ eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong());
+ }
+
+ @Test
public void onTouchInteractionStart_fullScreenAndCapabilitiesAll_showMagnificationButton()
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index f26c86c..955217c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -392,6 +392,23 @@
assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
}
+ @Test
+ public void onWindowMagnificationActivationState_magnifierEnabled_notifyActivatedState() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+
+ verify(mMockCallback).onWindowMagnificationActivationState(eq(true));
+ }
+
+ @Test
+ public void onWindowMagnificationActivationState_magnifierDisabled_notifyDeactivatedState() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+ mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, true);
+
+ verify(mMockCallback).onWindowMagnificationActivationState(eq(false));
+ }
+
private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
final int len = pointersLocation.length;
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
new file mode 100644
index 0000000..7afcbf7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.SparseIntArray;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.appsearch.external.localstorage.MockPackageManager;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class PlatformLoggerTest {
+ private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
+ private static final int TEST_DEFAULT_SAMPLING_RATIO = 10;
+ private static final String TEST_PACKAGE_NAME = "packageName";
+ private MockPackageManager mMockPackageManager = new MockPackageManager();
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = ApplicationProvider.getApplicationContext();
+ mContext =
+ new ContextWrapper(context) {
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager.getMockPackageManager();
+ }
+ };
+ }
+
+ @Test
+ public void testcreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ TEST_DEFAULT_SAMPLING_RATIO,
+ /*samplingRatioMap=*/ null));
+
+ // Make sure default sampling ratio is used if samplingMap is not provided.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_UNKNOWN).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_QUERY).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ }
+
+
+ @Test
+ public void testcreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
+ int putDocumentSamplingRatio = 1;
+ int querySamplingRatio = 2;
+ final SparseIntArray samplingRatios = new SparseIntArray();
+ samplingRatios.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingRatio);
+ samplingRatios.put(CallStats.CALL_TYPE_QUERY, querySamplingRatio);
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ TEST_DEFAULT_SAMPLING_RATIO,
+ samplingRatios));
+
+ // The default sampling ratio should be used if no sampling ratio is
+ // provided for certain call type.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
+ TEST_DEFAULT_SAMPLING_RATIO);
+
+ // The configured sampling ratio is used if sampling ratio is available
+ // for certain call type.
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingRatio).isEqualTo(
+ putDocumentSamplingRatio);
+ assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+ CallStats.CALL_TYPE_QUERY).mSamplingRatio).isEqualTo(
+ querySamplingRatio);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
+ final int samplingRatio = 1;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ samplingRatio,
+ /* samplingMap=*/ null));
+
+ // Sample should always be logged for the first time if sampling is disabled(value is one).
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_falseWhenSampleRatioIsNegative() {
+ final int samplingRatio = -1;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ samplingRatio,
+ /* samplingMap=*/ null));
+
+ // Makes sure sample will be excluded due to sampling if sample ratio is negative.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+ // Skipped count should be 0 since it doesn't pass the sampling.
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
+ // Next sample won't be excluded due to sampling.
+ final int samplingRatio = 1;
+ // Next sample would guaranteed to be too close.
+ final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ minTimeIntervalBetweenSamplesMillis,
+ samplingRatio,
+ /* samplingMap=*/ null));
+ logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+ // Makes sure sample will be excluded due to rate limiting if samples are too close.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(1);
+ }
+
+ @Test
+ public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
+ // Next sample won't be excluded due to sampling.
+ final int samplingRatio = 1;
+ // Next sample would guaranteed to be included.
+ final int minTimeIntervalBetweenSamplesMillis = 0;
+ final String testPackageName = "packageName";
+ PlatformLogger logger = new PlatformLogger(
+ ApplicationProvider.getApplicationContext(),
+ UserHandle.USER_NULL,
+ new PlatformLogger.Config(
+ minTimeIntervalBetweenSamplesMillis,
+ samplingRatio,
+ /* samplingMap=*/ null));
+ logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+ // Makes sure sample will be logged if it is not too close to previous sample.
+ assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+ assertThat(logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+ }
+
+ /** Makes sure the caching works while getting the UID for calling package. */
+ @Test
+ public void testGetPackageUidAsUser() throws Exception {
+ final String testPackageName = "packageName";
+ final int testUid = 1234;
+ PlatformLogger logger = new PlatformLogger(
+ mContext,
+ mContext.getUserId(),
+ new PlatformLogger.Config(
+ TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+ TEST_DEFAULT_SAMPLING_RATIO,
+ /* samplingMap=*/ null));
+ mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
+
+ //
+ // First time, no cache
+ //
+ PlatformLogger.ExtraStats extraStats = logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+ verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
+ eq(testPackageName), /*userId=*/ anyInt());
+ assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+
+ //
+ // Second time, we have cache
+ //
+ extraStats = logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+ // Count is still one since we will use the cache
+ verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
+ eq(testPackageName), /*userId=*/ anyInt());
+ assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+
+ //
+ // Remove the cache and try again
+ //
+ assertThat(logger.removeCachedUidForPackage(testPackageName)).isEqualTo(testUid);
+ extraStats = logger.createExtraStatsLocked(testPackageName,
+ CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+ // count increased by 1 since cache is cleared
+ verify(mMockPackageManager.getMockPackageManager(), times(2)).getPackageUidAsUser(
+ eq(testPackageName), /*userId=*/ anyInt());
+ assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54825ee..c1b6101 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -65,6 +65,7 @@
@Mock HysteresisLevels mScreenBrightnessThresholds;
@Mock Handler mNoOpHandler;
@Mock DisplayDevice mDisplayDevice;
+ @Mock HighBrightnessModeController mHbmController;
private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
@Before
@@ -90,7 +91,8 @@
BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
- mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay, mContext
+ mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay,
+ mContext, mHbmController
);
controller.setLoggingEnabled(true);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 47f3bf9..b5336e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -45,7 +45,6 @@
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
-import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -667,7 +666,6 @@
@Test
public void initCecVersion_limitToMinimumSupportedVersion() {
mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
- Log.e("MARVIN", "set setting CEC");
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
@@ -679,9 +677,7 @@
@Test
public void initCecVersion_limitToAtLeast1_4() {
- Log.e("MARVIN", "set HAL CEC to 0");
mNativeWrapper.setCecVersion(0x0);
- Log.e("MARVIN", "set setting CEC to 2");
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
@@ -694,7 +690,6 @@
@Test
public void initCecVersion_useHighestMatchingVersion() {
mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
- Log.e("MARVIN", "set setting CEC");
mHdmiControlService.getHdmiCecConfig().setIntValue(
HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
HdmiControlManager.HDMI_CEC_VERSION_2_0);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 1b7e1ca..7d5eec0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -107,7 +107,8 @@
waitForCompletion(thread);
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED));
+ verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
+ eq(Vibration.Status.IGNORED_UNSUPPORTED));
}
@Test
@@ -121,7 +122,8 @@
waitForCompletion(thread);
verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
- verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED));
+ verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
+ eq(Vibration.Status.IGNORED_UNSUPPORTED));
}
@Test
@@ -206,8 +208,8 @@
thread.cancel();
waitForCompletion(thread);
- verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
- verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+ verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
+ verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index e22cda6..eba5634 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -42,16 +42,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.app.ActivityOptions;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.LaunchParamsController.LaunchParams;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +91,48 @@
}
@Test
+ public void getOrCreateLaunchRootRespectsResolvedWindowingMode() {
+ final Task rootTask = createTaskStackOnDisplay(
+ WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ rootTask.mCreatedByOrganizer = true;
+ final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
+ taskDisplayArea.setLaunchRootTask(
+ rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD});
+
+ final Task candidateRootTask = createTaskStackOnDisplay(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+ final LaunchParams launchParams = new LaunchParams();
+ launchParams.mWindowingMode = WINDOWING_MODE_FREEFORM;
+
+ final Task actualRootTask = taskDisplayArea.getOrCreateRootTask(
+ activity, null /* options */, candidateRootTask,
+ launchParams, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertSame(rootTask, actualRootTask.getRootTask());
+ }
+
+ @Test
+ public void getOrCreateLaunchRootUsesActivityOptionsWindowingMode() {
+ final Task rootTask = createTaskStackOnDisplay(
+ WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ rootTask.mCreatedByOrganizer = true;
+ final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
+ taskDisplayArea.setLaunchRootTask(
+ rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD});
+
+ final Task candidateRootTask = createTaskStackOnDisplay(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ final Task actualRootTask = taskDisplayArea.getOrCreateRootTask(
+ activity, options, candidateRootTask,
+ null /* launchParams */, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertSame(rootTask, actualRootTask.getRootTask());
+ }
+
+ @Test
public void testActivityWithZBoost_taskDisplayAreaDoesNotMoveUp() {
final Task stack = createTaskStackOnDisplay(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 7822a85..ae8e2de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -100,9 +100,9 @@
}
@Override
- public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
- CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
- int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+ public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+ int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
final com.android.server.wm.WindowState window;
final ActivityRecord activity;
final WindowManagerService wm = mWmSupplier.get();
diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java
index 809f2bc..5fb6b33 100644
--- a/telecomm/java/android/telecom/CallDiagnosticService.java
+++ b/telecomm/java/android/telecom/CallDiagnosticService.java
@@ -27,6 +27,8 @@
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
+import android.telephony.Annotation;
+import android.telephony.ims.ImsReasonInfo;
import android.util.ArrayMap;
import com.android.internal.telecom.ICallDiagnosticService;
@@ -105,6 +107,12 @@
throws RemoteException {
handleBluetoothCallQualityReport(qualityReport);
}
+
+ @Override
+ public void notifyCallDisconnected(@NonNull String callId,
+ @NonNull DisconnectCause disconnectCause) throws RemoteException {
+ handleCallDisconnected(callId, disconnectCause);
+ }
}
/**
@@ -329,6 +337,32 @@
}
/**
+ * Handles a request from the Telecom framework to get a disconnect message from the
+ * {@link CallDiagnosticService}.
+ * @param callId The ID of the call.
+ * @param disconnectCause The telecom disconnect cause.
+ */
+ private void handleCallDisconnected(@NonNull String callId,
+ @NonNull DisconnectCause disconnectCause) {
+ Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
+ DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
+ CharSequence message;
+ if (disconnectCause.getImsReasonInfo() != null) {
+ message = diagnosticCall.onCallDisconnected(disconnectCause.getImsReasonInfo());
+ } else {
+ message = diagnosticCall.onCallDisconnected(
+ disconnectCause.getTelephonyDisconnectCause(),
+ disconnectCause.getTelephonyPreciseDisconnectCause());
+ }
+ try {
+ mAdapter.overrideDisconnectMessage(callId, message);
+ } catch (RemoteException e) {
+ Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
+ callId, disconnectCause, e);
+ }
+ }
+
+ /**
* Handles an incoming bluetooth call quality report from Telecom. Notifies via
* {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
* BluetoothCallQualityReport)}.
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 1472a4ac..ed7b79f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,9 +16,13 @@
package android.telecom;
+import android.annotation.Nullable;
import android.media.ToneGenerator;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.Annotation;
+import android.telephony.PreciseDisconnectCause;
+import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import java.util.Objects;
@@ -112,6 +116,9 @@
private CharSequence mDisconnectDescription;
private String mDisconnectReason;
private int mToneToPlay;
+ private int mTelephonyDisconnectCause;
+ private int mTelephonyPreciseDisconnectCause;
+ private ImsReasonInfo mImsReasonInfo;
/**
* Creates a new DisconnectCause.
@@ -155,11 +162,36 @@
*/
public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
int toneToPlay) {
+ this(code, label, description, reason, toneToPlay,
+ android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
+ PreciseDisconnectCause.ERROR_UNSPECIFIED,
+ null /* imsReasonInfo */);
+ }
+
+ /**
+ * Creates a new DisconnectCause instance.
+ * @param code The code for the disconnect cause.
+ * @param label The localized label to show to the user to explain the disconnect.
+ * @param description The localized description to show to the user to explain the disconnect.
+ * @param reason The reason for the disconnect.
+ * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}.
+ * @param telephonyDisconnectCause The Telephony disconnect cause.
+ * @param telephonyPreciseDisconnectCause The Telephony precise disconnect cause.
+ * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
+ * @hide
+ */
+ public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
+ int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
+ @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
+ @Nullable ImsReasonInfo imsReasonInfo) {
mDisconnectCode = code;
mDisconnectLabel = label;
mDisconnectDescription = description;
mDisconnectReason = reason;
mToneToPlay = toneToPlay;
+ mTelephonyDisconnectCause = telephonyDisconnectCause;
+ mTelephonyPreciseDisconnectCause = telephonyPreciseDisconnectCause;
+ mImsReasonInfo = imsReasonInfo;
}
/**
@@ -209,6 +241,33 @@
}
/**
+ * Returns the telephony {@link android.telephony.DisconnectCause} for the call.
+ * @return The disconnect cause.
+ * @hide
+ */
+ public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() {
+ return mTelephonyDisconnectCause;
+ }
+
+ /**
+ * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call.
+ * @return The precise disconnect cause.
+ * @hide
+ */
+ public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() {
+ return mTelephonyPreciseDisconnectCause;
+ }
+
+ /**
+ * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection.
+ * @return The {@link ImsReasonInfo} or {@code null} if not known.
+ * @hide
+ */
+ public @Nullable ImsReasonInfo getImsReasonInfo() {
+ return mImsReasonInfo;
+ }
+
+ /**
* Returns the tone to play when disconnected.
*
* @return the tone as defined in {@link ToneGenerator} to play when disconnected.
@@ -217,7 +276,8 @@
return mToneToPlay;
}
- public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR = new Creator<DisconnectCause>() {
+ public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR
+ = new Creator<DisconnectCause>() {
@Override
public DisconnectCause createFromParcel(Parcel source) {
int code = source.readInt();
@@ -225,7 +285,11 @@
CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
String reason = source.readString();
int tone = source.readInt();
- return new DisconnectCause(code, label, description, reason, tone);
+ int telephonyDisconnectCause = source.readInt();
+ int telephonyPreciseDisconnectCause = source.readInt();
+ ImsReasonInfo imsReasonInfo = source.readParcelable(null);
+ return new DisconnectCause(code, label, description, reason, tone,
+ telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
}
@Override
@@ -241,6 +305,9 @@
TextUtils.writeToParcel(mDisconnectDescription, destination, flags);
destination.writeString(mDisconnectReason);
destination.writeInt(mToneToPlay);
+ destination.writeInt(mTelephonyDisconnectCause);
+ destination.writeInt(mTelephonyPreciseDisconnectCause);
+ destination.writeParcelable(mImsReasonInfo, 0);
}
@Override
@@ -254,7 +321,10 @@
+ Objects.hashCode(mDisconnectLabel)
+ Objects.hashCode(mDisconnectDescription)
+ Objects.hashCode(mDisconnectReason)
- + Objects.hashCode(mToneToPlay);
+ + Objects.hashCode(mToneToPlay)
+ + Objects.hashCode(mTelephonyDisconnectCause)
+ + Objects.hashCode(mTelephonyPreciseDisconnectCause)
+ + Objects.hashCode(mImsReasonInfo);
}
@Override
@@ -265,7 +335,11 @@
&& Objects.equals(mDisconnectLabel, d.getLabel())
&& Objects.equals(mDisconnectDescription, d.getDescription())
&& Objects.equals(mDisconnectReason, d.getReason())
- && Objects.equals(mToneToPlay, d.getTone());
+ && Objects.equals(mToneToPlay, d.getTone())
+ && Objects.equals(mTelephonyDisconnectCause, d.getTelephonyDisconnectCause())
+ && Objects.equals(mTelephonyPreciseDisconnectCause,
+ d.getTelephonyPreciseDisconnectCause())
+ && Objects.equals(mImsReasonInfo, d.getImsReasonInfo());
}
return false;
}
@@ -325,6 +399,11 @@
+ " Label: (" + label + ")"
+ " Description: (" + description + ")"
+ " Reason: (" + reason + ")"
- + " Tone: (" + mToneToPlay + ") ]";
+ + " Tone: (" + mToneToPlay + ") "
+ + " TelephonyCause: " + mTelephonyDisconnectCause + "/"
+ + mTelephonyPreciseDisconnectCause
+ + " ImsReasonInfo: "
+ + mImsReasonInfo
+ + "]";
}
}
diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
index 65b4d19..fc9879a 100644
--- a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
@@ -18,6 +18,7 @@
import android.telecom.BluetoothCallQualityReport;
import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
import android.telecom.ParcelableCall;
import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
@@ -34,4 +35,5 @@
void removeDiagnosticCall(in String callId);
void receiveDeviceToDeviceMessage(in String callId, int message, int value);
void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport);
+ void notifyCallDisconnected(in String callId, in DisconnectCause disconnectCause);
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4dc6c7c..3084b14 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14634,6 +14634,10 @@
/**
* Enable/Disable E-UTRA-NR Dual Connectivity.
*
+ * This api is supported only if
+ * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+ * returns true.
* @param nrDualConnectivityState expected NR dual connectivity state
* This can be passed following states
* <ol>
@@ -14648,6 +14652,9 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
public @EnableNrDualConnectivityResult int setNrDualConnectivityState(
@NrDualConnectivityState int nrDualConnectivityState) {
try {
@@ -14667,15 +14674,21 @@
/**
* Is E-UTRA-NR Dual Connectivity enabled.
+ * This api is supported only if
+ * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+ * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+ * returns true.
* @return true if dual connectivity is enabled else false. Enabled state does not mean dual
* connectivity is active. It means the device is allowed to connect to both primary and
* secondary cell.
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
* @throws IllegalStateException if the Telephony process is not currently available.
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
public boolean isNrDualConnectivityEnabled() {
try {
ITelephony telephony = getITelephony();
@@ -14927,11 +14940,23 @@
public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
"CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+ /**
+ * Indicates whether {@link #setNrDualConnectivityState()} and
+ * {@link #isNrDualConnectivityEnabled()} ()} are available. See comments
+ * on respective methods for more information.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
+ "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "CAPABILITY_", value = {
CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
+ CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
})
public @interface RadioInterfaceCapability {}
diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
index e121b68..df58da5 100644
--- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
+++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
@@ -100,7 +100,6 @@
platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig),
packageName);
try {
- uiAutomation.dropShellPermissionIdentity();
mTestStatement.evaluate();
} finally {
adoptShellPermissions(uiAutomation);
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
index ce226fd..926ff4d 100644
--- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -130,6 +130,9 @@
return result;
}
sBuffers[slot] = anb;
+ if (timeoutMs == 0) {
+ return android::OK;
+ }
android::sp<android::Fence> fence(new android::Fence(fenceFd));
int waitResult = fence->wait(timeoutMs);
if (waitResult != android::OK) {
@@ -197,6 +200,28 @@
return result;
}
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetAsyncMode(JNIEnv* /* env */,
+ jclass /* clazz */,
+ jboolean async) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setAsyncMode(async);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetDequeueTimeout(
+ JNIEnv* /* env */, jclass /* clazz */, jlong timeoutMs) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setDequeueTimeout(timeoutMs);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetMaxDequeuedBufferCount(
+ JNIEnv* /* env */, jclass /* clazz */, jint maxDequeuedBuffers) {
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ return surface->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+}
+
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount(
JNIEnv* /* env */, jclass /* clazz */, jint count) {
assert(sAnw);
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
index 7d278dc..b67dc380 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -17,6 +17,7 @@
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -93,4 +94,80 @@
assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
}
+
+ @Test
+ // Leave IGBP in sync mode, try to dequeue and queue as fast as possible. Check that we
+ // occasionally get timeout errors.
+ fun testSyncMode_dequeueWithoutBlockingFails() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ var failures = false
+ for (i in 1..numFrames) {
+ if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+ failures = true
+ break
+ }
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertTrue(failures)
+ }
+ }
+
+ @Test
+ // Set IGBP to be in async mode, try to dequeue and queue as fast as possible. Client should be
+ // able to dequeue and queue buffers without being blocked.
+ fun testAsyncMode_dequeueWithoutBlocking() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+ for (i in 1..numFrames) {
+ assertEquals(0, activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */))
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ }
+ }
+
+ @Test
+ // Disable triple buffering in the system and leave IGBP in sync mode. Check that we
+ // occasionally get timeout errors.
+ fun testSyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ var failures = false
+ for (i in 1..numFrames) {
+ if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+ failures = true
+ break
+ }
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertTrue(failures)
+ }
+ }
+
+ @Test
+ // Disable triple buffering in the system and set IGBP to be in async mode. Try to dequeue and
+ // queue as fast as possible. Without triple buffering, the client does not have an extra buffer
+ // to dequeue and will not be able to dequeue and queue buffers without being blocked.
+ fun testAsyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+ val numFrames = 1000L
+ runOnUiThread { activity ->
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+ assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+ var failures = false
+ for (i in 1..numFrames) {
+ if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+ failures = true
+ break
+ }
+ activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+ }
+ assertTrue(failures)
+ }
+ }
}
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
index cfbd3ac..45a7094 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -54,7 +54,13 @@
external fun SurfaceSetScalingMode(scalingMode: Int)
external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int
external fun SurfaceCancelBuffer(slot: Int)
- external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true)
+ external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true): Int
+ external fun SurfaceSetAsyncMode(async: Boolean): Int
+ external fun SurfaceSetDequeueTimeout(timeout: Long): Int
+ external fun SurfaceQuery(what: Int): Int
+ external fun SurfaceSetMaxDequeuedBufferCount(maxDequeuedBuffers: Int): Int
+
+ // system/native_window.h functions
external fun NativeWindowSetBufferCount(count: Int): Int
external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int
external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int
diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
index 9c0fc7c..50ecb42 100644
--- a/tests/net/common/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -113,6 +113,15 @@
p = new IpPrefix("f00:::/32");
fail("Expected IllegalArgumentException: invalid IPv6 address");
} catch (IllegalArgumentException expected) { }
+
+ p = new IpPrefix("/64");
+ assertEquals("::/64", p.toString());
+
+ p = new IpPrefix("/128");
+ assertEquals("::1/128", p.toString());
+
+ p = new IpPrefix("[2001:db8::123]/64");
+ assertEquals("2001:db8::/64", p.toString());
}
@Test
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index 1eaf30c..2cf3cf9 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -53,6 +53,7 @@
import org.junit.runner.RunWith;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -117,6 +118,20 @@
assertEquals(456, address.getScope());
assertTrue(address.isIpv4());
+ address = new LinkAddress("/64", 1 /* flags */, 2 /* scope */);
+ assertEquals(Inet6Address.LOOPBACK, address.getAddress());
+ assertEquals(64, address.getPrefixLength());
+ assertEquals(1, address.getFlags());
+ assertEquals(2, address.getScope());
+ assertTrue(address.isIpv6());
+
+ address = new LinkAddress("[2001:db8::123]/64", 3 /* flags */, 4 /* scope */);
+ assertEquals(InetAddresses.parseNumericAddress("2001:db8::123"), address.getAddress());
+ assertEquals(64, address.getPrefixLength());
+ assertEquals(3, address.getFlags());
+ assertEquals(4, address.getScope());
+ assertTrue(address.isIpv6());
+
// InterfaceAddress doesn't have a constructor. Fetch some from an interface.
List<InterfaceAddress> addrs = NetworkInterface.getByName("lo").getInterfaceAddresses();
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index a07bce3..8932892 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -30,12 +30,21 @@
* -e selectTest_verbose true \
* com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
* </pre>
+ *
+ * <p>Use this filter when running FrameworksMockingCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.server.wm.test.filters.FrameworksTestsFilter \
+ * -e selectTest_verbose true \
+ * com.android.frameworks.mockingcoretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
*/
public final class FrameworksTestsFilter extends SelectTest {
private static final String[] SELECTED_TESTS = {
// Test specifications for FrameworksMockingCoreTests.
"android.app.activity.ActivityThreadClientTest",
+ "android.view.DisplayTest",
// Test specifications for FrameworksCoreTests.
"android.app.servertransaction.", // all tests under the package.
"android.view.CutoutSpecificationTest",