Merge "Bubbles: fix some issues with the pointer" into sc-dev
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 5def55f..5bf0b84 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -33,9 +33,10 @@
java_sdk_library {
name: "framework-appsearch",
- srcs: [ ":framework-appsearch-sources" ],
+ srcs: [":framework-appsearch-sources"],
sdk_version: "core_platform", // TODO(b/146218515) should be module_current
impl_only_libs: ["framework-minus-apex"], // TODO(b/146218515) should be removed
+ libs: ["unsupportedappusage"], // TODO(b/181887768) should be removed
defaults: ["framework-module-defaults"],
permitted_packages: ["android.app.appsearch"],
aidl: {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 64ac63c..c112d0e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.util.SchemaMigrationUtil;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -295,6 +296,19 @@
}
/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void getByUri(
+ @NonNull GetByUriRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BatchResultCallback<String, GenericDocument> callback) {
+ getByDocumentId(request.toGetByDocumentIdRequest(), executor, callback);
+ }
+
+ /**
* Gets {@link GenericDocument} objects by document IDs in a namespace from the {@link
* AppSearchSession} database.
*
@@ -489,6 +503,19 @@
}
/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public void remove(
+ @NonNull RemoveByUriRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BatchResultCallback<String, Void> callback) {
+ remove(request.toRemoveByDocumentIdRequest(), executor, callback);
+ }
+
+ /**
* Removes {@link GenericDocument} objects by document IDs in a namespace from the {@link
* AppSearchSession} database.
*
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 2a941fb..4378a98 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.app.appsearch.exceptions.IllegalSchemaException;
import android.app.appsearch.util.BundleUtil;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.util.ArraySet;
@@ -747,6 +748,31 @@
}
/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public Builder(@NonNull String propertyName) {
+ mBundle.putString(NAME_FIELD, propertyName);
+ mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT);
+ mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
+ mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, false);
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder setSchemaType(@NonNull String schemaType) {
+ mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
+ return this;
+ }
+
+ /**
* The cardinality of the property (whether it is optional, required or repeated).
*
* <p>If this method is not called, the default cardinality is {@link
@@ -778,6 +804,18 @@
}
/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public DocumentPropertyConfig.Builder setIndexNestedProperties(
+ boolean indexNestedProperties) {
+ return setShouldIndexNestedProperties(indexNestedProperties);
+ }
+
+ /**
* Constructs a new {@link PropertyConfig} from the contents of this builder.
*
* <p>After calling this method, the builder must no longer be used.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 2e42749..39a4884 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.appsearch.util.BundleUtil;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
@@ -137,6 +138,17 @@
return mBundle;
}
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public String getUri() {
+ return getId();
+ }
+
/** Returns the unique identifier of the {@link GenericDocument}. */
@NonNull
public String getId() {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
new file mode 100644
index 0000000..7b05eac
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+@Deprecated
+public final class GetByUriRequest {
+ /**
+ * Schema type to be used in {@link GetByUriRequest.Builder#addProjection} to apply property
+ * paths to all results, excepting any types that have had their own, specific property paths
+ * set.
+ */
+ public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
+
+ private final String mNamespace;
+ private final Set<String> mIds;
+ private final Map<String, List<String>> mTypePropertyPathsMap;
+
+ GetByUriRequest(
+ @NonNull String namespace,
+ @NonNull Set<String> ids,
+ @NonNull Map<String, List<String>> typePropertyPathsMap) {
+ mNamespace = Objects.requireNonNull(namespace);
+ mIds = Objects.requireNonNull(ids);
+ mTypePropertyPathsMap = Objects.requireNonNull(typePropertyPathsMap);
+ }
+
+ /** Returns the namespace attached to the request. */
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ /** Returns the set of document IDs attached to the request. */
+ @NonNull
+ public Set<String> getUris() {
+ return Collections.unmodifiableSet(mIds);
+ }
+
+ /**
+ * Returns a map from schema type to property paths to be used for projection.
+ *
+ * <p>If the map is empty, then all properties will be retrieved for all results.
+ *
+ * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this
+ * function, rather than calling it multiple times.
+ */
+ @NonNull
+ public Map<String, List<String>> getProjections() {
+ Map<String, List<String>> copy = new ArrayMap<>();
+ for (Map.Entry<String, List<String>> entry : mTypePropertyPathsMap.entrySet()) {
+ copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
+ }
+ return copy;
+ }
+
+ /**
+ * Returns a map from schema type to property paths to be used for projection.
+ *
+ * <p>If the map is empty, then all properties will be retrieved for all results.
+ *
+ * <p>A more efficient version of {@link #getProjections}, but it returns a modifiable map. This
+ * is not meant to be unhidden and should only be used by internal classes.
+ *
+ * @hide
+ */
+ @NonNull
+ public Map<String, List<String>> getProjectionsInternal() {
+ return mTypePropertyPathsMap;
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @NonNull
+ public GetByDocumentIdRequest toGetByDocumentIdRequest() {
+ GetByDocumentIdRequest.Builder builder =
+ new GetByDocumentIdRequest.Builder(mNamespace).addIds(mIds);
+ for (Map.Entry<String, List<String>> projection : mTypePropertyPathsMap.entrySet()) {
+ builder.addProjection(projection.getKey(), projection.getValue());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Builder for {@link GetByUriRequest} objects.
+ *
+ * <p>Once {@link #build} is called, the instance can no longer be used.
+ */
+ public static final class Builder {
+ private final String mNamespace;
+ private final Set<String> mIds = new ArraySet<>();
+ private final Map<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>();
+ private boolean mBuilt = false;
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public Builder(@NonNull String namespace) {
+ mNamespace = Objects.requireNonNull(namespace);
+ }
+
+ /**
+ * Adds one or more document IDs to the request.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
+ @NonNull
+ public Builder addUris(@NonNull String... ids) {
+ Objects.requireNonNull(ids);
+ return addUris(Arrays.asList(ids));
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder addUris(@NonNull Collection<String> ids) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Objects.requireNonNull(ids);
+ mIds.addAll(ids);
+ return this;
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder addProjection(
+ @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Objects.requireNonNull(schemaType);
+ Objects.requireNonNull(propertyPaths);
+ List<String> propertyPathsList = new ArrayList<>(propertyPaths.size());
+ for (String propertyPath : propertyPaths) {
+ Objects.requireNonNull(propertyPath);
+ propertyPathsList.add(propertyPath);
+ }
+ mProjectionTypePropertyPaths.put(schemaType, propertyPathsList);
+ return this;
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public GetByUriRequest build() {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBuilt = true;
+ return new GetByUriRequest(mNamespace, mIds, mProjectionTypePropertyPaths);
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
new file mode 100644
index 0000000..9c74966
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+@Deprecated
+public final class RemoveByUriRequest {
+ private final String mNamespace;
+ private final Set<String> mIds;
+
+ RemoveByUriRequest(String namespace, Set<String> ids) {
+ mNamespace = namespace;
+ mIds = ids;
+ }
+
+ /** Returns the namespace to remove documents from. */
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ /** Returns the set of document IDs attached to the request. */
+ @NonNull
+ public Set<String> getUris() {
+ return Collections.unmodifiableSet(mIds);
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @NonNull
+ public RemoveByDocumentIdRequest toRemoveByDocumentIdRequest() {
+ return new RemoveByDocumentIdRequest.Builder(mNamespace).addIds(mIds).build();
+ }
+
+ /**
+ * Builder for {@link RemoveByUriRequest} objects.
+ *
+ * <p>Once {@link #build} is called, the instance can no longer be used.
+ */
+ public static final class Builder {
+ private final String mNamespace;
+ private final Set<String> mIds = new ArraySet<>();
+ private boolean mBuilt = false;
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public Builder(@NonNull String namespace) {
+ mNamespace = Objects.requireNonNull(namespace);
+ }
+
+ /**
+ * Adds one or more document IDs to the request.
+ *
+ * @throws IllegalStateException if the builder has already been used.
+ */
+ @NonNull
+ public Builder addUris(@NonNull String... ids) {
+ Objects.requireNonNull(ids);
+ return addUris(Arrays.asList(ids));
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder addUris(@NonNull Collection<String> ids) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Objects.requireNonNull(ids);
+ mIds.addAll(ids);
+ return this;
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public RemoveByUriRequest build() {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBuilt = true;
+ return new RemoveByUriRequest(mNamespace, mIds);
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
index 8c8ade8..5cb59b3 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -18,6 +18,7 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.Preconditions;
@@ -68,7 +69,8 @@
/** Builder for {@link ReportUsageRequest} objects. */
public static final class Builder {
private final String mNamespace;
- private final String mDocumentId;
+ // TODO(b/181887768): Make this final
+ private String mDocumentId;
private Long mUsageTimestampMillis;
private boolean mBuilt = false;
@@ -79,6 +81,40 @@
}
/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public Builder(@NonNull String namespace) {
+ mNamespace = Objects.requireNonNull(namespace);
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder setUri(@NonNull String uri) {
+ mDocumentId = uri;
+ return this;
+ }
+
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public ReportUsageRequest.Builder setUsageTimeMillis(
+ @CurrentTimeMillisLong long usageTimestampMillis) {
+ return setUsageTimestampMillis(usageTimestampMillis);
+ }
+
+ /**
* Sets the timestamp in milliseconds of the usage report (the time at which the document
* was used).
*
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index b648071..9a1796c 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -192,7 +192,7 @@
return this;
}
- /** @deprecated this method exists only for dogfooder transition and must be removed */
+ /** @deprecated This method exists only for dogfooder transition and must be removed. */
@Deprecated
@NonNull
public Builder addMatch(@NonNull MatchInfo matchInfo) {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index b7bd387..7ad5fe8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.util.ArraySet;
@@ -324,6 +325,17 @@
return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
}
+ /**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ @NonNull
+ public String getUri() {
+ return getDocumentId();
+ }
+
/** Returns the id of the {@link GenericDocument} that failed to be migrated. */
@NonNull
public String getDocumentId() {
diff --git a/core/api/current.txt b/core/api/current.txt
index 7ed8b6c..b257f5e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -586,7 +586,6 @@
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
- field public static final int edgeEffectType;
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -53791,7 +53790,6 @@
method public int getCheckedItemPosition();
method public android.util.SparseBooleanArray getCheckedItemPositions();
method public int getChoiceMode();
- method public int getEdgeEffectType();
method public int getListPaddingBottom();
method public int getListPaddingLeft();
method public int getListPaddingRight();
@@ -53833,7 +53831,6 @@
method public void setChoiceMode(int);
method public void setDrawSelectorOnTop(boolean);
method public void setEdgeEffectColor(@ColorInt int);
- method public void setEdgeEffectType(int);
method public void setFastScrollAlwaysVisible(boolean);
method public void setFastScrollEnabled(boolean);
method public void setFastScrollStyle(int);
@@ -54524,7 +54521,6 @@
method @ColorInt public int getColor();
method public float getDistance();
method public int getMaxHeight();
- method public int getType();
method public boolean isFinished();
method public void onAbsorb(int);
method public void onPull(float);
@@ -54534,10 +54530,7 @@
method public void setBlendMode(@Nullable android.graphics.BlendMode);
method public void setColor(@ColorInt int);
method public void setSize(int, int);
- method public void setType(int);
field public static final android.graphics.BlendMode DEFAULT_BLEND_MODE;
- field public static final int TYPE_GLOW = 0; // 0x0
- field public static final int TYPE_STRETCH = 1; // 0x1
}
public class EditText extends android.widget.TextView {
@@ -54842,7 +54835,6 @@
method public boolean executeKeyEvent(android.view.KeyEvent);
method public void fling(int);
method public boolean fullScroll(int);
- method public int getEdgeEffectType();
method @ColorInt public int getLeftEdgeEffectColor();
method public int getMaxScrollAmount();
method @ColorInt public int getRightEdgeEffectColor();
@@ -54850,7 +54842,6 @@
method public boolean isSmoothScrollingEnabled();
method public boolean pageScroll(int);
method public void setEdgeEffectColor(@ColorInt int);
- method public void setEdgeEffectType(int);
method public void setFillViewport(boolean);
method public void setLeftEdgeEffectColor(@ColorInt int);
method public void setRightEdgeEffectColor(@ColorInt int);
@@ -55736,7 +55727,6 @@
method public void fling(int);
method public boolean fullScroll(int);
method @ColorInt public int getBottomEdgeEffectColor();
- method public int getEdgeEffectType();
method public int getMaxScrollAmount();
method @ColorInt public int getTopEdgeEffectColor();
method public boolean isFillViewport();
@@ -55745,7 +55735,6 @@
method public void scrollToDescendant(@NonNull android.view.View);
method public void setBottomEdgeEffectColor(@ColorInt int);
method public void setEdgeEffectColor(@ColorInt int);
- method public void setEdgeEffectType(int);
method public void setFillViewport(boolean);
method public void setSmoothScrollingEnabled(boolean);
method public void setTopEdgeEffectColor(@ColorInt int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35767b3..1330234 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1810,6 +1810,7 @@
public class VintfObject {
method public static String[] getHalNamesAndVersions();
+ method @NonNull public static String getPlatformSepolicyVersion();
method public static String getSepolicyVersion();
method public static Long getTargetFrameworkCompatibilityMatrixVersion();
method public static java.util.Map<java.lang.String,java.lang.String[]> getVndkSnapshots();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b3e656d..ff210e1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5613,7 +5613,7 @@
}
/** Performs the activity relaunch locally vs. requesting from system-server. */
- private void handleRelaunchActivityLocally(IBinder token) {
+ public void handleRelaunchActivityLocally(IBinder token) {
final ActivityClientRecord r = mActivities.get(token);
if (r == null) {
Log.w(TAG, "Activity to relaunch no longer exists");
@@ -5977,20 +5977,6 @@
// Update all affected Resources objects to use new ResourcesImpl
mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
}
-
- ApplicationPackageManager.configurationChanged();
-
- // Trigger a regular Configuration change event, only with a different assetsSeq number
- // so that we actually call through to all components.
- // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to
- // store configurations per-process.
- final Configuration config = mConfigurationController.getConfiguration();
- Configuration newConfig = new Configuration();
- newConfig.assetsSeq = (config != null ? config.assetsSeq : 0) + 1;
- mConfigurationController.handleConfigurationChanged(newConfig, null /* compat */);
-
- // Preserve windows to avoid black flickers when overlays change.
- relaunchAllActivities(true /* preserveWindows */, "handleApplicationInfoChanged");
}
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d77ce4a..75a38c2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1894,7 +1894,7 @@
OP_MANAGE_MEDIA, // MANAGE_MEDIA
OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT
OP_UWB_RANGING, // OP_UWB_RANGING
- OP_ACTIVITY_RECOGNITION_SOURCE, // OP_ACTIVITY_RECOGNITION_SOURCE
+ OP_ACTIVITY_RECOGNITION, // OP_ACTIVITY_RECOGNITION_SOURCE
OP_BLUETOOTH_ADVERTISE, // OP_BLUETOOTH_ADVERTISE
OP_RECORD_INCOMING_PHONE_AUDIO, // OP_RECORD_INCOMING_PHONE_AUDIO
};
@@ -4723,8 +4723,8 @@
}
/**
- * Flag for querying app op history: get only aggregate information and no
- * discrete accesses.
+ * Flag for querying app op history: get only aggregate information (counts of events) and no
+ * discret accesses information - specific accesses with timestamp.
*
* @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
*
@@ -4735,8 +4735,8 @@
public static final int HISTORY_FLAG_AGGREGATE = 1 << 0;
/**
- * Flag for querying app op history: get only discrete information and no
- * aggregate accesses.
+ * Flag for querying app op history: get only discrete access information (only specific
+ * accesses with timestamps) and no aggregate information (counts over time).
*
* @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
*
@@ -4747,7 +4747,7 @@
public static final int HISTORY_FLAG_DISCRETE = 1 << 1;
/**
- * Flag for querying app op history: get all types of historical accesses.
+ * Flag for querying app op history: get all types of historical access information.
*
* @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7dc662a..609c014 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5513,6 +5513,10 @@
* This method requires the caller to be the device owner.
* <p>
* This proxy is only a recommendation and it is possible that some apps will ignore it.
+ * <p>
+ * Note: The device owner won't be able to set a global HTTP proxy if there are unaffiliated
+ * secondary users or profiles on the device. It's recommended that affiliation ids are set for
+ * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
*
* @see ProxyInfo
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -12791,6 +12795,11 @@
* <p>In this mode, the DNS subsystem will attempt a TLS handshake to the network-supplied
* resolver prior to attempting name resolution in cleartext.
*
+ * <p>Note: The device owner won't be able to set the global private DNS mode if there are
+ * unaffiliated secondary users or profiles on the device. It's recommended that affiliation
+ * ids are set for new users as soon as possible after provisioning via
+ * {@link #setAffiliationIds}.
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
*
* @return {@code PRIVATE_DNS_SET_NO_ERROR} if the mode was set successfully, or
@@ -12826,6 +12835,11 @@
* the ability to resolve hostnames as system traffic to the resolver may not go through the
* VPN.
*
+ * <p>Note: The device owner won't be able to set the global private DNS mode if there are
+ * unaffiliated secondary users or profiles on the device. It's recommended that affiliation
+ * ids are set for new users as soon as possible after provisioning via
+ * {@link #setAffiliationIds}.
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
* @param privateDnsHost The hostname of a server that implements DNS over TLS (RFC7858).
*
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index de3eeee..2dbbfdf 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -43,6 +43,12 @@
*/
public class PeopleSpaceTile implements Parcelable {
+ public static final int SHOW_CONVERSATIONS = 1 << 0;
+ public static final int BLOCK_CONVERSATIONS = 1 << 1;
+ public static final int SHOW_IMPORTANT_CONVERSATIONS = 1 << 2;
+ public static final int SHOW_STARRED_CONTACTS = 1 << 3;
+ public static final int SHOW_CONTACTS = 1 << 4;
+
private String mId;
private CharSequence mUserName;
private Icon mUserIcon;
@@ -61,6 +67,11 @@
private Intent mIntent;
private long mNotificationTimestamp;
private List<ConversationStatus> mStatuses;
+ private boolean mCanBypassDnd;
+ private boolean mIsPackageSuspended;
+ private boolean mIsUserQuieted;
+ private int mNotificationPolicyState;
+ private float mContactAffinity;
private PeopleSpaceTile(Builder b) {
mId = b.mId;
@@ -81,6 +92,11 @@
mIntent = b.mIntent;
mNotificationTimestamp = b.mNotificationTimestamp;
mStatuses = b.mStatuses;
+ mCanBypassDnd = b.mCanBypassDnd;
+ mIsPackageSuspended = b.mIsPackageSuspended;
+ mIsUserQuieted = b.mIsUserQuieted;
+ mNotificationPolicyState = b.mNotificationPolicyState;
+ mContactAffinity = b.mContactAffinity;
}
public String getId() {
@@ -173,6 +189,41 @@
return mStatuses;
}
+ /**
+ * Whether the app associated with the conversation can bypass DND.
+ */
+ public boolean canBypassDnd() {
+ return mCanBypassDnd;
+ }
+
+ /**
+ * Whether the app associated with the conversation is suspended.
+ */
+ public boolean isPackageSuspended() {
+ return mIsPackageSuspended;
+ }
+
+ /**
+ * Whether the user associated with the conversation is quieted.
+ */
+ public boolean isUserQuieted() {
+ return mIsUserQuieted;
+ }
+
+ /**
+ * Returns the state of notifications for the conversation.
+ */
+ public int getNotificationPolicyState() {
+ return mNotificationPolicyState;
+ }
+
+ /**
+ * Returns the contact affinity (whether the contact is starred).
+ */
+ public float getContactAffinity() {
+ return mContactAffinity;
+ }
+
/** Converts a {@link PeopleSpaceTile} into a {@link PeopleSpaceTile.Builder}. */
public Builder toBuilder() {
Builder builder =
@@ -192,6 +243,11 @@
builder.setIntent(mIntent);
builder.setNotificationTimestamp(mNotificationTimestamp);
builder.setStatuses(mStatuses);
+ builder.setCanBypassDnd(mCanBypassDnd);
+ builder.setIsPackageSuspended(mIsPackageSuspended);
+ builder.setIsUserQuieted(mIsUserQuieted);
+ builder.setNotificationPolicyState(mNotificationPolicyState);
+ builder.setContactAffinity(mContactAffinity);
return builder;
}
@@ -215,6 +271,11 @@
private Intent mIntent;
private long mNotificationTimestamp;
private List<ConversationStatus> mStatuses;
+ private boolean mCanBypassDnd;
+ private boolean mIsPackageSuspended;
+ private boolean mIsUserQuieted;
+ private int mNotificationPolicyState;
+ private float mContactAffinity;
/** Builder for use only if a shortcut is not available for the tile. */
public Builder(String id, CharSequence userName, Icon userIcon, Intent intent) {
@@ -223,6 +284,7 @@
mUserIcon = userIcon;
mIntent = intent;
mPackageName = intent == null ? null : intent.getPackage();
+ mNotificationPolicyState = SHOW_CONVERSATIONS;
}
public Builder(ShortcutInfo info, LauncherApps launcherApps) {
@@ -232,6 +294,7 @@
mUserHandle = info.getUserHandle();
mPackageName = info.getPackage();
mContactUri = getContactUri(info);
+ mNotificationPolicyState = SHOW_CONVERSATIONS;
}
public Builder(ConversationChannel channel, LauncherApps launcherApps) {
@@ -246,6 +309,9 @@
mLastInteractionTimestamp = channel.getLastEventTimestamp();
mIsImportantConversation = channel.getParentNotificationChannel() != null
&& channel.getParentNotificationChannel().isImportantConversation();
+ mCanBypassDnd = channel.getParentNotificationChannel() != null
+ && channel.getParentNotificationChannel().canBypassDnd();
+ mNotificationPolicyState = SHOW_CONVERSATIONS;
}
/** Returns the Contact's Uri if present. */
@@ -366,6 +432,36 @@
return this;
}
+ /** Sets whether the conversation channel can bypass DND. */
+ public Builder setCanBypassDnd(boolean canBypassDnd) {
+ mCanBypassDnd = canBypassDnd;
+ return this;
+ }
+
+ /** Sets whether the package is suspended. */
+ public Builder setIsPackageSuspended(boolean isPackageSuspended) {
+ mIsPackageSuspended = isPackageSuspended;
+ return this;
+ }
+
+ /** Sets whether the user has been quieted. */
+ public Builder setIsUserQuieted(boolean isUserQuieted) {
+ mIsUserQuieted = isUserQuieted;
+ return this;
+ }
+
+ /** Sets the state of blocked notifications for the conversation. */
+ public Builder setNotificationPolicyState(int notificationPolicyState) {
+ mNotificationPolicyState = notificationPolicyState;
+ return this;
+ }
+
+ /** Sets the contact's affinity. */
+ public Builder setContactAffinity(float contactAffinity) {
+ mContactAffinity = contactAffinity;
+ return this;
+ }
+
/** Builds a {@link PeopleSpaceTile}. */
@NonNull
public PeopleSpaceTile build() {
@@ -393,6 +489,11 @@
mNotificationTimestamp = in.readLong();
mStatuses = new ArrayList<>();
in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
+ mCanBypassDnd = in.readBoolean();
+ mIsPackageSuspended = in.readBoolean();
+ mIsUserQuieted = in.readBoolean();
+ mNotificationPolicyState = in.readInt();
+ mContactAffinity = in.readFloat();
}
@Override
@@ -420,6 +521,11 @@
dest.writeParcelable(mIntent, flags);
dest.writeLong(mNotificationTimestamp);
dest.writeParcelableList(mStatuses, flags);
+ dest.writeBoolean(mCanBypassDnd);
+ dest.writeBoolean(mIsPackageSuspended);
+ dest.writeBoolean(mIsUserQuieted);
+ dest.writeInt(mNotificationPolicyState);
+ dest.writeFloat(mContactAffinity);
}
public static final @android.annotation.NonNull
@@ -427,7 +533,6 @@
public PeopleSpaceTile createFromParcel(Parcel source) {
return new PeopleSpaceTile(source);
}
-
public PeopleSpaceTile[] newArray(int size) {
return new PeopleSpaceTile[size];
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 22d75ef..725576f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -3069,6 +3069,13 @@
/**
* @hide
*/
+ public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
+ sCompatibilityModeEnabled = compatibilityModeEnabled;
+ }
+
+ /**
+ * @hide
+ */
public static void readConfigUseRoundIcon(Resources r) {
if (r != null) {
sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 250145e..52dad3e 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -24,7 +24,10 @@
import android.annotation.TestApi;
import android.content.Context;
+import com.android.internal.util.ArrayUtils;
+
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Manages the state of the system for devices with user-configurable hardware like a foldable
@@ -170,4 +173,34 @@
*/
void onStateChanged(int state);
}
+
+ /**
+ * Listens to changes in device state and reports the state as folded if the device state
+ * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState}
+ * resource.
+ * @hide
+ */
+ public static class FoldStateListener implements DeviceStateCallback {
+ private final int[] mFoldedDeviceStates;
+ private final Consumer<Boolean> mDelegate;
+
+ @Nullable
+ private Boolean lastResult;
+
+ public FoldStateListener(Context context, Consumer<Boolean> listener) {
+ mFoldedDeviceStates = context.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ mDelegate = listener;
+ }
+
+ @Override
+ public final void onStateChanged(int state) {
+ final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+
+ if (lastResult == null || !lastResult.equals(folded)) {
+ lastResult = folded;
+ mDelegate.accept(folded);
+ }
+ }
+ }
}
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
new file mode 100644
index 0000000..449e3ae
--- /dev/null
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -0,0 +1,94 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+
+/**
+ * Contains power consumption data across the entire device.
+ *
+ * {@hide}
+ */
+public final class AggregateBatteryConsumer extends BatteryConsumer implements Parcelable {
+
+ private final double mConsumedPowerMah;
+
+ public AggregateBatteryConsumer(@NonNull Builder builder) {
+ super(builder.mPowerComponentsBuilder.build());
+ mConsumedPowerMah = builder.mConsumedPowerMah;
+ }
+
+ private AggregateBatteryConsumer(@NonNull Parcel source) {
+ super(new PowerComponents(source));
+ mConsumedPowerMah = source.readDouble();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeDouble(mConsumedPowerMah);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<AggregateBatteryConsumer> CREATOR =
+ new Creator<AggregateBatteryConsumer>() {
+ public AggregateBatteryConsumer createFromParcel(@NonNull Parcel source) {
+ return new AggregateBatteryConsumer(source);
+ }
+
+ public AggregateBatteryConsumer[] newArray(int size) {
+ return new AggregateBatteryConsumer[size];
+ }
+ };
+
+ @Override
+ public double getConsumedPower() {
+ return mConsumedPowerMah;
+ }
+
+ /**
+ * Builder for DeviceBatteryConsumer.
+ */
+ public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> {
+ private double mConsumedPowerMah;
+
+ public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) {
+ super(customPowerComponentNames, includePowerModels);
+ }
+
+ /**
+ * Sets the total power included in this aggregate.
+ */
+ public Builder setConsumedPower(double consumedPowerMah) {
+ mConsumedPowerMah = consumedPowerMah;
+ return this;
+ }
+
+ /**
+ * Creates a read-only object out of the Builder values.
+ */
+ @NonNull
+ public AggregateBatteryConsumer build() {
+ return new AggregateBatteryConsumer(this);
+ }
+ }
+}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 32ca92c..6b628b0 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -27,7 +27,7 @@
*
* @hide
*/
-public abstract class BatteryConsumer {
+public class BatteryConsumer {
/**
* Power usage component, describing the particular part of the system
@@ -72,14 +72,15 @@
public static final int POWER_COMPONENT_WIFI = 11;
public static final int POWER_COMPONENT_WAKELOCK = 12;
public static final int POWER_COMPONENT_MEMORY = 13;
- public static final int POWER_COMPONENT_PHONE = 13;
- public static final int POWER_COMPONENT_IDLE = 15;
+ public static final int POWER_COMPONENT_PHONE = 14;
+ public static final int POWER_COMPONENT_AMBIENT_DISPLAY = 15;
+ public static final int POWER_COMPONENT_IDLE = 16;
// Power that is re-attributed to other battery consumers. For example, for System Server
// this represents the power attributed to apps requesting system services.
// The value should be negative or zero.
- public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 16;
+ public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 17;
- public static final int POWER_COMPONENT_COUNT = 17;
+ public static final int POWER_COMPONENT_COUNT = 18;
public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9ec6938..eec6810 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3234,6 +3234,11 @@
public abstract int getMaxLearnedBatteryCapacity() ;
/**
+ * @return The latest learned battery capacity in uAh.
+ */
+ public abstract int getLearnedBatteryCapacity();
+
+ /**
* Return the array of discharge step durations.
*/
public abstract LevelStepTracker getDischargeLevelStepTracker();
@@ -3925,7 +3930,9 @@
getStartClockTime(),
whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000,
getEstimatedBatteryCapacity(),
- getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity(),
+ getLearnedBatteryCapacity(),
+ getMinLearnedBatteryCapacity(),
+ getMaxLearnedBatteryCapacity(),
screenDozeTime / 1000);
@@ -4688,22 +4695,31 @@
pw.println(sb.toString());
}
+ final int lastLearnedBatteryCapacity = getLearnedBatteryCapacity();
+ if (lastLearnedBatteryCapacity > 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Last learned battery capacity: ");
+ sb.append(BatteryStatsHelper.makemAh(lastLearnedBatteryCapacity / 1000));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
final int minLearnedBatteryCapacity = getMinLearnedBatteryCapacity();
if (minLearnedBatteryCapacity > 0) {
sb.setLength(0);
sb.append(prefix);
- sb.append(" Min learned battery capacity: ");
- sb.append(BatteryStatsHelper.makemAh(minLearnedBatteryCapacity / 1000));
- sb.append(" mAh");
+ sb.append(" Min learned battery capacity: ");
+ sb.append(BatteryStatsHelper.makemAh(minLearnedBatteryCapacity / 1000));
+ sb.append(" mAh");
pw.println(sb.toString());
}
final int maxLearnedBatteryCapacity = getMaxLearnedBatteryCapacity();
if (maxLearnedBatteryCapacity > 0) {
sb.setLength(0);
sb.append(prefix);
- sb.append(" Max learned battery capacity: ");
- sb.append(BatteryStatsHelper.makemAh(maxLearnedBatteryCapacity / 1000));
- sb.append(" mAh");
+ sb.append(" Max learned battery capacity: ");
+ sb.append(BatteryStatsHelper.makemAh(maxLearnedBatteryCapacity / 1000));
+ sb.append(" mAh");
pw.println(sb.toString());
}
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index efdef62..6bc861f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.util.Range;
import android.util.SparseArray;
@@ -23,16 +24,53 @@
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis.
+ * <p>
+ * The totals for the entire device are returned as AggregateBatteryConsumers, which can be
+ * obtained by calling {@link #getAggregateBatteryConsumer(int)}.
+ * <p>
+ * Power attributed to individual apps is returned as UidBatteryConsumers, see
+ * {@link #getUidBatteryConsumers()}.
*
* @hide
*/
public final class BatteryUsageStats implements Parcelable {
- private final double mConsumedPower;
+
+ /**
+ * Scope of battery stats included in a BatteryConsumer: the entire device, just
+ * the apps, etc.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"AGGREGATE_BATTERY_CONSUMER_SCOPE_"}, value = {
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+ AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public static @interface AggregateBatteryConsumerScope {
+ }
+
+ /**
+ * Power consumption by the entire device, since last charge. The power usage in this
+ * scope includes both the power attributed to apps and the power unattributed to any
+ * apps.
+ */
+ public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE = 0;
+
+ /**
+ * Aggregated power consumed by all applications, combined, since last charge. This is
+ * the sum of power reported in UidBatteryConsumers.
+ */
+ public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS = 1;
+
+ public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2;
+
private final int mDischargePercentage;
private final long mStatsStartTimestampMs;
private final double mDischargedPowerLowerBound;
@@ -41,8 +79,8 @@
private final long mChargeTimeRemainingMs;
private final String[] mCustomPowerComponentNames;
private final List<UidBatteryConsumer> mUidBatteryConsumers;
- private final List<SystemBatteryConsumer> mSystemBatteryConsumers;
private final List<UserBatteryConsumer> mUserBatteryConsumers;
+ private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
private final Parcel mHistoryBuffer;
private final List<BatteryStats.HistoryTag> mHistoryTagPool;
@@ -57,8 +95,7 @@
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
- double totalPower = 0;
-
+ double totalPowerMah = 0;
final int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
for (int i = 0; i < uidBatteryConsumerCount; i++) {
@@ -66,30 +103,28 @@
builder.mUidBatteryConsumerBuilders.valueAt(i);
if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
final UidBatteryConsumer consumer = uidBatteryConsumerBuilder.build();
- totalPower += consumer.getConsumedPower();
+ totalPowerMah += consumer.getConsumedPower();
mUidBatteryConsumers.add(consumer);
}
}
- final int systemBatteryConsumerCount = builder.mSystemBatteryConsumerBuilders.size();
- mSystemBatteryConsumers = new ArrayList<>(systemBatteryConsumerCount);
- for (int i = 0; i < systemBatteryConsumerCount; i++) {
- final SystemBatteryConsumer consumer =
- builder.mSystemBatteryConsumerBuilders.valueAt(i).build();
- totalPower += consumer.getConsumedPower() - consumer.getPowerConsumedByApps();
- mSystemBatteryConsumers.add(consumer);
- }
-
final int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
for (int i = 0; i < userBatteryConsumerCount; i++) {
final UserBatteryConsumer consumer =
builder.mUserBatteryConsumerBuilders.valueAt(i).build();
- totalPower += consumer.getConsumedPower();
+ totalPowerMah += consumer.getConsumedPower();
mUserBatteryConsumers.add(consumer);
}
- mConsumedPower = totalPower;
+ builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(totalPowerMah);
+
+ mAggregateBatteryConsumers =
+ new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+ for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+ mAggregateBatteryConsumers[i] = builder.mAggregateBatteryConsumersBuilders[i].build();
+ }
}
/**
@@ -101,6 +136,15 @@
}
/**
+ * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully
+ * charged), in mAh
+ */
+ public double getConsumedPower() {
+ return mAggregateBatteryConsumers[AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE]
+ .getConsumedPower();
+ }
+
+ /**
* Portion of battery charge drained since BatteryStats reset (e.g. due to being fully
* charged), as percentage of the full charge in the range [0:100]
*/
@@ -136,11 +180,11 @@
}
/**
- * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully
- * charged), in mAh
+ * Returns a battery consumer for the specified battery consumer type.
*/
- public double getConsumedPower() {
- return mConsumedPower;
+ public BatteryConsumer getAggregateBatteryConsumer(
+ @AggregateBatteryConsumerScope int scope) {
+ return mAggregateBatteryConsumers[scope];
}
@NonNull
@@ -149,11 +193,6 @@
}
@NonNull
- public List<SystemBatteryConsumer> getSystemBatteryConsumers() {
- return mSystemBatteryConsumers;
- }
-
- @NonNull
public List<UserBatteryConsumer> getUserBatteryConsumers() {
return mUserBatteryConsumers;
}
@@ -178,13 +217,19 @@
private BatteryUsageStats(@NonNull Parcel source) {
mStatsStartTimestampMs = source.readLong();
- mConsumedPower = source.readDouble();
mDischargePercentage = source.readInt();
mDischargedPowerLowerBound = source.readDouble();
mDischargedPowerUpperBound = source.readDouble();
mBatteryTimeRemainingMs = source.readLong();
mChargeTimeRemainingMs = source.readLong();
mCustomPowerComponentNames = source.readStringArray();
+ mAggregateBatteryConsumers =
+ new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+ for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+ mAggregateBatteryConsumers[i] =
+ AggregateBatteryConsumer.CREATOR.createFromParcel(source);
+ mAggregateBatteryConsumers[i].setCustomPowerComponentNames(mCustomPowerComponentNames);
+ }
int uidCount = source.readInt();
mUidBatteryConsumers = new ArrayList<>(uidCount);
for (int i = 0; i < uidCount; i++) {
@@ -193,14 +238,6 @@
consumer.setCustomPowerComponentNames(mCustomPowerComponentNames);
mUidBatteryConsumers.add(consumer);
}
- int sysCount = source.readInt();
- mSystemBatteryConsumers = new ArrayList<>(sysCount);
- for (int i = 0; i < sysCount; i++) {
- final SystemBatteryConsumer consumer =
- SystemBatteryConsumer.CREATOR.createFromParcel(source);
- consumer.setCustomPowerComponentNames(mCustomPowerComponentNames);
- mSystemBatteryConsumers.add(consumer);
- }
int userCount = source.readInt();
mUserBatteryConsumers = new ArrayList<>(userCount);
for (int i = 0; i < userCount; i++) {
@@ -237,21 +274,19 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mStatsStartTimestampMs);
- dest.writeDouble(mConsumedPower);
dest.writeInt(mDischargePercentage);
dest.writeDouble(mDischargedPowerLowerBound);
dest.writeDouble(mDischargedPowerUpperBound);
dest.writeLong(mBatteryTimeRemainingMs);
dest.writeLong(mChargeTimeRemainingMs);
dest.writeStringArray(mCustomPowerComponentNames);
+ for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+ mAggregateBatteryConsumers[i].writeToParcel(dest, flags);
+ }
dest.writeInt(mUidBatteryConsumers.size());
for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
mUidBatteryConsumers.get(i).writeToParcel(dest, flags);
}
- dest.writeInt(mSystemBatteryConsumers.size());
- for (int i = mSystemBatteryConsumers.size() - 1; i >= 0; i--) {
- mSystemBatteryConsumers.get(i).writeToParcel(dest, flags);
- }
dest.writeInt(mUserBatteryConsumers.size());
for (int i = mUserBatteryConsumers.size() - 1; i >= 0; i--) {
mUserBatteryConsumers.get(i).writeToParcel(dest, flags);
@@ -299,10 +334,10 @@
private double mDischargedPowerUpperBoundMah;
private long mBatteryTimeRemainingMs = -1;
private long mChargeTimeRemainingMs = -1;
+ private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders =
+ new AggregateBatteryConsumer.Builder[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders =
new SparseArray<>();
- private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
- new SparseArray<>();
private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
new SparseArray<>();
private Parcel mHistoryBuffer;
@@ -315,6 +350,10 @@
public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) {
mCustomPowerComponentNames = customPowerComponentNames;
mIncludePowerModels = includePowerModels;
+ for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+ mAggregateBatteryConsumersBuilders[i] = new AggregateBatteryConsumer.Builder(
+ customPowerComponentNames, includePowerModels);
+ }
}
/**
@@ -386,7 +425,17 @@
}
/**
- * Creates or returns a exiting UidBatteryConsumer, which represents battery attribution
+ * Creates or returns an AggregateBatteryConsumer builder, which represents aggregate
+ * battery consumption data for the specified scope.
+ */
+ @NonNull
+ public AggregateBatteryConsumer.Builder getAggregateBatteryConsumerBuilder(
+ @AggregateBatteryConsumerScope int scope) {
+ return mAggregateBatteryConsumersBuilders[scope];
+ }
+
+ /**
+ * Creates or returns a UidBatteryConsumer, which represents battery attribution
* data for an individual UID.
*/
@NonNull
@@ -403,23 +452,7 @@
}
/**
- * Creates or returns a exiting SystemBatteryConsumer, which represents battery attribution
- * data for a specific drain type.
- */
- @NonNull
- public SystemBatteryConsumer.Builder getOrCreateSystemBatteryConsumerBuilder(
- @SystemBatteryConsumer.DrainType int drainType) {
- SystemBatteryConsumer.Builder builder = mSystemBatteryConsumerBuilders.get(drainType);
- if (builder == null) {
- builder = new SystemBatteryConsumer.Builder(mCustomPowerComponentNames,
- mIncludePowerModels, drainType);
- mSystemBatteryConsumerBuilders.put(drainType, builder);
- }
- return builder;
- }
-
- /**
- * Creates or returns a exiting UserBatteryConsumer, which represents battery attribution
+ * Creates or returns a UserBatteryConsumer, which represents battery attribution
* data for an individual {@link UserHandle}.
*/
@NonNull
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index a06a857..f3e0ce9 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1470,7 +1470,7 @@
return MediaStore.getOriginalMediaFormatFileDescriptor(context,
ParcelFileDescriptor.dup(fd));
} catch (Exception e) {
- Log.w(TAG, "Failed to convert to modern format file descriptor", e);
+ Log.d(TAG, "Failed to convert to modern format file descriptor", e);
return null;
}
}
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
deleted file mode 100644
index 7618339..0000000
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.util.Slog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Contains power consumption data attributed to a system-wide drain type.
- *
- * {@hide}
- */
-public class SystemBatteryConsumer extends BatteryConsumer implements Parcelable {
- private static final String TAG = "SystemBatteryConsumer";
-
- // ****************
- // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
- // so the constant values must never change.
- // ****************
- @IntDef(prefix = {"DRAIN_TYPE_"}, value = {
- DRAIN_TYPE_AMBIENT_DISPLAY,
- // Reserved: APP
- DRAIN_TYPE_BLUETOOTH,
- DRAIN_TYPE_CAMERA,
- DRAIN_TYPE_MOBILE_RADIO,
- DRAIN_TYPE_FLASHLIGHT,
- DRAIN_TYPE_IDLE,
- DRAIN_TYPE_MEMORY,
- // Reserved: OVERCOUNTED,
- DRAIN_TYPE_PHONE,
- DRAIN_TYPE_SCREEN,
- // Reserved: UNACCOUNTED,
- // Reserved: USER,
- DRAIN_TYPE_WIFI,
- DRAIN_TYPE_CUSTOM,
- })
- @Retention(RetentionPolicy.SOURCE)
- public static @interface DrainType {
- }
-
- public static final int DRAIN_TYPE_AMBIENT_DISPLAY = 0;
- public static final int DRAIN_TYPE_BLUETOOTH = 2;
- public static final int DRAIN_TYPE_CAMERA = 3;
- public static final int DRAIN_TYPE_MOBILE_RADIO = 4;
- public static final int DRAIN_TYPE_FLASHLIGHT = 5;
- public static final int DRAIN_TYPE_IDLE = 6;
- public static final int DRAIN_TYPE_MEMORY = 7;
- public static final int DRAIN_TYPE_PHONE = 9;
- public static final int DRAIN_TYPE_SCREEN = 10;
- public static final int DRAIN_TYPE_WIFI = 13;
- public static final int DRAIN_TYPE_CUSTOM = 14;
-
- @DrainType
- private final int mDrainType;
-
- private final double mPowerConsumedByAppsMah;
-
- @DrainType
- public int getDrainType() {
- return mDrainType;
- }
-
- private SystemBatteryConsumer(@NonNull SystemBatteryConsumer.Builder builder) {
- super(builder.mPowerComponentsBuilder.build());
- mDrainType = builder.mDrainType;
- mPowerConsumedByAppsMah = builder.mPowerConsumedByAppsMah;
- if (mPowerConsumedByAppsMah > getConsumedPower()) {
- Slog.wtf(TAG,
- "Power attributed to apps exceeds total: drain type = " + mDrainType
- + " total consumed power = " + getConsumedPower()
- + " power consumed by apps = " + mPowerConsumedByAppsMah);
- }
- }
-
- private SystemBatteryConsumer(Parcel in) {
- super(new PowerComponents(in));
- mDrainType = in.readInt();
- mPowerConsumedByAppsMah = in.readDouble();
- }
-
- public double getPowerConsumedByApps() {
- return mPowerConsumedByAppsMah;
- }
-
- /**
- * Returns the amount of time this consumer was operating.
- */
- public long getUsageDurationMillis() {
- return mPowerComponents.getMaxComponentUsageDurationMillis();
- }
-
- /**
- * Writes the contents into a Parcel.
- */
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(mDrainType);
- dest.writeDouble(mPowerConsumedByAppsMah);
- }
-
- public static final Creator<SystemBatteryConsumer> CREATOR =
- new Creator<SystemBatteryConsumer>() {
- @Override
- public SystemBatteryConsumer createFromParcel(Parcel in) {
- return new SystemBatteryConsumer(in);
- }
-
- @Override
- public SystemBatteryConsumer[] newArray(int size) {
- return new SystemBatteryConsumer[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * Builder for SystemBatteryConsumer.
- */
- public static final class Builder extends BaseBuilder<Builder> {
- @DrainType
- private final int mDrainType;
- private double mPowerConsumedByAppsMah;
- private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
-
- Builder(@NonNull String[] customPowerComponentNames,
- boolean includePowerModels, @DrainType int drainType) {
- super(customPowerComponentNames, includePowerModels);
- mDrainType = drainType;
- }
-
- /**
- * Sets the amount of power used by this system component that is attributed to apps.
- * It should not exceed the total consumed power.
- */
- public Builder setPowerConsumedByApps(double powerConsumedByAppsMah) {
- mPowerConsumedByAppsMah = powerConsumedByAppsMah;
- return this;
- }
-
- /**
- * Add a UidBatteryConsumer to this SystemBatteryConsumer. For example,
- * the UidBatteryConsumer with the UID == {@link Process#BLUETOOTH_UID} should
- * be added to the SystemBatteryConsumer with the drain type == {@link
- * #DRAIN_TYPE_BLUETOOTH}.
- * <p>
- * Calculated power and duration components of the added battery consumers
- * are aggregated at the time the SystemBatteryConsumer is built by the {@link #build()}
- * method.
- * </p>
- */
- public void addUidBatteryConsumer(UidBatteryConsumer.Builder uidBatteryConsumerBuilder) {
- if (mUidBatteryConsumers == null) {
- mUidBatteryConsumers = new ArrayList<>();
- }
- mUidBatteryConsumers.add(uidBatteryConsumerBuilder);
- }
-
- /**
- * Creates a read-only object out of the Builder values.
- */
- @NonNull
- public SystemBatteryConsumer build() {
- if (mUidBatteryConsumers != null) {
- for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
- UidBatteryConsumer.Builder uidBatteryConsumer = mUidBatteryConsumers.get(i);
- mPowerComponentsBuilder.addPowerAndDuration(
- uidBatteryConsumer.mPowerComponentsBuilder);
- }
- }
- return new SystemBatteryConsumer(this);
- }
- }
-}
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 7af8f71..bf0b655 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import android.util.Slog;
@@ -112,6 +113,15 @@
public static native String getSepolicyVersion();
/**
+ * @return the PLATFORM_SEPOLICY_VERSION build flag available in framework
+ * compatibility matrix.
+ *
+ * @hide
+ */
+ @TestApi
+ public static native @NonNull String getPlatformSepolicyVersion();
+
+ /**
* @return a list of VNDK snapshots supported by the framework, as
* specified in framework manifest. For example,
* [("27", ["libjpeg.so", "libbase.so"]),
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index 16204d8..973836a 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -19,7 +19,6 @@
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;
@@ -62,24 +61,6 @@
private static final String TAG = "DragAndDrop";
private static final boolean DEBUG = false;
- /**
- * 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;
/**
@@ -128,7 +109,9 @@
}
/**
- * Take permissions transiently. Permissions will be revoked when the app process terminates.
+ * Take permissions transiently. Permissions will be tied to this object's lifecycle; if not
+ * released explicitly, they will be released automatically when there are no more references
+ * to this object and it's garbage collected.
*
* <p>Note: This API is not exposed to apps.
*
@@ -138,14 +121,10 @@
*/
public boolean takeTransient() {
try {
- if (sAppToken == null) {
- sAppToken = new Binder();
- }
if (DEBUG) {
- Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
- + toHexString(sAppToken.hashCode()));
+ Log.d(TAG, this + ": calling takeTransient()");
}
- mDragAndDropPermissions.takeTransient(sAppToken);
+ mDragAndDropPermissions.takeTransient();
} catch (RemoteException e) {
Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
return false;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index f6d525c..3cffeb0 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
+
import android.annotation.IntDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -172,8 +174,6 @@
**/
public static final int DEADLINE = 13;
- private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
-
/**
* Identifiers for metrics available for each frame.
*
@@ -343,7 +343,7 @@
}
if (id == FIRST_DRAW_FRAME) {
- return (mTimingData[Index.FLAGS] & FRAME_INFO_FLAG_FIRST_DRAW) != 0 ? 1 : 0;
+ return (mTimingData[Index.FLAGS] & FLAG_WINDOW_VISIBILITY_CHANGED) != 0 ? 1 : 0;
} else if (id == INTENDED_VSYNC_TIMESTAMP) {
return mTimingData[Index.INTENDED_VSYNC];
} else if (id == VSYNC_TIMESTAMP) {
diff --git a/core/java/android/view/OnReceiveContentListener.java b/core/java/android/view/OnReceiveContentListener.java
index 1e930e6..f90e01c 100644
--- a/core/java/android/view/OnReceiveContentListener.java
+++ b/core/java/android/view/OnReceiveContentListener.java
@@ -74,7 +74,7 @@
* preserve the common behavior for inserting text. See the class javadoc for a sample
* implementation.
*
- * <p>Handling different content
+ * <h3>Handling different content</h3>
* <ul>
* <li>Text. If the {@link ContentInfo#getSource() source} is
* {@link ContentInfo#SOURCE_AUTOFILL autofill}, the view's content should be fully
@@ -86,6 +86,25 @@
* completely separate view).
* </ul>
*
+ * <h3>URI permissions</h3>
+ * <p>{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION Read permissions} are
+ * granted automatically by the platform for any
+ * {@link android.content.ContentResolver#SCHEME_CONTENT content URIs} in the payload passed
+ * to this listener. Permissions are transient and will be released automatically by the
+ * platform.
+ * <ul>
+ * <li>If the {@link ContentInfo#getSource() source} is the
+ * {@link ContentInfo#SOURCE_CLIPBOARD clipboard}, permissions are released whenever the
+ * next copy action is performed by the user.
+ * <li>If the source is {@link ContentInfo#SOURCE_AUTOFILL autofill}, permissions are tied
+ * to the target {@link android.app.Activity} lifecycle (released when the activity
+ * finishes).
+ * <li>For other sources, permissions are tied to the passed-in {@code payload} object
+ * (released automatically when there are no more references to it). To ensure that
+ * permissions are not released prematurely, implementations of this listener should pass
+ * along the {@code payload} object if processing is done on a background thread.
+ * </ul>
+ *
* @param view The view where the content insertion was requested.
* @param payload The content to insert and related metadata.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0958f3f..a06f193 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2764,7 +2764,9 @@
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
- mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED;
+ }
+ if (mFirst || viewVisibilityChanged) {
+ mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
}
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
final boolean freeformResizing = (relayoutResult
diff --git a/core/java/android/view/inputmethod/InputContentInfo.java b/core/java/android/view/inputmethod/InputContentInfo.java
index 9aa410f..5ebd9c1 100644
--- a/core/java/android/view/inputmethod/InputContentInfo.java
+++ b/core/java/android/view/inputmethod/InputContentInfo.java
@@ -199,7 +199,12 @@
}
/**
- * Requests a temporary read-only access permission for content URI associated with this object.
+ * Requests a temporary {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION read-only}
+ * access permission for the content URI associated with this object.
+ *
+ * <p>The lifecycle of the permission granted here is tied to this object instance. If the
+ * permission is not released explicitly via {@link #releasePermission()}, it will be
+ * released automatically when there are no more references to this object.</p>
*
* <p>Does nothing if the temporary permission is already granted.</p>
*/
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index 541b494..7726086 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -315,6 +315,8 @@
private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub {
private final Executor mExecutor;
private final UiTranslationStateCallback mCallback;
+ private ULocale mSourceLocale;
+ private ULocale mTargetLocale;
UiTranslationStateRemoteCallback(Executor executor,
UiTranslationStateCallback callback) {
@@ -331,10 +333,12 @@
int state = bundle.getInt(EXTRA_STATE);
switch (state) {
case STATE_UI_TRANSLATION_STARTED:
+ mSourceLocale = (ULocale) bundle.getSerializable(EXTRA_SOURCE_LOCALE);
+ mTargetLocale = (ULocale) bundle.getSerializable(EXTRA_TARGET_LOCALE);
+ mCallback.onStarted(mSourceLocale, mTargetLocale);
+ break;
case STATE_UI_TRANSLATION_RESUMED:
- mCallback.onStarted(
- (ULocale) bundle.getSerializable(EXTRA_SOURCE_LOCALE),
- (ULocale) bundle.getSerializable(EXTRA_TARGET_LOCALE));
+ mCallback.onStarted(mSourceLocale, mTargetLocale);
break;
case STATE_UI_TRANSLATION_PAUSED:
mCallback.onPaused();
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index eb16cef..b3f848b 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6614,27 +6614,6 @@
}
/**
- * Returns the {@link EdgeEffect#getType()} for the edge effects.
- * @return the {@link EdgeEffect#getType()} for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- @EdgeEffect.EdgeEffectType
- public int getEdgeEffectType() {
- return mEdgeGlowTop.getType();
- }
-
- /**
- * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
- * @param type The edge effect type to use for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
- mEdgeGlowTop.setType(type);
- mEdgeGlowBottom.setType(type);
- invalidate();
- }
-
- /**
* Sets the recycler listener to be notified whenever a View is set aside in
* the recycler for later reuse. This listener can be used to free resources
* associated to the View.
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 4d2d9e8..c11344e 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -62,9 +62,7 @@
*/
public class EdgeEffect {
/**
- * This sets the default value for {@link #setType(int)} to {@link #TYPE_STRETCH} instead
- * of {@link #TYPE_GLOW}. The type can still be overridden by the theme, view attribute,
- * or by calling {@link #setType(int)}.
+ * This sets the edge effect to use stretch instead of glow.
*
* @hide
*/
@@ -73,34 +71,19 @@
public static final long USE_STRETCH_EDGE_EFFECT_BY_DEFAULT = 171228096L;
/**
- * This sets the default value for {@link #setType(int)} to {@link #TYPE_STRETCH} instead
- * of {@link #TYPE_GLOW} for views that instantiate with
- * {@link #EdgeEffect(Context, AttributeSet)}, indicating use of S+ EdgeEffect support. The
- * type can still be overridden by the theme, view attribute, or by calling
- * {@link #setType(int)}.
- *
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
- public static final long USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED = 178807038L;
-
- /**
* The default blend mode used by {@link EdgeEffect}.
*/
public static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_ATOP;
/**
- * Use a color edge glow for the edge effect. From XML, use
- * <code>android:edgeEffectType="glow"</code>.
+ * Use a color edge glow for the edge effect.
*/
- public static final int TYPE_GLOW = 0;
+ private static final int TYPE_GLOW = 0;
/**
- * Use a stretch for the edge effect. From XML, use
- * <code>android:edgeEffectType="stretch"</code>.
+ * Use a stretch for the edge effect.
*/
- public static final int TYPE_STRETCH = 1;
+ private static final int TYPE_STRETCH = 1;
/**
* The velocity threshold before the spring animation is considered settled.
@@ -221,7 +204,7 @@
* @param context Context used to provide theming and resource information for the EdgeEffect
*/
public EdgeEffect(Context context) {
- this(context, null, Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT));
+ this(context, null);
}
/**
@@ -230,20 +213,12 @@
* @param attrs The attributes of the XML tag that is inflating the view
*/
public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
- this(context, attrs,
- Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
- || Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED));
- }
-
- private EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs,
- boolean defaultStretch) {
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.EdgeEffect);
final int themeColor = a.getColor(
com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
- mEdgeEffectType = a.getInt(
- com.android.internal.R.styleable.EdgeEffect_edgeEffectType,
- defaultStretch ? TYPE_STRETCH : TYPE_GLOW);
+ mEdgeEffectType = Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
+ ? TYPE_STRETCH : TYPE_GLOW;
a.recycle();
mPaint.setAntiAlias(true);
@@ -467,7 +442,6 @@
if (mEdgeEffectType == TYPE_STRETCH) {
mState = STATE_RECEDE;
mVelocity = velocity * ON_ABSORB_VELOCITY_ADJUSTMENT;
- mDistance = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
} else {
mState = STATE_ABSORB;
@@ -506,17 +480,6 @@
}
/**
- * Sets the edge effect type to use. The default without a theme attribute set is
- * {@link EdgeEffect#TYPE_GLOW}.
- *
- * @param type The edge effect type to use.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- public void setType(@EdgeEffectType int type) {
- mEdgeEffectType = type;
- }
-
- /**
* Set or clear the blend mode. A blend mode defines how source pixels
* (generated by a drawing command) are composited with the destination pixels
* (content of the render target).
@@ -542,16 +505,6 @@
}
/**
- * Return the edge effect type to use.
- *
- * @return The edge effect type to use.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- public @EdgeEffectType int getType() {
- return mEdgeEffectType;
- }
-
- /**
* Returns the blend mode. A blend mode defines how source pixels
* (generated by a drawing command) are composited with the destination pixels
* (content of the render target).
@@ -568,7 +521,7 @@
* Draw into the provided canvas. Assumes that the canvas has been rotated
* accordingly and the size has been set. The effect will be drawn the full
* width of X=0 to X=width, beginning from Y=0 and extending to some factor <
- * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a
+ * 1.f of height. The effect will only be visible on a
* hardware canvas, e.g. {@link RenderNode#beginRecording()}.
*
* @param canvas Canvas to draw into
@@ -686,7 +639,7 @@
* @return The maximum height of the edge effect
*/
public int getMaxHeight() {
- return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
+ return (int) mHeight;
}
private void update() {
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index e41893e..018cba7 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -308,27 +308,6 @@
}
/**
- * Returns the {@link EdgeEffect#getType()} for the edge effects.
- * @return the {@link EdgeEffect#getType()} for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- @EdgeEffect.EdgeEffectType
- public int getEdgeEffectType() {
- return mEdgeGlowLeft.getType();
- }
-
- /**
- * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
- * @param type The edge effect type to use for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
- mEdgeGlowRight.setType(type);
- mEdgeGlowLeft.setType(type);
- invalidate();
- }
-
- /**
* @return The maximum amount this scroll view will scroll in response to
* an arrow event.
*/
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 3610eb4..693b13b 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -340,27 +340,6 @@
}
/**
- * Returns the {@link EdgeEffect#getType()} for the edge effects.
- * @return the {@link EdgeEffect#getType()} for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- @EdgeEffect.EdgeEffectType
- public int getEdgeEffectType() {
- return mEdgeGlowTop.getType();
- }
-
- /**
- * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
- * @param type The edge effect type to use for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
- mEdgeGlowTop.setType(type);
- mEdgeGlowBottom.setType(type);
- invalidate();
- }
-
- /**
* @return The maximum amount this scroll view will scroll in response to
* an arrow event.
*/
@@ -368,7 +347,6 @@
return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
}
-
private void initScrollView() {
mScroller = new OverScroller(getContext());
setFocusable(true);
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index 8b2a2dc..537e797 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -22,6 +22,8 @@
import android.annotation.NonNull;
import android.graphics.Color;
+import com.android.internal.graphics.cam.Cam;
+
/**
* Copied from: frameworks/support/core-utils/java/android/support/v4/graphics/ColorUtils.java
*
@@ -333,6 +335,35 @@
}
/**
+ * Convert the ARGB color to a color appearance model.
+ *
+ * The color appearance model is based on CAM16 hue and chroma, using L*a*b*'s L* as the
+ * third dimension.
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored.
+ */
+ public static Cam colorToCAM(@ColorInt int color) {
+ return Cam.fromInt(color);
+ }
+
+ /**
+ * Convert a color appearance model representation to an ARGB color.
+ *
+ * Note: the returned color may have a lower chroma than requested. Whether a chroma is
+ * available depends on luminance. For example, there's no such thing as a high chroma light
+ * red, due to the limitations of our eyes and/or physics. If the requested chroma is
+ * unavailable, the highest possible chroma at the requested luminance is returned.
+ *
+ * @param hue hue, in degrees, in CAM coordinates
+ * @param chroma chroma in CAM coordinates.
+ * @param lstar perceptual luminance, L* in L*a*b*
+ */
+ @ColorInt
+ public static int CAMToColor(float hue, float chroma, float lstar) {
+ return Cam.getInt(hue, chroma, lstar);
+ }
+
+ /**
* Set the alpha component of {@code color} to be {@code alpha}.
*/
@ColorInt
diff --git a/core/java/com/android/internal/graphics/cam/Cam.java b/core/java/com/android/internal/graphics/cam/Cam.java
new file mode 100644
index 0000000..1ac5e50
--- /dev/null
+++ b/core/java/com/android/internal/graphics/cam/Cam.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.graphics.cam;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * A color appearance model, based on CAM16, extended to use L* as the lightness dimension, and
+ * coupled to a gamut mapping algorithm. Creates a color system, enables a digital design system.
+ */
+public class Cam {
+ // The maximum difference between the requested L* and the L* returned.
+ private static final float DL_MAX = 0.2f;
+ // The maximum color distance, in CAM16-UCS, between a requested color and the color returned.
+ private static final float DE_MAX = 1.0f;
+ // When the delta between the floor & ceiling of a binary search for chroma is less than this,
+ // the binary search terminates.
+ private static final float CHROMA_SEARCH_ENDPOINT = 0.4f;
+ // When the delta between the floor & ceiling of a binary search for J, lightness in CAM16,
+ // is less than this, the binary search terminates.
+ private static final float LIGHTNESS_SEARCH_ENDPOINT = 0.01f;
+
+ // CAM16 color dimensions, see getters for documentation.
+ private final float mHue;
+ private final float mChroma;
+ private final float mJ;
+ private final float mQ;
+ private final float mM;
+ private final float mS;
+
+ // Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
+ private final float mJstar;
+ private final float mAstar;
+ private final float mBstar;
+
+ /** Hue in CAM16 */
+ public float getHue() {
+ return mHue;
+ }
+
+ /** Chroma in CAM16 */
+ public float getChroma() {
+ return mChroma;
+ }
+
+ /** Lightness in CAM16 */
+ public float getJ() {
+ return mJ;
+ }
+
+ /**
+ * Brightness in CAM16.
+ *
+ * <p>Prefer lightness, brightness is an absolute quantity. For example, a sheet of white paper
+ * is much brighter viewed in sunlight than in indoor light, but it is the lightest object under
+ * any lighting.
+ */
+ public float getQ() {
+ return mQ;
+ }
+
+ /**
+ * Colorfulness in CAM16.
+ *
+ * <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much
+ * more colorful outside than inside, but it has the same chroma in both environments.
+ */
+ public float getM() {
+ return mM;
+ }
+
+ /**
+ * Saturation in CAM16.
+ *
+ * <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness
+ * relative to the color's own brightness, where chroma is colorfulness relative to white.
+ */
+ public float getS() {
+ return mS;
+ }
+
+ /** Lightness coordinate in CAM16-UCS */
+ public float getJstar() {
+ return mJstar;
+ }
+
+ /** a* coordinate in CAM16-UCS */
+ public float getAstar() {
+ return mAstar;
+ }
+
+ /** b* coordinate in CAM16-UCS */
+ public float getBstar() {
+ return mBstar;
+ }
+
+ /** Construct a CAM16 color */
+ Cam(float hue, float chroma, float j, float q, float m, float s, float jstar, float astar,
+ float bstar) {
+ mHue = hue;
+ mChroma = chroma;
+ mJ = j;
+ mQ = q;
+ mM = m;
+ mS = s;
+ mJstar = jstar;
+ mAstar = astar;
+ mBstar = bstar;
+ }
+
+ /**
+ * Given a hue & chroma in CAM16, L* in L*a*b*, return an ARGB integer. The chroma of the color
+ * returned may, and frequently will, be lower than requested. Assumes the color is viewed in
+ * the
+ * frame defined by the sRGB standard.
+ */
+ public static int getInt(float hue, float chroma, float lstar) {
+ return getInt(hue, chroma, lstar, Frame.DEFAULT);
+ }
+
+ /**
+ * Create a color appearance model from a ARGB integer representing a color. It is assumed the
+ * color was viewed in the frame defined in the sRGB standard.
+ */
+ @NonNull
+ public static Cam fromInt(int argb) {
+ return fromIntInFrame(argb, Frame.DEFAULT);
+ }
+
+ /**
+ * Create a color appearance model from a ARGB integer representing a color, specifying the
+ * frame in which the color was viewed. Prefer Cam.fromInt.
+ */
+ @NonNull
+ public static Cam fromIntInFrame(int argb, @NonNull Frame frame) {
+ // Transform ARGB int to XYZ
+ float[] xyz = CamUtils.xyzFromInt(argb);
+
+ // Transform XYZ to 'cone'/'rgb' responses
+ float[][] matrix = CamUtils.XYZ_TO_CAM16RGB;
+ float rT = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
+ float gT = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
+ float bT = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
+
+ // Discount illuminant
+ float rD = frame.getRgbD()[0] * rT;
+ float gD = frame.getRgbD()[1] * gT;
+ float bD = frame.getRgbD()[2] * bT;
+
+ // Chromatic adaptation
+ float rAF = (float) Math.pow(frame.getFl() * Math.abs(rD) / 100.0, 0.42);
+ float gAF = (float) Math.pow(frame.getFl() * Math.abs(gD) / 100.0, 0.42);
+ float bAF = (float) Math.pow(frame.getFl() * Math.abs(bD) / 100.0, 0.42);
+ float rA = Math.signum(rD) * 400.0f * rAF / (rAF + 27.13f);
+ float gA = Math.signum(gD) * 400.0f * gAF / (gAF + 27.13f);
+ float bA = Math.signum(bD) * 400.0f * bAF / (bAF + 27.13f);
+
+ // redness-greenness
+ float a = (float) (11.0 * rA + -12.0 * gA + bA) / 11.0f;
+ // yellowness-blueness
+ float b = (float) (rA + gA - 2.0 * bA) / 9.0f;
+
+ // auxiliary components
+ float u = (20.0f * rA + 20.0f * gA + 21.0f * bA) / 20.0f;
+ float p2 = (40.0f * rA + 20.0f * gA + bA) / 20.0f;
+
+ // hue
+ float atan2 = (float) Math.atan2(b, a);
+ float atanDegrees = atan2 * 180.0f / (float) Math.PI;
+ float hue =
+ atanDegrees < 0
+ ? atanDegrees + 360.0f
+ : atanDegrees >= 360 ? atanDegrees - 360.0f : atanDegrees;
+ float hueRadians = hue * (float) Math.PI / 180.0f;
+
+ // achromatic response to color
+ float ac = p2 * frame.getNbb();
+
+ // CAM16 lightness and brightness
+ float j = 100.0f * (float) Math.pow(ac / frame.getAw(), frame.getC() * frame.getZ());
+ float q =
+ 4.0f
+ / frame.getC()
+ * (float) Math.sqrt(j / 100.0f)
+ * (frame.getAw() + 4.0f)
+ * frame.getFlRoot();
+
+ // CAM16 chroma, colorfulness, and saturation.
+ float huePrime = (hue < 20.14) ? hue + 360 : hue;
+ float eHue = 0.25f * (float) (Math.cos(huePrime * Math.PI / 180.0 + 2.0) + 3.8);
+ float p1 = 50000.0f / 13.0f * eHue * frame.getNc() * frame.getNcb();
+ float t = p1 * (float) Math.sqrt(a * a + b * b) / (u + 0.305f);
+ float alpha =
+ (float) Math.pow(t, 0.9) * (float) Math.pow(1.64 - Math.pow(0.29, frame.getN()),
+ 0.73);
+ // CAM16 chroma, colorfulness, saturation
+ float c = alpha * (float) Math.sqrt(j / 100.0);
+ float m = c * frame.getFlRoot();
+ float s = 50.0f * (float) Math.sqrt((alpha * frame.getC()) / (frame.getAw() + 4.0f));
+
+ // CAM16-UCS components
+ float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
+ float mstar = 1.0f / 0.0228f * (float) Math.log(1.0f + 0.0228f * m);
+ float astar = mstar * (float) Math.cos(hueRadians);
+ float bstar = mstar * (float) Math.sin(hueRadians);
+
+ return new Cam(hue, c, j, q, m, s, jstar, astar, bstar);
+ }
+
+ /**
+ * Create a CAM from lightness, chroma, and hue coordinates. It is assumed those coordinates
+ * were measured in the sRGB standard frame.
+ */
+ @NonNull
+ private static Cam fromJch(float j, float c, float h) {
+ return fromJchInFrame(j, c, h, Frame.DEFAULT);
+ }
+
+ /**
+ * Create a CAM from lightness, chroma, and hue coordinates, and also specify the frame in which
+ * the color is being viewed.
+ */
+ @NonNull
+ private static Cam fromJchInFrame(float j, float c, float h, Frame frame) {
+ float q =
+ 4.0f
+ / frame.getC()
+ * (float) Math.sqrt(j / 100.0)
+ * (frame.getAw() + 4.0f)
+ * frame.getFlRoot();
+ float m = c * frame.getFlRoot();
+ float alpha = c / (float) Math.sqrt(j / 100.0);
+ float s = 50.0f * (float) Math.sqrt((alpha * frame.getC()) / (frame.getAw() + 4.0f));
+
+ float hueRadians = h * (float) Math.PI / 180.0f;
+ float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
+ float mstar = 1.0f / 0.0228f * (float) Math.log(1.0 + 0.0228 * m);
+ float astar = mstar * (float) Math.cos(hueRadians);
+ float bstar = mstar * (float) Math.sin(hueRadians);
+ return new Cam(h, c, j, q, m, s, jstar, astar, bstar);
+ }
+
+ /**
+ * Distance in CAM16-UCS space between two colors.
+ *
+ * <p>Much like L*a*b* was designed to measure distance between colors, the CAM16 standard
+ * defined a color space called CAM16-UCS to measure distance between CAM16 colors.
+ */
+ public float distance(@NonNull Cam other) {
+ float dJ = getJstar() - other.getJstar();
+ float dA = getAstar() - other.getAstar();
+ float dB = getBstar() - other.getBstar();
+ double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
+ double dE = 1.41 * Math.pow(dEPrime, 0.63);
+ return (float) dE;
+ }
+
+ /** Returns perceived color as an ARGB integer, as viewed in standard sRGB frame. */
+ public int viewedInSrgb() {
+ return viewed(Frame.DEFAULT);
+ }
+
+ /** Returns color perceived in a frame as an ARGB integer. */
+ public int viewed(@NonNull Frame frame) {
+ float alpha =
+ (getChroma() == 0.0 || getJ() == 0.0)
+ ? 0.0f
+ : getChroma() / (float) Math.sqrt(getJ() / 100.0);
+
+ float t =
+ (float) Math.pow(alpha / Math.pow(1.64 - Math.pow(0.29, frame.getN()), 0.73),
+ 1.0 / 0.9);
+ float hRad = getHue() * (float) Math.PI / 180.0f;
+
+ float eHue = 0.25f * (float) (Math.cos(hRad + 2.0) + 3.8);
+ float ac = frame.getAw() * (float) Math.pow(getJ() / 100.0,
+ 1.0 / frame.getC() / frame.getZ());
+ float p1 = eHue * (50000.0f / 13.0f) * frame.getNc() * frame.getNcb();
+ float p2 = (ac / frame.getNbb());
+
+ float hSin = (float) Math.sin(hRad);
+ float hCos = (float) Math.cos(hRad);
+
+ float gamma =
+ 23.0f * (p2 + 0.305f) * t / (23.0f * p1 + 11.0f * t * hCos + 108.0f * t * hSin);
+ float a = gamma * hCos;
+ float b = gamma * hSin;
+ float rA = (460.0f * p2 + 451.0f * a + 288.0f * b) / 1403.0f;
+ float gA = (460.0f * p2 - 891.0f * a - 261.0f * b) / 1403.0f;
+ float bA = (460.0f * p2 - 220.0f * a - 6300.0f * b) / 1403.0f;
+
+ float rCBase = (float) Math.max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
+ float rC = Math.signum(rA) * (100.0f / frame.getFl()) * (float) Math.pow(rCBase,
+ 1.0 / 0.42);
+ float gCBase = (float) Math.max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
+ float gC = Math.signum(gA) * (100.0f / frame.getFl()) * (float) Math.pow(gCBase,
+ 1.0 / 0.42);
+ float bCBase = (float) Math.max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
+ float bC = Math.signum(bA) * (100.0f / frame.getFl()) * (float) Math.pow(bCBase,
+ 1.0 / 0.42);
+ float rF = rC / frame.getRgbD()[0];
+ float gF = gC / frame.getRgbD()[1];
+ float bF = bC / frame.getRgbD()[2];
+
+
+ float[][] matrix = CamUtils.CAM16RGB_TO_XYZ;
+ float x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
+ float y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
+ float z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
+
+ int argb = ColorUtils.XYZToColor(x, y, z);
+ return argb;
+ }
+
+ /**
+ * Given a hue & chroma in CAM16, L* in L*a*b*, and the frame in which the color will be
+ * viewed,
+ * return an ARGB integer.
+ *
+ * <p>The chroma of the color returned may, and frequently will, be lower than requested. This
+ * is
+ * a fundamental property of color that cannot be worked around by engineering. For example, a
+ * red
+ * hue, with high chroma, and high L* does not exist: red hues have a maximum chroma below 10
+ * in
+ * light shades, creating pink.
+ */
+ public static int getInt(float hue, float chroma, float lstar, @NonNull Frame frame) {
+ // This is a crucial routine for building a color system, CAM16 itself is not sufficient.
+ //
+ // * Why these dimensions?
+ // Hue and chroma from CAM16 are used because they're the most accurate measures of those
+ // quantities. L* from L*a*b* is used because it correlates with luminance, luminance is
+ // used to measure contrast for a11y purposes, thus providing a key constraint on what
+ // colors
+ // can be used.
+ //
+ // * Why is this routine required to build a color system?
+ // In all perceptually accurate color spaces (i.e. L*a*b* and later), `chroma` may be
+ // impossible for a given `hue` and `lstar`.
+ // For example, a high chroma light red does not exist - chroma is limited to below 10 at
+ // light red shades, we call that pink. High chroma light green does exist, but not dark
+ // Also, when converting from another color space to RGB, the color may not be able to be
+ // represented in RGB. In those cases, the conversion process ends with RGB values
+ // outside 0-255
+ // The vast majority of color libraries surveyed simply round to 0 to 255. That is not an
+ // option for this library, as it distorts the expected luminance, and thus the expected
+ // contrast needed for a11y
+ //
+ // * What does this routine do?
+ // Dealing with colors in one color space not fitting inside RGB is, loosely referred to as
+ // gamut mapping or tone mapping. These algorithms are traditionally idiosyncratic, there is
+ // no universal answer. However, because the intent of this library is to build a system for
+ // digital design, and digital design uses luminance to measure contrast/a11y, we have one
+ // very important constraint that leads to an objective algorithm: the L* of the returned
+ // color _must_ match the requested L*.
+ //
+ // Intuitively, if the color must be distorted to fit into the RGB gamut, and the L*
+ // requested *must* be fulfilled, than the hue or chroma of the returned color will need
+ // to be different from the requested hue/chroma.
+ //
+ // After exploring both options, it was more intuitive that if the requested chroma could
+ // not be reached, it used the highest possible chroma. The alternative was finding the
+ // closest hue where the requested chroma could be reached, but that is not nearly as
+ // intuitive, as the requested hue is so fundamental to the color description.
+
+ // If the color doesn't have meaningful chroma, return a gray with the requested Lstar.
+ //
+ // Yellows are very chromatic at L = 100, and blues are very chromatic at L = 0. All the
+ // other hues are white at L = 100, and black at L = 0. To preserve consistency for users of
+ // this system, it is better to simply return white at L* > 99, and black and L* < 0.
+ if (chroma < 1.0 || Math.round(lstar) <= 0.0 || Math.round(lstar) >= 100.0) {
+ return CamUtils.intFromLstar(lstar);
+ }
+
+ hue = hue < 0 ? 0 : Math.min(360, hue);
+
+ // The highest chroma possible. Updated as binary search proceeds.
+ float high = chroma;
+
+ // The guess for the current binary search iteration. Starts off at the highest chroma,
+ // thus, if a color is possible at the requested chroma, the search can stop after one try.
+ float mid = chroma;
+ float low = 0.0f;
+ boolean isFirstLoop = true;
+
+ Cam answer = null;
+
+ while (Math.abs(low - high) >= CHROMA_SEARCH_ENDPOINT) {
+ // Given the current chroma guess, mid, and the desired hue, find J, lightness in
+ // CAM16 color space, that creates a color with L* = `lstar` in the L*a*b* color space.
+ Cam possibleAnswer = findCamByJ(hue, mid, lstar);
+
+ if (isFirstLoop) {
+ if (possibleAnswer != null) {
+ return possibleAnswer.viewed(frame);
+ } else {
+ // If this binary search iteration was the first iteration, and this point
+ // has been reached, it means the requested chroma was not available at the
+ // requested hue and L*.
+ // Proceed to a traditional binary search that starts at the midpoint between
+ // the requested chroma and 0.
+ isFirstLoop = false;
+ mid = low + (high - low) / 2.0f;
+ continue;
+ }
+ }
+
+ if (possibleAnswer == null) {
+ // There isn't a CAM16 J that creates a color with L* `lstar`. Try a lower chroma.
+ high = mid;
+ } else {
+ answer = possibleAnswer;
+ // It is possible to create a color. Try higher chroma.
+ low = mid;
+ }
+
+ mid = low + (high - low) / 2.0f;
+ }
+
+ // There was no answer: meaning, for the desired hue, there was no chroma low enough to
+ // generate a color with the desired L*.
+ // All values of L* are possible when there is 0 chroma. Return a color with 0 chroma, i.e.
+ // a shade of gray, with the desired L*.
+ if (answer == null) {
+ return CamUtils.intFromLstar(lstar);
+ }
+
+ return answer.viewed(frame);
+ }
+
+ // Find J, lightness in CAM16 color space, that creates a color with L* = `lstar` in the L*a*b*
+ // color space.
+ //
+ // Returns null if no J could be found that generated a color with L* `lstar`.
+ @Nullable
+ private static Cam findCamByJ(float hue, float chroma, float lstar) {
+ float low = 0.0f;
+ float high = 100.0f;
+ float mid = 0.0f;
+ float bestdL = 1000.0f;
+ float bestdE = 1000.0f;
+
+ Cam bestCam = null;
+ while (Math.abs(low - high) > LIGHTNESS_SEARCH_ENDPOINT) {
+ mid = low + (high - low) / 2;
+ // Create the intended CAM color
+ Cam camBeforeClip = Cam.fromJch(mid, chroma, hue);
+ // Convert the CAM color to RGB. If the color didn't fit in RGB, during the conversion,
+ // the initial RGB values will be outside 0 to 255. The final RGB values are clipped to
+ // 0 to 255, distorting the intended color.
+ int clipped = camBeforeClip.viewedInSrgb();
+ float clippedLstar = CamUtils.lstarFromInt(clipped);
+ float dL = Math.abs(lstar - clippedLstar);
+
+ // If the clipped color's L* is within error margin...
+ if (dL < DL_MAX) {
+ // ...check if the CAM equivalent of the clipped color is far away from intended CAM
+ // color. For the intended color, use lightness and chroma from the clipped color,
+ // and the intended hue. Callers are wondering what the lightness is, they know
+ // chroma may be distorted, so the only concern here is if the hue slipped too far.
+ Cam camClipped = Cam.fromInt(clipped);
+ float dE = camClipped.distance(
+ Cam.fromJch(camClipped.getJ(), camClipped.getChroma(), hue));
+ if (dE <= DE_MAX) {
+ bestdL = dL;
+ bestdE = dE;
+ bestCam = camClipped;
+ }
+ }
+
+ // If there's no error at all, there's no need to search more.
+ //
+ // Note: this happens much more frequently than expected, but this is a very delicate
+ // property which relies on extremely precise sRGB <=> XYZ calculations, as well as fine
+ // tuning of the constants that determine error margins and when the binary search can
+ // terminate.
+ if (bestdL == 0 && bestdE == 0) {
+ break;
+ }
+
+ if (clippedLstar < lstar) {
+ low = mid;
+ } else {
+ high = mid;
+ }
+ }
+
+ return bestCam;
+ }
+
+}
diff --git a/core/java/com/android/internal/graphics/cam/CamUtils.java b/core/java/com/android/internal/graphics/cam/CamUtils.java
new file mode 100644
index 0000000..13dafdb
--- /dev/null
+++ b/core/java/com/android/internal/graphics/cam/CamUtils.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.graphics.cam;
+
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * Collection of methods for transforming between color spaces.
+ *
+ * <p>Methods are named $xFrom$Y. For example, lstarFromInt() returns L* from an ARGB integer.
+ *
+ * <p>These methods, generally, convert colors between the L*a*b*, XYZ, and sRGB spaces.
+ *
+ * <p>L*a*b* is a perceptually accurate color space. This is particularly important in the L*
+ * dimension: it measures luminance and unlike lightness measures traditionally used in UI work via
+ * RGB or HSL, this luminance transitions smoothly, permitting creation of pleasing shades of a
+ * color, and more pleasing transitions between colors.
+ *
+ * <p>XYZ is commonly used as an intermediate color space for converting between one color space to
+ * another. For example, to convert RGB to L*a*b*, first RGB is converted to XYZ, then XYZ is
+ * convered to L*a*b*.
+ *
+ * <p>sRGB is a "specification originated from work in 1990s through cooperation by Hewlett-Packard
+ * and Microsoft, and it was designed to be a standard definition of RGB for the internet, which it
+ * indeed became...The standard is based on a sampling of computer monitors at the time...The whole
+ * idea of sRGB is that if everyone assumed that RGB meant the same thing, then the results would be
+ * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of
+ * Color Psychology, 2015
+ */
+public final class CamUtils {
+ private CamUtils() {
+ }
+
+ // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16.
+ static final float[][] XYZ_TO_CAM16RGB = {
+ {0.401288f, 0.650173f, -0.051461f},
+ {-0.250268f, 1.204414f, 0.045854f},
+ {-0.002079f, 0.048952f, 0.953127f}
+ };
+
+ // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
+ static final float[][] CAM16RGB_TO_XYZ = {
+ {1.86206786f, -1.01125463f, 0.14918677f},
+ {0.38752654f, 0.62144744f, -0.00897398f},
+ {-0.01584150f, -0.03412294f, 1.04996444f}
+ };
+
+ // Need this, XYZ coordinates in internal ColorUtils are private
+
+ // sRGB specification has D65 whitepoint - Stokes, Anderson, Chandrasekar, Motta - A Standard
+ // Default Color Space for the Internet: sRGB, 1996
+ static final float[] WHITE_POINT_D65 = {95.047f, 100.0f, 108.883f};
+
+ // This is a more precise sRGB to XYZ transformation matrix than traditionally
+ // used. It was derived using Schlomer's technique of transforming the xyY
+ // primaries to XYZ, then applying a correction to ensure mapping from sRGB
+ // 1, 1, 1 to the reference white point, D65.
+ static final float[][] SRGB_TO_XYZ = {
+ {0.41233895f, 0.35762064f, 0.18051042f},
+ {0.2126f, 0.7152f, 0.0722f},
+ {0.01932141f, 0.11916382f, 0.95034478f}
+ };
+
+ static int intFromLstar(float lstar) {
+ if (lstar < 1) {
+ return 0xff000000;
+ } else if (lstar > 99) {
+ return 0xffffffff;
+ }
+
+ // XYZ to LAB conversion routine, assume a and b are 0.
+ float fy = (lstar + 16.0f) / 116.0f;
+
+ // fz = fx = fy because a and b are 0
+ float fz = fy;
+ float fx = fy;
+
+ float kappa = 24389f / 27f;
+ float epsilon = 216f / 24389f;
+ boolean lExceedsEpsilonKappa = (lstar > 8.0f);
+ float yT = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa;
+ boolean cubeExceedEpsilon = (fy * fy * fy) > epsilon;
+ float xT = cubeExceedEpsilon ? fx * fx * fx : (116f * fx - 16f) / kappa;
+ float zT = cubeExceedEpsilon ? fz * fz * fz : (116f * fx - 16f) / kappa;
+
+ return ColorUtils.XYZToColor(xT * CamUtils.WHITE_POINT_D65[0],
+ yT * CamUtils.WHITE_POINT_D65[1], zT * CamUtils.WHITE_POINT_D65[2]);
+ }
+
+ /** Returns L* from L*a*b*, perceptual luminance, from an ARGB integer (ColorInt). */
+ public static float lstarFromInt(int argb) {
+ return lstarFromY(yFromInt(argb));
+ }
+
+ static float lstarFromY(float y) {
+ y = y / 100.0f;
+ final float e = 216.f / 24389.f;
+ float yIntermediate;
+ if (y <= e) {
+ return ((24389.f / 27.f) * y);
+ } else {
+ yIntermediate = (float) Math.cbrt(y);
+ }
+ return 116.f * yIntermediate - 16.f;
+ }
+
+ static float yFromInt(int argb) {
+ final float r = linearized(Color.red(argb));
+ final float g = linearized(Color.green(argb));
+ final float b = linearized(Color.blue(argb));
+ float[][] matrix = SRGB_TO_XYZ;
+ float y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]);
+ return y;
+ }
+
+ @NonNull
+ static float[] xyzFromInt(int argb) {
+ final float r = linearized(Color.red(argb));
+ final float g = linearized(Color.green(argb));
+ final float b = linearized(Color.blue(argb));
+
+ float[][] matrix = SRGB_TO_XYZ;
+ float x = (r * matrix[0][0]) + (g * matrix[0][1]) + (b * matrix[0][2]);
+ float y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]);
+ float z = (r * matrix[2][0]) + (g * matrix[2][1]) + (b * matrix[2][2]);
+ return new float[]{x, y, z};
+ }
+
+ static float yFromLstar(float lstar) {
+ float ke = 8.0f;
+ if (lstar > ke) {
+ return (float) Math.pow(((lstar + 16.0) / 116.0), 3) * 100f;
+ } else {
+ return lstar / (24389f / 27f) * 100f;
+ }
+ }
+
+ static float linearized(int rgbComponent) {
+ float normalized = (float) rgbComponent / 255.0f;
+
+ if (normalized <= 0.04045f) {
+ return (normalized / 12.92f) * 100.0f;
+ } else {
+ return (float) Math.pow(((normalized + 0.055f) / 1.055f), 2.4f) * 100.0f;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/graphics/cam/Frame.java b/core/java/com/android/internal/graphics/cam/Frame.java
new file mode 100644
index 0000000..c422ad1
--- /dev/null
+++ b/core/java/com/android/internal/graphics/cam/Frame.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.graphics.cam;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+/**
+ * The frame, or viewing conditions, where a color was seen. Used, along with a color, to create a
+ * color appearance model representing the color.
+ *
+ * <p>To convert a traditional color to a color appearance model, it requires knowing what
+ * conditions the color was observed in. Our perception of color depends on, for example, the tone
+ * of the light illuminating the color, how bright that light was, etc.
+ *
+ * <p>This class is modelled separately from the color appearance model itself because there are a
+ * number of calculations during the color => CAM conversion process that depend only on the viewing
+ * conditions. Caching those calculations in a Frame instance saves a significant amount of time.
+ */
+public final class Frame {
+ // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar,
+ // Motta - A Standard Default Color Space for the Internet: sRGB, 1996.
+ //
+ // White point = D65
+ // Luminance of adapting field: 200 / Pi / 5, units are cd/m^2.
+ // sRGB ambient illuminance = 64 lux (per sRGB spec). However, the spec notes this is
+ // artificially low and based on monitors in 1990s. Use 200, the sRGB spec says this is the
+ // real average, and a survey of lux values on Wikipedia confirms this is a comfortable
+ // default: somewhere between a very dark overcast day and office lighting.
+ // Per CAM16 introduction paper (Li et al, 2017) Ew = pi * lw, and La = lw * Yb/Yw
+ // Ew = ambient environment luminance, in lux.
+ // Yb/Yw is taken to be midgray, ~20% relative luminance (XYZ Y 18.4, CIELAB L* 50).
+ // Therefore La = (Ew / pi) * .184
+ // La = 200 / pi * .184
+ // Image surround to 10 degrees = ~20% relative luminance = CIELAB L* 50
+ //
+ // Not from sRGB standard:
+ // Surround = average, 2.0.
+ // Discounting illuminant = false, doesn't occur for self-luminous displays
+ public static final Frame DEFAULT =
+ Frame.make(
+ CamUtils.WHITE_POINT_D65,
+ (float) (200.0f / Math.PI * CamUtils.yFromLstar(50.0f) / 100.f), 50.0f, 2.0f,
+ false);
+
+ private final float mAw;
+ private final float mNbb;
+ private final float mNcb;
+ private final float mC;
+ private final float mNc;
+ private final float mN;
+ private final float[] mRgbD;
+ private final float mFl;
+ private final float mFlRoot;
+ private final float mZ;
+
+ float getAw() {
+ return mAw;
+ }
+
+ float getN() {
+ return mN;
+ }
+
+ float getNbb() {
+ return mNbb;
+ }
+
+ float getNcb() {
+ return mNcb;
+ }
+
+ float getC() {
+ return mC;
+ }
+
+ float getNc() {
+ return mNc;
+ }
+
+ @NonNull
+ float[] getRgbD() {
+ return mRgbD;
+ }
+
+ float getFl() {
+ return mFl;
+ }
+
+ float getFlRoot() {
+ return mFlRoot;
+ }
+
+ float getZ() {
+ return mZ;
+ }
+
+ private Frame(float n, float aw, float nbb, float ncb, float c, float nc, float[] rgbD,
+ float fl, float fLRoot, float z) {
+ mN = n;
+ mAw = aw;
+ mNbb = nbb;
+ mNcb = ncb;
+ mC = c;
+ mNc = nc;
+ mRgbD = rgbD;
+ mFl = fl;
+ mFlRoot = fLRoot;
+ mZ = z;
+ }
+
+ /** Create a custom frame. */
+ @NonNull
+ public static Frame make(@NonNull float[] whitepoint, float adaptingLuminance,
+ float backgroundLstar, float surround, boolean discountingIlluminant) {
+ // Transform white point XYZ to 'cone'/'rgb' responses
+ float[][] matrix = CamUtils.XYZ_TO_CAM16RGB;
+ float[] xyz = whitepoint;
+ float rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
+ float gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
+ float bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
+
+ // Scale input surround, domain (0, 2), to CAM16 surround, domain (0.8, 1.0)
+ float f = 0.8f + (surround / 10.0f);
+ // "Exponential non-linearity"
+ float c = (f >= 0.9) ? MathUtils.lerp(0.59f, 0.69f, ((f - 0.9f) * 10.0f)) : MathUtils.lerp(
+ 0.525f, 0.59f, ((f - 0.8f) * 10.0f));
+ // Calculate degree of adaptation to illuminant
+ float d = discountingIlluminant ? 1.0f : f * (1.0f - ((1.0f / 3.6f) * (float) Math.exp(
+ (-adaptingLuminance - 42.0f) / 92.0f)));
+ // Per Li et al, if D is greater than 1 or less than 0, set it to 1 or 0.
+ d = (d > 1.0) ? 1.0f : (d < 0.0) ? 0.0f : d;
+ // Chromatic induction factor
+ float nc = f;
+
+ // Cone responses to the whitepoint, adjusted for illuminant discounting.
+ //
+ // Why use 100.0 instead of the white point's relative luminance?
+ //
+ // Some papers and implementations, for both CAM02 and CAM16, use the Y
+ // value of the reference white instead of 100. Fairchild's Color Appearance
+ // Models (3rd edition) notes that this is in error: it was included in the
+ // CIE 2004a report on CIECAM02, but, later parts of the conversion process
+ // account for scaling of appearance relative to the white point relative
+ // luminance. This part should simply use 100 as luminance.
+ float[] rgbD = new float[]{d * (100.0f / rW) + 1.0f - d, d * (100.0f / gW) + 1.0f - d,
+ d * (100.0f / bW) + 1.0f - d, };
+ // Luminance-level adaptation factor
+ float k = 1.0f / (5.0f * adaptingLuminance + 1.0f);
+ float k4 = k * k * k * k;
+ float k4F = 1.0f - k4;
+ float fl = (k4 * adaptingLuminance) + (0.1f * k4F * k4F * (float) Math.cbrt(
+ 5.0 * adaptingLuminance));
+
+ // Intermediate factor, ratio of background relative luminance to white relative luminance
+ float n = CamUtils.yFromLstar(backgroundLstar) / whitepoint[1];
+
+ // Base exponential nonlinearity
+ // note Schlomer 2018 has a typo and uses 1.58, the correct factor is 1.48
+ float z = 1.48f + (float) Math.sqrt(n);
+
+ // Luminance-level induction factors
+ float nbb = 0.725f / (float) Math.pow(n, 0.2);
+ float ncb = nbb;
+
+ // Discounted cone responses to the white point, adjusted for post-chromatic
+ // adaptation perceptual nonlinearities.
+ float[] rgbAFactors = new float[]{(float) Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
+ (float) Math.pow(fl * rgbD[1] * gW / 100.0, 0.42), (float) Math.pow(
+ fl * rgbD[2] * bW / 100.0, 0.42)};
+
+ float[] rgbA = new float[]{(400.0f * rgbAFactors[0]) / (rgbAFactors[0] + 27.13f),
+ (400.0f * rgbAFactors[1]) / (rgbAFactors[1] + 27.13f),
+ (400.0f * rgbAFactors[2]) / (rgbAFactors[2] + 27.13f), };
+
+ float aw = ((2.0f * rgbA[0]) + rgbA[1] + (0.05f * rgbA[2])) * nbb;
+
+ return new Frame(n, aw, nbb, ncb, c, nc, rgbD, fl, (float) Math.pow(fl, 0.25), z);
+ }
+}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 73d962e..0307268 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -20,7 +20,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.SparseArray;
@@ -50,10 +49,11 @@
BatteryStats.STATS_SINCE_CHARGED);
final double powerMah = getMeasuredOrEstimatedPower(powerModel,
measuredEnergyUC, mPowerEstimator, durationMs);
- builder.getOrCreateSystemBatteryConsumerBuilder(
- SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah, powerModel)
- .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, durationMs);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ powerMah, powerModel);
}
/**
diff --git a/core/java/com/android/internal/os/AudioPowerCalculator.java b/core/java/com/android/internal/os/AudioPowerCalculator.java
index 9da8191..2eab506 100644
--- a/core/java/com/android/internal/os/AudioPowerCalculator.java
+++ b/core/java/com/android/internal/os/AudioPowerCalculator.java
@@ -17,8 +17,10 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
/**
* A {@link PowerCalculator} to calculate power consumed by audio hardware.
@@ -31,18 +33,47 @@
// TODO(b/175344313): improve the model by taking into account different audio routes
private final UsageBasedPowerEstimator mPowerEstimator;
+ private static class PowerAndDuration {
+ public long durationMs;
+ public double powerMah;
+ }
+
public AudioPowerCalculator(PowerProfile powerProfile) {
mPowerEstimator = new UsageBasedPowerEstimator(
powerProfile.getAveragePower(PowerProfile.POWER_AUDIO));
}
@Override
- protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final PowerAndDuration total = new PowerAndDuration();
+
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ calculateApp(app, total, app.getBatteryStatsUid(), rawRealtimeUs);
+ }
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, total.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, total.powerMah);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, total.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, total.powerMah);
+ }
+
+ private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
+ BatteryStats.Uid u, long rawRealtimeUs) {
final long durationMs = mPowerEstimator.calculateDuration(u.getAudioTurnedOnTimer(),
rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
final double powerMah = mPowerEstimator.calculatePower(durationMs);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, powerMah);
+ total.durationMs += durationMs;
+ total.powerMah += powerMah;
}
}
diff --git a/core/java/com/android/internal/os/BatteryChargeCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java
index dc72f32..16f92ef 100644
--- a/core/java/com/android/internal/os/BatteryChargeCalculator.java
+++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java
@@ -28,20 +28,28 @@
* Estimates the battery discharge amounts.
*/
public class BatteryChargeCalculator extends PowerCalculator {
- private final double mBatteryCapacity;
-
- public BatteryChargeCalculator(PowerProfile powerProfile) {
- mBatteryCapacity = powerProfile.getBatteryCapacity();
- }
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
builder.setDischargePercentage(
- batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED))
- .setDischargedPowerRange(
- batteryStats.getLowDischargeAmountSinceCharge() * mBatteryCapacity / 100,
- batteryStats.getHighDischargeAmountSinceCharge() * mBatteryCapacity / 100);
+ batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED));
+
+ int batteryCapacityMah = batteryStats.getLearnedBatteryCapacity() / 1000;
+ if (batteryCapacityMah <= 0) {
+ batteryCapacityMah = batteryStats.getMinLearnedBatteryCapacity() / 1000;
+ if (batteryCapacityMah <= 0) {
+ batteryCapacityMah = batteryStats.getEstimatedBatteryCapacity();
+ }
+ }
+ final double dischargedPowerLowerBoundMah =
+ batteryStats.getLowDischargeAmountSinceCharge() * batteryCapacityMah / 100.0;
+ final double dischargedPowerUpperBoundMah =
+ batteryStats.getHighDischargeAmountSinceCharge() * batteryCapacityMah / 100.0;
+ builder.setDischargePercentage(
+ batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED))
+ .setDischargedPowerRange(dischargedPowerLowerBoundMah,
+ dischargedPowerUpperBoundMah);
final long batteryTimeRemainingMs = batteryStats.computeBatteryTimeRemaining(rawRealtimeUs);
if (batteryTimeRemainingMs != -1) {
@@ -52,6 +60,11 @@
if (chargeTimeRemainingMs != -1) {
builder.setChargeTimeRemainingMs(chargeTimeRemainingMs / 1000);
}
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(
+ (dischargedPowerLowerBoundMah + dischargedPowerUpperBoundMah) / 2);
}
@Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 02a29085..db67bab 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -161,7 +161,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 198;
+ static final int VERSION = 199;
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -1056,6 +1056,7 @@
private int mBatteryVoltageMv = -1;
private int mEstimatedBatteryCapacityMah = -1;
+ private int mLastLearnedBatteryCapacityUah = -1;
private int mMinLearnedBatteryCapacityUah = -1;
private int mMaxLearnedBatteryCapacityUah = -1;
@@ -1142,6 +1143,11 @@
}
@Override
+ public int getLearnedBatteryCapacity() {
+ return mLastLearnedBatteryCapacityUah;
+ }
+
+ @Override
public int getMinLearnedBatteryCapacity() {
return mMinLearnedBatteryCapacityUah;
}
@@ -11199,6 +11205,7 @@
} else {
mEstimatedBatteryCapacityMah = -1;
}
+ mLastLearnedBatteryCapacityUah = -1;
mMinLearnedBatteryCapacityUah = -1;
mMaxLearnedBatteryCapacityUah = -1;
mInteractiveTimer.reset(false, elapsedRealtimeUs);
@@ -13799,6 +13806,7 @@
mRecordingHistory = DEBUG;
}
+ mLastLearnedBatteryCapacityUah = chargeFullUah;
if (mMinLearnedBatteryCapacityUah == -1) {
mMinLearnedBatteryCapacityUah = chargeFullUah;
} else {
@@ -15066,6 +15074,7 @@
mDischargeCurrentLevel = in.readInt();
mCurrentBatteryLevel = in.readInt();
mEstimatedBatteryCapacityMah = in.readInt();
+ mLastLearnedBatteryCapacityUah = in.readInt();
mMinLearnedBatteryCapacityUah = in.readInt();
mMaxLearnedBatteryCapacityUah = in.readInt();
mLowDischargeAmountSinceCharge = in.readInt();
@@ -15570,6 +15579,7 @@
out.writeInt(mDischargeCurrentLevel);
out.writeInt(mCurrentBatteryLevel);
out.writeInt(mEstimatedBatteryCapacityMah);
+ out.writeInt(mLastLearnedBatteryCapacityUah);
out.writeInt(mMinLearnedBatteryCapacityUah);
out.writeInt(mMaxLearnedBatteryCapacityUah);
out.writeInt(getLowDischargeAmountSinceCharge());
@@ -16071,6 +16081,7 @@
mRealtimeStartUs = in.readLong();
mOnBattery = in.readInt() != 0;
mEstimatedBatteryCapacityMah = in.readInt();
+ mLastLearnedBatteryCapacityUah = in.readInt();
mMinLearnedBatteryCapacityUah = in.readInt();
mMaxLearnedBatteryCapacityUah = in.readInt();
mOnBatteryInternal = false; // we are no longer really running.
@@ -16310,6 +16321,7 @@
out.writeLong(mRealtimeStartUs);
out.writeInt(mOnBattery ? 1 : 0);
out.writeInt(mEstimatedBatteryCapacityMah);
+ out.writeInt(mLastLearnedBatteryCapacityUah);
out.writeInt(mMinLearnedBatteryCapacityUah);
out.writeInt(mMaxLearnedBatteryCapacityUah);
mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index babcea1..4989559 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -54,7 +54,7 @@
mPowerCalculators = new ArrayList<>();
// Power calculators are applied in the order of registration
- mPowerCalculators.add(new BatteryChargeCalculator(mPowerProfile));
+ mPowerCalculators.add(new BatteryChargeCalculator());
mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 2c32e48..6e99bbb 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -21,7 +21,6 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
@@ -58,19 +57,11 @@
final PowerAndDuration total = new PowerAndDuration();
- SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
- builder.getOrCreateSystemBatteryConsumerBuilder(
- SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH);
-
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
calculateApp(app, total, query);
- if (app.getUid() == Process.BLUETOOTH_UID) {
- app.excludeFromBatteryUsageStats();
- systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
- }
}
final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
@@ -87,12 +78,18 @@
Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
+ " power=" + formatCharge(systemPowerMah));
}
- systemBatteryConsumerBuilder
- .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
- systemComponentDurationMs)
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
- Math.max(systemPowerMah, total.powerMah), powerModel)
- .setPowerConsumedByApps(total.powerMah);
+ Math.max(systemPowerMah, total.powerMah), powerModel);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah,
+ powerModel);
}
private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java
index e56e7be..ddcabe8 100644
--- a/core/java/com/android/internal/os/CameraPowerCalculator.java
+++ b/core/java/com/android/internal/os/CameraPowerCalculator.java
@@ -17,6 +17,7 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
@@ -36,6 +37,24 @@
}
@Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
+
+ final long durationMs = batteryStats.getCameraOnTime(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+ }
+
+ @Override
protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long durationMs =
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 2a55aa9..0d041c4 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -80,13 +80,28 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ double totalPowerMah = 0;
+
Result result = new Result();
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
calculateApp(app, app.getBatteryStatsUid(), query, result);
+ totalPowerMah += result.powerMah;
}
+
+ final long consumptionUC = batteryStats.getCpuMeasuredBatteryConsumptionUC();
+ final int powerModel = getPowerModel(consumptionUC, query);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, totalPowerMah);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU,
+ powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY
+ ? uCtoMah(consumptionUC) : totalPowerMah, powerModel);
}
private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 9941e30..9b51a8e 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -15,12 +15,13 @@
*/
package com.android.internal.os;
+import android.os.AggregateBatteryConsumer;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
/**
* Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
@@ -33,33 +34,62 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
+ double[] totalAppPowerMah = null;
+
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ totalAppPowerMah = calculateApp(app, app.getBatteryStatsUid(), totalAppPowerMah);
+ }
+
final double[] customMeasuredPowerMah = calculateMeasuredEnergiesMah(
batteryStats.getCustomConsumerMeasuredBatteryConsumptionUC());
if (customMeasuredPowerMah != null) {
- final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
- builder.getOrCreateSystemBatteryConsumerBuilder(
- SystemBatteryConsumer.DRAIN_TYPE_CUSTOM);
+ final AggregateBatteryConsumer.Builder deviceBatteryConsumerBuilder =
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
for (int i = 0; i < customMeasuredPowerMah.length; i++) {
- systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
+ deviceBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
customMeasuredPowerMah[i]);
}
}
+ if (totalAppPowerMah != null) {
+ final AggregateBatteryConsumer.Builder appsBatteryConsumerBuilder =
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+ for (int i = 0; i < totalAppPowerMah.length; i++) {
+ appsBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
+ totalAppPowerMah[i]);
+ }
+ }
}
- @Override
- protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
- long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ private double[] calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ double[] totalPowerMah) {
+ double[] newTotalPowerMah = null;
final double[] customMeasuredPowerMah = calculateMeasuredEnergiesMah(
u.getCustomConsumerMeasuredBatteryConsumptionUC());
if (customMeasuredPowerMah != null) {
+ if (totalPowerMah == null) {
+ newTotalPowerMah = new double[customMeasuredPowerMah.length];
+ } else if (totalPowerMah.length != customMeasuredPowerMah.length) {
+ newTotalPowerMah = new double[customMeasuredPowerMah.length];
+ System.arraycopy(totalPowerMah, 0, newTotalPowerMah, 0,
+ customMeasuredPowerMah.length);
+ } else {
+ newTotalPowerMah = totalPowerMah;
+ }
for (int i = 0; i < customMeasuredPowerMah.length; i++) {
app.setConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
customMeasuredPowerMah[i]);
+ newTotalPowerMah[i] += customMeasuredPowerMah[i];
}
}
+ return newTotalPowerMah;
}
@Override
diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
index cbe0cde..32df17c 100644
--- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java
+++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
@@ -17,6 +17,7 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
@@ -34,6 +35,24 @@
}
@Override
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+ long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
+
+ final long durationMs = batteryStats.getFlashlightOnTime(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ final double powerMah = mPowerEstimator.calculatePower(durationMs);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah);
+ }
+
+ @Override
protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final long durationMs = mPowerEstimator.calculateDuration(u.getFlashlightTurnedOnTimer(),
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index 7eb4b4a..a508e03 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -46,6 +46,7 @@
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ double appsPowerMah = 0;
final double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
@@ -55,12 +56,27 @@
final long consumptionUC =
app.getBatteryStatsUid().getGnssMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
- calculateApp(app, app.getBatteryStatsUid(), powerModel, rawRealtimeUs,
- averageGnssPowerMa, consumptionUC);
+ appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), powerModel,
+ rawRealtimeUs, averageGnssPowerMa, consumptionUC);
}
+
+ final long consumptionUC = batteryStats.getGnssMeasuredBatteryConsumptionUC();
+ final int powerModel = getPowerModel(consumptionUC, query);
+ double powerMah;
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ powerMah = uCtoMah(consumptionUC);
+ } else {
+ powerMah = appsPowerMah;
+ }
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah, powerModel);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, appsPowerMah, powerModel);
}
- private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ private double calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
@BatteryConsumer.PowerModel int powerModel, long rawRealtimeUs,
double averageGnssPowerMa, long measuredChargeUC) {
final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
@@ -76,6 +92,7 @@
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_GNSS, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah, powerModel);
+ return powerMah;
}
@Override
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index 0c80deb..d33a88d 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -20,7 +20,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -53,7 +52,8 @@
calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
BatteryStats.STATS_SINCE_CHARGED);
if (mPowerMah != 0) {
- builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_IDLE)
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE, mPowerMah)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE, mDurationMs);
}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index 64c68ce..e670178 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -482,7 +482,8 @@
// Unit is 10ms.
mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
if (mDeltaTimes[i] < 0) {
- Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
+ Slog.e(mTag, "Negative delta from freq time for uid: " + uid
+ + ", delta: " + mDeltaTimes[i]);
valid = false;
}
notify |= mDeltaTimes[i] > 0;
@@ -648,7 +649,8 @@
cb.onUidCpuTime(uid, delta);
}
} else if (delta < 0) {
- Slog.e(mTag, "Negative delta from active time proc: " + delta);
+ Slog.e(mTag, "Negative delta from active time for uid: " + uid
+ + ", delta: " + delta);
}
}
}
@@ -822,7 +824,8 @@
for (int i = 0; i < mNumClusters; i++) {
mDeltaTime[i] = mCurTime[i] - lastTimes[i];
if (mDeltaTime[i] < 0) {
- Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
+ Slog.e(mTag, "Negative delta from cluster time for uid: " + uid
+ + ", delta: " + mDeltaTime[i]);
valid = false;
}
notify |= mDeltaTime[i] > 0;
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 5d5c155..09fd85e 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -4,7 +4,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.LongSparseArray;
import android.util.SparseArray;
@@ -31,7 +30,8 @@
BatteryStats.STATS_SINCE_CHARGED);
final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
- builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_MEMORY)
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MEMORY, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY, powerMah);
}
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 4db15a4..eb5993d 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -19,7 +19,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.telephony.CellSignalStrength;
@@ -105,14 +104,19 @@
calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
- builder.getOrCreateSystemBatteryConsumerBuilder(
- SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO)
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- total.remainingPowerMah + total.totalAppPowerMah,
- powerModel)
- .setPowerConsumedByApps(total.totalAppPowerMah);
+ total.remainingPowerMah + total.totalAppPowerMah, powerModel);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ total.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ total.totalAppPowerMah, powerModel);
}
}
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 2e3bff3..8dd463c 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -20,7 +20,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UserHandle;
import android.util.SparseArray;
@@ -44,7 +43,8 @@
BatteryStats.STATS_SINCE_CHARGED) / 1000;
final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
if (phoneOnPower != 0) {
- builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_PHONE)
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
}
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index dc0f719..1b3bc23 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -20,7 +20,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateUtils;
@@ -68,6 +67,7 @@
rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, consumptionUC);
double totalAppPower = 0;
+ long totalAppDuration = 0;
// Now deal with each app's UidBatteryConsumer. The results are stored in the
// BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
@@ -86,6 +86,7 @@
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
appPowerAndDuration.powerMah, powerModel);
totalAppPower += appPowerAndDuration.powerMah;
+ totalAppDuration += appPowerAndDuration.durationMs;
}
break;
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
@@ -93,14 +94,20 @@
smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
rawRealtimeUs);
totalAppPower = totalPowerAndDuration.powerMah;
+ totalAppDuration = totalPowerAndDuration.durationMs;
}
- builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
- .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
- totalPowerAndDuration.durationMs)
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
Math.max(totalPowerAndDuration.powerMah, totalAppPower), powerModel)
- .setPowerConsumedByApps(totalAppPower);
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
+ totalPowerAndDuration.durationMs);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppPower, powerModel)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppDuration);
}
/**
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
index d18b7b1..83e5b57 100644
--- a/core/java/com/android/internal/os/SensorPowerCalculator.java
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -19,6 +19,7 @@
import android.hardware.SensorManager;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.util.SparseArray;
@@ -38,12 +39,32 @@
}
@Override
- protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ double appsPowerMah = 0;
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), rawRealtimeUs);
+ }
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, appsPowerMah);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, appsPowerMah);
+ }
+
+ private double calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ long rawRealtimeUs) {
+ final double powerMah = calculatePowerMah(u, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SENSORS,
calculateDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED))
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS,
- calculatePowerMah(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED));
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, powerMah);
+ return powerMah;
}
@Override
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index b4d5f97..a26abc2 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -87,6 +87,15 @@
systemServicePowerMah * uid.getProportionalSystemServiceUsage());
}
}
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+ systemServicePowerMah);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+ systemServicePowerMah);
}
@Override
diff --git a/core/java/com/android/internal/os/VideoPowerCalculator.java b/core/java/com/android/internal/os/VideoPowerCalculator.java
index 0cad9a7..47916a6 100644
--- a/core/java/com/android/internal/os/VideoPowerCalculator.java
+++ b/core/java/com/android/internal/os/VideoPowerCalculator.java
@@ -17,8 +17,10 @@
import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
/**
* A {@link PowerCalculator} to calculate power consumed by video hardware.
@@ -28,18 +30,47 @@
public class VideoPowerCalculator extends PowerCalculator {
private final UsageBasedPowerEstimator mPowerEstimator;
+ private static class PowerAndDuration {
+ public long durationMs;
+ public double powerMah;
+ }
+
public VideoPowerCalculator(PowerProfile powerProfile) {
mPowerEstimator = new UsageBasedPowerEstimator(
powerProfile.getAveragePower(PowerProfile.POWER_VIDEO));
}
@Override
- protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+ public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+ final PowerAndDuration total = new PowerAndDuration();
+
+ final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+ builder.getUidBatteryConsumerBuilders();
+ for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+ final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+ calculateApp(app, total, app.getBatteryStatsUid(), rawRealtimeUs);
+ }
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, total.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, total.powerMah);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, total.durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, total.powerMah);
+ }
+
+ private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
+ BatteryStats.Uid u, long rawRealtimeUs) {
final long durationMs = mPowerEstimator.calculateDuration(u.getVideoTurnedOnTimer(),
rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
final double powerMah = mPowerEstimator.calculatePower(durationMs);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, powerMah);
+ total.durationMs += durationMs;
+ total.powerMah += powerMah;
}
}
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index 194b6b8..d594107 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -51,6 +51,7 @@
double osPowerMah = 0;
long osDurationMs = 0;
long totalAppDurationMs = 0;
+ double appPowerMah = 0;
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
@@ -60,6 +61,7 @@
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
totalAppDurationMs += result.durationMs;
+ appPowerMah += result.powerMah;
if (app.getUid() == Process.ROOT_UID) {
osBatteryConsumer = app;
@@ -71,13 +73,24 @@
// The device has probably been awake for longer than the screen on
// time and application wake lock time would account for. Assign
// this remainder to the OS, if possible.
+ calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
+ BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
if (osBatteryConsumer != null) {
- calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
- BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
result.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
}
+
+ final long wakeTimeMillis =
+ calculateWakeTimeMillis(batteryStats, rawRealtimeUs, rawUptimeUs);
+ final double powerMah = mPowerEstimator.calculatePower(wakeTimeMillis);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK, wakeTimeMillis)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, powerMah);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, appPowerMah);
}
@Override
@@ -145,8 +158,7 @@
private void calculateRemaining(PowerAndDuration result, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType, double osPowerMah, long osDurationMs,
long totalAppDurationMs) {
- final long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000
- - stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000
+ final long wakeTimeMillis = calculateWakeTimeMillis(stats, rawRealtimeUs, rawUptimeUs)
- totalAppDurationMs;
if (wakeTimeMillis > 0) {
final double power = mPowerEstimator.calculatePower(wakeTimeMillis);
@@ -157,4 +169,12 @@
result.powerMah = osPowerMah + power;
}
}
+
+ private long calculateWakeTimeMillis(BatteryStats batteryStats, long rawRealtimeUs,
+ long rawUptimeUs) {
+ final long batteryUptimeUs = batteryStats.getBatteryUptime(rawUptimeUs);
+ final long screenOnTimeUs =
+ batteryStats.getScreenOnTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+ return (batteryUptimeUs - screenOnTimeUs) / 1000;
+ }
}
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index ef5b147..776a705 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -20,7 +20,6 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.Log;
@@ -79,10 +78,6 @@
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
- final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
- builder.getOrCreateSystemBatteryConsumerBuilder(
- SystemBatteryConsumer.DRAIN_TYPE_WIFI);
-
long totalAppDurationMs = 0;
double totalAppPowerMah = 0;
final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic();
@@ -104,11 +99,6 @@
powerDurationAndTraffic.durationMs);
app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
powerDurationAndTraffic.powerMah, powerModel);
-
- if (app.getUid() == Process.WIFI_UID) {
- systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
- app.excludeFromBatteryUsageStats();
- }
}
final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC();
@@ -117,12 +107,16 @@
BatteryStats.STATS_SINCE_CHARGED, batteryStats.hasWifiActivityReporting(),
totalAppDurationMs, totalAppPowerMah, consumptionUC);
- systemBatteryConsumerBuilder
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
powerDurationAndTraffic.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
- totalAppPowerMah + powerDurationAndTraffic.powerMah, powerModel)
- .setPowerConsumedByApps(totalAppPowerMah);
+ totalAppPowerMah + powerDurationAndTraffic.powerMah, powerModel);
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
+ totalAppPowerMah, powerModel);
}
/**
diff --git a/core/java/com/android/internal/view/IDragAndDropPermissions.aidl b/core/java/com/android/internal/view/IDragAndDropPermissions.aidl
index edb759a..4834d22 100644
--- a/core/java/com/android/internal/view/IDragAndDropPermissions.aidl
+++ b/core/java/com/android/internal/view/IDragAndDropPermissions.aidl
@@ -24,6 +24,6 @@
*/
interface IDragAndDropPermissions {
void take(IBinder activityToken);
- void takeTransient(IBinder transientToken);
+ void takeTransient();
void release();
}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 9ed5eb1..be15a9b 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -16,16 +16,10 @@
package com.android.internal.widget;
-import static android.widget.EdgeEffect.TYPE_GLOW;
-import static android.widget.EdgeEffect.TYPE_STRETCH;
-import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_BY_DEFAULT;
-import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED;
-
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -466,8 +460,6 @@
private final int[] mScrollConsumed = new int[2];
private final int[] mNestedOffsets = new int[2];
- private int mEdgeEffectType;
-
/**
* These are views that had their a11y importance changed during a layout. We defer these events
* until the end of the layout because a11y service may make sync calls back to the RV while
@@ -595,12 +587,8 @@
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
- boolean defaultToStretch = Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
- || Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.EdgeEffect);
- mEdgeEffectType = a.getInt(com.android.internal.R.styleable.EdgeEffect_edgeEffectType,
- defaultToStretch ? TYPE_STRETCH : TYPE_GLOW);
a.recycle();
// Re-set whether nested scrolling is enabled so that it is set on all API levels
@@ -626,28 +614,6 @@
}
/**
- * Returns the {@link EdgeEffect#getType()} used for all EdgeEffects.
- *
- * @return @link EdgeEffect#getType()} used for all EdgeEffects.
- */
- @EdgeEffect.EdgeEffectType
- public int getEdgeEffectType() {
- return mEdgeEffectType;
- }
-
- /**
- * Sets the {@link EdgeEffect#getType()} used in all EdgeEffects.
- * Any existing over-scroll effects are cleared and new effects are created as needed.
- *
- * @param type the {@link EdgeEffect#getType()} used in all EdgeEffects.
- */
- public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
- mEdgeEffectType = type;
- invalidateGlows();
- invalidate();
- }
-
- /**
* Instantiate and set a LayoutManager, if specified in the attributes.
*/
private void createLayoutManager(Context context, String className, AttributeSet attrs,
@@ -2223,7 +2189,6 @@
return;
}
mLeftGlow = new EdgeEffect(getContext());
- mLeftGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2237,7 +2202,6 @@
return;
}
mRightGlow = new EdgeEffect(getContext());
- mRightGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2251,7 +2215,6 @@
return;
}
mTopGlow = new EdgeEffect(getContext());
- mTopGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2266,7 +2229,6 @@
return;
}
mBottomGlow = new EdgeEffect(getContext());
- mBottomGlow.setType(mEdgeEffectType);
if (mClipToPadding) {
mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 93cde3d..1174db5 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -387,28 +387,6 @@
}
/**
- * Returns the {@link EdgeEffect#getType()} for the edge effects.
- * @return the {@link EdgeEffect#getType()} for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- @EdgeEffect.EdgeEffectType
- public int getEdgeEffectType() {
- // Both left and right edge have the same edge effect type
- return mLeftEdge.getType();
- }
-
- /**
- * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
- * @param type The edge effect type to use for the edge effects.
- * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
- */
- public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
- mLeftEdge.setType(type);
- mRightEdge.setType(type);
- invalidate();
- }
-
- /**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index b616ffc..f1ae268 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -171,10 +171,6 @@
/*
* JNI registration.
*/
-static const JNINativeMethod gMethods[] = {
- // n/a
-};
-
int register_android_media_AudioAttributes(JNIEnv *env)
{
jclass audioAttributesClass = FindClassOrDie(env, kClassPathName);
@@ -218,5 +214,5 @@
env->DeleteLocalRef(audioAttributesClass);
- return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+ return 0;
}
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 4bd33a9..1baea2a 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -37,11 +37,13 @@
namespace android {
+using vintf::CompatibilityMatrix;
using vintf::HalManifest;
using vintf::Level;
using vintf::SchemaType;
using vintf::to_string;
using vintf::toXml;
+using vintf::Version;
using vintf::VintfObject;
using vintf::Vndk;
@@ -119,6 +121,28 @@
return env->NewStringUTF(cString.c_str());
}
+static jstring android_os_VintfObject_getPlatformSepolicyVersion(JNIEnv* env, jclass) {
+ std::shared_ptr<const CompatibilityMatrix> matrix =
+ VintfObject::GetFrameworkCompatibilityMatrix();
+ if (matrix == nullptr || matrix->type() != SchemaType::FRAMEWORK) {
+ jniThrowRuntimeException(env, "Cannot get framework compatibility matrix");
+ return nullptr;
+ }
+
+ auto versions = matrix->getSepolicyVersions();
+ if (versions.empty()) {
+ jniThrowRuntimeException(env,
+ "sepolicy_version in framework compatibility matrix is empty");
+ return nullptr;
+ }
+
+ Version latest;
+ for (const auto& range : versions) {
+ latest = std::max(latest, range.maxVer());
+ }
+ return env->NewStringUTF(to_string(latest).c_str());
+}
+
static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) {
std::shared_ptr<const HalManifest> manifest = VintfObject::GetFrameworkHalManifest();
if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) {
@@ -145,12 +169,17 @@
// ----------------------------------------------------------------------------
static const JNINativeMethod gVintfObjectMethods[] = {
- {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
- {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
- {"getHalNamesAndVersions", "()[Ljava/lang/String;", (void*)android_os_VintfObject_getHalNamesAndVersions},
- {"getSepolicyVersion", "()Ljava/lang/String;", (void*)android_os_VintfObject_getSepolicyVersion},
- {"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
- {"getTargetFrameworkCompatibilityMatrixVersion", "()Ljava/lang/Long;", (void*)android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion},
+ {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
+ {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
+ {"getHalNamesAndVersions", "()[Ljava/lang/String;",
+ (void*)android_os_VintfObject_getHalNamesAndVersions},
+ {"getSepolicyVersion", "()Ljava/lang/String;",
+ (void*)android_os_VintfObject_getSepolicyVersion},
+ {"getPlatformSepolicyVersion", "()Ljava/lang/String;",
+ (void*)android_os_VintfObject_getPlatformSepolicyVersion},
+ {"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
+ {"getTargetFrameworkCompatibilityMatrixVersion", "()Ljava/lang/Long;",
+ (void*)android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion},
};
const char* const kVintfObjectPathName = "android/os/VintfObject";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4dc4bef..4b828ba 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3947,11 +3947,11 @@
<!-- This permission is required among systems services when accessing
tuner resource management related APIs or information.
- <p>Protection level: signature|privileged
- <p>Not for use by third-party applications.
+ <p>Protection level: signature|privileged|vendorPrivileged
+ <p>This should only be used by the OEM TvInputService.
@hide -->
<permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
<!-- This permission is required by Media Resource Manager Service when
accessing its overridePid Api.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 480b478..f0c43ff 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1179,15 +1179,6 @@
<!-- Color applied to effects. -->
<attr name="effectColor" format="color" />
- <!-- The type of the edge effect. The default is glow. -->
- <attr name="edgeEffectType">
- <!-- Use a colored glow at the edge. -->
- <enum name="glow" value="0" />
-
- <!-- Stretch the content. -->
- <enum name="stretch" value="1" />
- </attr>
-
<!-- =================== -->
<!-- Lighting properties -->
<!-- =================== -->
@@ -9257,7 +9248,6 @@
<!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->
<declare-styleable name="EdgeEffect">
<attr name="colorEdgeEffect" />
- <attr name="edgeEffectType" />
</declare-styleable>
<!-- Use <code>tv-input</code> as the root tag of the XML resource that describes a
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 0e9526a..482b112 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3064,7 +3064,6 @@
<public name="hotwordDetectionService" />
<public name="previewLayout" />
<public name="clipToOutline" />
- <public name="edgeEffectType" />
<public name="knownCerts" />
<public name="windowBackgroundBlurRadius"/>
<public name="windowSplashScreenBackground"/>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_device_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_device_24.xml
new file mode 100644
index 0000000..223cdf4
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_device_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="#3ddc84">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,1L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-2 -2,-2zM7,6h10v10L7,16L7,6zM17,21L7,21v-3h10v3zM7,4L7,3h10v1L7,4zM10,19h4v1h-4z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index a15a8d8..24b164b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.util.DebugUtils;
@@ -63,7 +62,7 @@
}
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
- context.getPackageManager(), requestedBatteryConsumer);
+ requestedBatteryConsumer, batteryConsumerId, context.getPackageManager());
double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
double[] totalModeledPowerByComponentMah =
@@ -119,16 +118,20 @@
private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
String batteryConsumerId) {
+ for (int scope = 0;
+ scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+ scope++) {
+ if (batteryConsumerId(scope).equals(batteryConsumerId)) {
+ return batteryUsageStats.getAggregateBatteryConsumer(scope);
+ }
+ }
+
for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
return consumer;
}
}
- for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) {
- if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
- return consumer;
- }
- }
+
return null;
}
@@ -148,11 +151,25 @@
private void computeTotalPower(BatteryUsageStats batteryUsageStats,
double[] powerByComponentMah) {
- for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
- for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT;
- component++) {
- powerByComponentMah[component] += consumer.getConsumedPower(component);
- }
+ final BatteryConsumer consumer =
+ batteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+ powerByComponentMah[component] += consumer.getConsumedPower(component);
+ }
+ }
+
+ private void computeTotalPowerForCustomComponent(
+ BatteryUsageStats batteryUsageStats, double[] powerByComponentMah) {
+ final BatteryConsumer consumer =
+ batteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ final int customComponentCount = consumer.getCustomPowerComponentCount();
+ for (int component = 0;
+ component < Math.min(customComponentCount, powerByComponentMah.length);
+ component++) {
+ powerByComponentMah[component] += consumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
}
}
@@ -166,19 +183,6 @@
}
}
- private void computeTotalPowerForCustomComponent(
- BatteryUsageStats batteryUsageStats, double[] powerByComponentMah) {
- for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
- final int customComponentCount = consumer.getCustomPowerComponentCount();
- for (int component = 0;
- component < Math.min(customComponentCount, powerByComponentMah.length);
- component++) {
- powerByComponentMah[component] += consumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
- }
- }
- }
-
private void addEntry(String title, EntryType entryType, double amount, double totalAmount,
boolean isSystemBatteryConsumer) {
Entry entry = new Entry();
@@ -203,10 +207,13 @@
return "APP|"
+ UserHandle.getUserId(((UidBatteryConsumer) consumer).getUid()) + "|"
+ ((UidBatteryConsumer) consumer).getUid();
- } else if (consumer instanceof SystemBatteryConsumer) {
- return ((SystemBatteryConsumer) consumer).getDrainType() + "|0|0";
} else {
return "";
}
}
-}
+
+ public static String batteryConsumerId(
+ @BatteryUsageStats.AggregateBatteryConsumerScope int scope) {
+ return "SYS|" + scope;
+ }
+}
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index 6288e0b..f2d6bca 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -16,11 +16,13 @@
package com.android.frameworks.core.batterystatsviewer;
+import static com.android.frameworks.core.batterystatsviewer.BatteryConsumerData.batteryConsumerId;
+
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.util.DebugUtils;
@@ -41,10 +43,11 @@
}
@NonNull
- public static BatteryConsumerInfo makeBatteryConsumerInfo(PackageManager packageManager,
- @NonNull BatteryConsumer batteryConsumer) {
+ public static BatteryConsumerInfo makeBatteryConsumerInfo(
+ @NonNull BatteryConsumer batteryConsumer, String batteryConsumerId,
+ PackageManager packageManager) {
BatteryConsumerInfo info = new BatteryConsumerInfo();
- info.id = BatteryConsumerData.batteryConsumerId(batteryConsumer);
+ info.id = batteryConsumerId;
info.powerMah = batteryConsumer.getConsumedPower();
if (batteryConsumer instanceof UidBatteryConsumer) {
@@ -100,25 +103,29 @@
info.packages = sb;
}
}
- } else if (batteryConsumer instanceof SystemBatteryConsumer) {
- final SystemBatteryConsumer systemBatteryConsumer =
- (SystemBatteryConsumer) batteryConsumer;
- final int drainType = systemBatteryConsumer.getDrainType();
- String name = DebugUtils.constantToString(SystemBatteryConsumer.class, "DRAIN_TYPE_",
- drainType);
- info.label = name.charAt(0) + name.substring(1).toLowerCase().replace('_', ' ');
- info.isSystemBatteryConsumer = true;
- }
-
- // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
- if (info.iconInfo == null) {
- try {
- info.iconInfo =
- packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
- } catch (PackageManager.NameNotFoundException nameNotFoundException) {
- // Won't happen
+ // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
+ if (info.iconInfo == null) {
+ try {
+ info.iconInfo =
+ packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
+ } catch (PackageManager.NameNotFoundException nameNotFoundException) {
+ // Won't happen
+ }
+ }
+ } else {
+ for (int scope = 0;
+ scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+ scope++) {
+ if (batteryConsumerId(scope).equals(batteryConsumerId)) {
+ final String name = DebugUtils.constantToString(BatteryUsageStats.class,
+ "AGGREGATE_BATTERY_CONSUMER_SCOPE_", scope)
+ .replace('_', ' ');
+ info.label = name;
+ break;
+ }
}
}
+
return info;
}
}
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
index 63a15d6..9e63a35 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
@@ -18,68 +18,60 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
import android.os.Bundle;
+import android.os.UidBatteryConsumer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.viewpager.widget.ViewPager;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
-import com.google.android.material.tabs.TabLayout;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
/**
* Picker, showing a sorted lists of applications and other types of entities consuming power.
* Opens BatteryStatsViewerActivity upon item selection.
*/
-public class BatteryConsumerPickerActivity extends FragmentActivity {
+public class BatteryConsumerPickerActivity extends ComponentActivity {
private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
+ private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
+ private BatteryConsumerListAdapter mBatteryConsumerListAdapter;
+ private RecyclerView mAppList;
+ private View mLoadingView;
+ private final Runnable mBatteryStatsRefresh = this::loadBatteryStats;
+
+ private interface OnBatteryConsumerSelectedListener {
+ void onBatteryConsumerSelected(String batteryConsumerId);
+ }
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- setContentView(R.layout.battery_consumer_picker_activity_layout);
+ setContentView(R.layout.battery_consumer_picker_layout);
+ mLoadingView = findViewById(R.id.loading_view);
- ViewPager viewPager = findViewById(R.id.pager);
+ mAppList = findViewById(R.id.list_view);
+ mAppList.setLayoutManager(new LinearLayoutManager(this));
+ mBatteryConsumerListAdapter =
+ new BatteryConsumerListAdapter((this::setSelectedBatteryConsumer));
+ mAppList.setAdapter(mBatteryConsumerListAdapter);
- FragmentStatePagerAdapter adapter = new FragmentStatePagerAdapter(
- getSupportFragmentManager()) {
-
- @Override
- public int getCount() {
- return 2;
- }
-
- @NonNull
- @Override
- public Fragment getItem(int position) {
- switch (position) {
- case 0:
- return new BatteryConsumerPickerFragment(
- BatteryConsumerPickerFragment.PICKER_TYPE_APP);
- case 1:
- default:
- return new BatteryConsumerPickerFragment(
- BatteryConsumerPickerFragment.PICKER_TYPE_DRAIN);
- }
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- switch (position) {
- case 0:
- return "Apps";
- case 1:
- return "Drains";
- }
- return null;
- }
- };
-
- viewPager.setAdapter(adapter);
- TabLayout tabLayout = findViewById(R.id.tab_layout);
- tabLayout.setupWithViewPager(viewPager);
if (icicle == null) {
final String batteryConsumerId = getPreferences(Context.MODE_PRIVATE)
.getString(PREF_SELECTED_BATTERY_CONSUMER, null);
@@ -101,4 +93,183 @@
.putExtra(BatteryStatsViewerActivity.EXTRA_BATTERY_CONSUMER, batteryConsumerId);
startActivity(intent);
}
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ loadBatteryStats();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh);
+ }
+
+ private void loadBatteryStats() {
+ LoaderManager.getInstance(this).restartLoader(0, null,
+ new BatteryConsumerListLoaderCallbacks());
+ getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS);
+ }
+
+ private static class BatteryConsumerListLoader extends
+ AsyncLoaderCompat<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> {
+ private final BatteryStatsManager mBatteryStatsManager;
+ private final PackageManager mPackageManager;
+
+ BatteryConsumerListLoader(Context context) {
+ super(context);
+ mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class);
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public List<BatteryConsumerInfoHelper.BatteryConsumerInfo> loadInBackground() {
+ final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats();
+ List<BatteryConsumerInfoHelper.BatteryConsumerInfo> batteryConsumerList =
+ new ArrayList<>();
+
+ for (int scope = 0;
+ scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+ scope++) {
+ batteryConsumerList.add(
+ BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
+ batteryUsageStats.getAggregateBatteryConsumer(scope),
+ BatteryConsumerData.batteryConsumerId(scope),
+ mPackageManager));
+ }
+
+ for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+ batteryConsumerList.add(
+ BatteryConsumerInfoHelper.makeBatteryConsumerInfo(consumer,
+ BatteryConsumerData.batteryConsumerId(consumer),
+ mPackageManager));
+ }
+
+ batteryConsumerList.sort(
+ Comparator.comparing(
+ (BatteryConsumerInfoHelper.BatteryConsumerInfo a) -> a.powerMah)
+ .reversed());
+
+ return batteryConsumerList;
+ }
+
+ @Override
+ protected void onDiscardResult(List<BatteryConsumerInfoHelper.BatteryConsumerInfo> result) {
+ }
+ }
+
+ private class BatteryConsumerListLoaderCallbacks implements
+ LoaderManager.LoaderCallbacks<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> {
+
+ @NonNull
+ @Override
+ public Loader<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> onCreateLoader(int id,
+ Bundle args) {
+ return new BatteryConsumerListLoader(BatteryConsumerPickerActivity.this);
+ }
+
+ @Override
+ public void onLoadFinished(
+ @NonNull Loader<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> loader,
+ List<BatteryConsumerInfoHelper.BatteryConsumerInfo> batteryConsumerList) {
+ mBatteryConsumerListAdapter.setBatteryConsumerList(batteryConsumerList);
+ mAppList.setVisibility(View.VISIBLE);
+ mLoadingView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onLoaderReset(
+ @NonNull Loader<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> loader) {
+ }
+ }
+
+ public class BatteryConsumerListAdapter
+ extends RecyclerView.Adapter<BatteryConsumerViewHolder> {
+ private final OnBatteryConsumerSelectedListener mListener;
+ private List<BatteryConsumerInfoHelper.BatteryConsumerInfo> mBatteryConsumerList;
+
+ public BatteryConsumerListAdapter(OnBatteryConsumerSelectedListener listener) {
+ mListener = listener;
+ }
+
+ void setBatteryConsumerList(
+ List<BatteryConsumerInfoHelper.BatteryConsumerInfo> batteryConsumerList) {
+ mBatteryConsumerList = batteryConsumerList;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mBatteryConsumerList.size();
+ }
+
+ @NonNull
+ @Override
+ public BatteryConsumerViewHolder onCreateViewHolder(
+ @NonNull ViewGroup viewGroup,
+ int position) {
+ LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
+ View view = layoutInflater.inflate(R.layout.battery_consumer_info_layout, viewGroup,
+ false);
+ return new BatteryConsumerViewHolder(view, mListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull BatteryConsumerViewHolder viewHolder, int position) {
+ BatteryConsumerInfoHelper.BatteryConsumerInfo item = mBatteryConsumerList.get(position);
+ viewHolder.id = item.id;
+ viewHolder.titleView.setText(item.label);
+ if (item.details != null) {
+ viewHolder.detailsView.setText(item.details);
+ viewHolder.detailsView.setVisibility(View.VISIBLE);
+ } else {
+ viewHolder.detailsView.setVisibility(View.GONE);
+ }
+ viewHolder.powerView.setText(
+ String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
+ if (item.iconInfo != null) {
+ viewHolder.iconView.setImageDrawable(
+ item.iconInfo.loadIcon(getPackageManager()));
+ } else {
+ viewHolder.iconView.setImageResource(R.drawable.gm_device_24);
+ }
+ if (item.packages != null) {
+ viewHolder.packagesView.setText(item.packages);
+ viewHolder.packagesView.setVisibility(View.VISIBLE);
+ } else {
+ viewHolder.packagesView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ // View Holder used when displaying apps
+ public static class BatteryConsumerViewHolder extends RecyclerView.ViewHolder
+ implements View.OnClickListener {
+ private final OnBatteryConsumerSelectedListener mListener;
+
+ public String id;
+ public TextView titleView;
+ public TextView detailsView;
+ public ImageView iconView;
+ public TextView packagesView;
+ public TextView powerView;
+
+ BatteryConsumerViewHolder(View view, OnBatteryConsumerSelectedListener listener) {
+ super(view);
+ mListener = listener;
+ view.setOnClickListener(this);
+ titleView = view.findViewById(android.R.id.title);
+ detailsView = view.findViewById(R.id.details);
+ iconView = view.findViewById(android.R.id.icon);
+ packagesView = view.findViewById(R.id.packages);
+ powerView = view.findViewById(R.id.power_mah);
+ powerView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ mListener.onBatteryConsumerSelected(id);
+ }
+ }
}
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
deleted file mode 100644
index 4922087..0000000
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2008 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.frameworks.core.batterystatsviewer;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.BatteryStatsManager;
-import android.os.BatteryUsageStats;
-import android.os.Bundle;
-import android.os.SystemBatteryConsumer;
-import android.os.UidBatteryConsumer;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.frameworks.core.batterystatsviewer.BatteryConsumerInfoHelper.BatteryConsumerInfo;
-import com.android.settingslib.utils.AsyncLoaderCompat;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Picker, showing a sorted lists of applications or other types of entities consuming power.
- * Returns the selected entity ID or null.
- */
-public class BatteryConsumerPickerFragment extends Fragment {
- private static final String TAG = "AppPicker";
-
- public static final String PICKER_TYPE = "pickertype";
-
- public static final int PICKER_TYPE_APP = 0;
- public static final int PICKER_TYPE_DRAIN = 1;
-
- private BatteryConsumerListAdapter mBatteryConsumerListAdapter;
- private RecyclerView mAppList;
- private View mLoadingView;
-
- private interface OnBatteryConsumerSelectedListener {
- void onBatteryConsumerSelected(String batteryConsumerId);
- }
-
- public BatteryConsumerPickerFragment(int pickerType) {
- Bundle args = new Bundle();
- args.putInt(PICKER_TYPE, pickerType);
- setArguments(args);
- }
-
- public BatteryConsumerPickerFragment() {
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.battery_consumer_picker_layout, container, false);
- mLoadingView = view.findViewById(R.id.loading_view);
-
- mAppList = view.findViewById(R.id.list_view);
- mAppList.setLayoutManager(new LinearLayoutManager(getContext()));
- mBatteryConsumerListAdapter = new BatteryConsumerListAdapter(
- BatteryConsumerPickerFragment.this::setSelectedBatteryConsumer);
- mAppList.setAdapter(mBatteryConsumerListAdapter);
-
- LoaderManager.getInstance(this).initLoader(0, getArguments(),
- new BatteryConsumerListLoaderCallbacks());
- return view;
- }
-
- public void setSelectedBatteryConsumer(String id) {
- ((BatteryConsumerPickerActivity) getActivity()).setSelectedBatteryConsumer(id);
- }
-
- private static class BatteryConsumerListLoader extends
- AsyncLoaderCompat<List<BatteryConsumerInfo>> {
- private final int mPickerType;
- private final BatteryStatsManager mBatteryStatsManager;
- private final PackageManager mPackageManager;
-
- BatteryConsumerListLoader(Context context, int pickerType) {
- super(context);
- mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class);
- mPickerType = pickerType;
- mPackageManager = context.getPackageManager();
- }
-
- @Override
- public List<BatteryConsumerInfo> loadInBackground() {
- final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats();
-
- List<BatteryConsumerInfo> batteryConsumerList = new ArrayList<>();
- switch (mPickerType) {
- case PICKER_TYPE_APP:
- for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
- batteryConsumerList.add(
- BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
- consumer));
- }
- break;
- case PICKER_TYPE_DRAIN:
- default:
- for (SystemBatteryConsumer consumer :
- batteryUsageStats.getSystemBatteryConsumers()) {
- batteryConsumerList.add(
- BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
- consumer));
- }
- break;
- }
-
- batteryConsumerList.sort(
- Comparator.comparing((BatteryConsumerInfo a) -> a.powerMah).reversed());
- return batteryConsumerList;
- }
-
- @Override
- protected void onDiscardResult(List<BatteryConsumerInfo> result) {
- }
- }
-
- private class BatteryConsumerListLoaderCallbacks implements
- LoaderManager.LoaderCallbacks<List<BatteryConsumerInfo>> {
-
- @NonNull
- @Override
- public Loader<List<BatteryConsumerInfo>> onCreateLoader(int id, Bundle args) {
- return new BatteryConsumerListLoader(getContext(), args.getInt(PICKER_TYPE));
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<List<BatteryConsumerInfo>> loader,
- List<BatteryConsumerInfo> batteryConsumerList) {
- mBatteryConsumerListAdapter.setBatteryConsumerList(batteryConsumerList);
- mAppList.setVisibility(View.VISIBLE);
- mLoadingView.setVisibility(View.GONE);
- }
-
- @Override
- public void onLoaderReset(
- @NonNull Loader<List<BatteryConsumerInfo>> loader) {
- }
- }
-
- public class BatteryConsumerListAdapter extends
- RecyclerView.Adapter<BatteryConsumerViewHolder> {
- private final OnBatteryConsumerSelectedListener mListener;
- private List<BatteryConsumerInfo> mBatteryConsumerList;
-
- public BatteryConsumerListAdapter(OnBatteryConsumerSelectedListener listener) {
- mListener = listener;
- }
-
- void setBatteryConsumerList(List<BatteryConsumerInfo> batteryConsumerList) {
- mBatteryConsumerList = batteryConsumerList;
- notifyDataSetChanged();
- }
-
- @Override
- public int getItemCount() {
- return mBatteryConsumerList.size();
- }
-
- @NonNull
- @Override
- public BatteryConsumerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
- int position) {
- LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
- View view = layoutInflater.inflate(R.layout.battery_consumer_info_layout, viewGroup,
- false);
- return new BatteryConsumerViewHolder(view, mListener);
- }
-
- @Override
- public void onBindViewHolder(@NonNull BatteryConsumerViewHolder viewHolder, int position) {
- BatteryConsumerInfo item = mBatteryConsumerList.get(position);
- viewHolder.id = item.id;
- viewHolder.titleView.setText(item.label);
- if (item.details != null) {
- viewHolder.detailsView.setText(item.details);
- viewHolder.detailsView.setVisibility(View.VISIBLE);
- } else {
- viewHolder.detailsView.setVisibility(View.GONE);
- }
- viewHolder.powerView.setText(
- String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
- viewHolder.iconView.setImageDrawable(
- item.iconInfo.loadIcon(getContext().getPackageManager()));
- if (item.packages != null) {
- viewHolder.packagesView.setText(item.packages);
- viewHolder.packagesView.setVisibility(View.VISIBLE);
- } else {
- viewHolder.packagesView.setVisibility(View.GONE);
- }
- }
- }
-
- // View Holder used when displaying apps
- public static class BatteryConsumerViewHolder extends RecyclerView.ViewHolder
- implements View.OnClickListener {
- private final OnBatteryConsumerSelectedListener mListener;
-
- public String id;
- public TextView titleView;
- public TextView detailsView;
- public ImageView iconView;
- public TextView packagesView;
- public TextView powerView;
-
- BatteryConsumerViewHolder(View view, OnBatteryConsumerSelectedListener listener) {
- super(view);
- mListener = listener;
- view.setOnClickListener(this);
- titleView = view.findViewById(android.R.id.title);
- detailsView = view.findViewById(R.id.details);
- iconView = view.findViewById(android.R.id.icon);
- packagesView = view.findViewById(R.id.packages);
- powerView = view.findViewById(R.id.power_mah);
- powerView.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onClick(View v) {
- mListener.onBatteryConsumerSelected(id);
- }
- }
-}
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 03dde04..bb75be4 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -51,7 +51,7 @@
private static final int LOADER_BATTERY_USAGE_STATS = 1;
private BatteryStatsDataAdapter mBatteryStatsDataAdapter;
- private final Runnable mBatteryStatsRefresh = this::periodicBatteryStatsRefresh;
+ private final Runnable mBatteryStatsRefresh = this::loadBatteryStats;
private String mBatteryConsumerId;
private TextView mTitleView;
private TextView mDetailsView;
@@ -85,13 +85,15 @@
mLoadingView = findViewById(R.id.loading_view);
mEmptyView = findViewById(R.id.empty_view);
- loadBatteryStats();
+ LoaderManager loaderManager = LoaderManager.getInstance(this);
+ loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null,
+ new BatteryUsageStatsLoaderCallbacks());
}
@Override
protected void onResume() {
super.onResume();
- periodicBatteryStatsRefresh();
+ loadBatteryStats();
}
@Override
@@ -100,15 +102,11 @@
getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh);
}
- private void periodicBatteryStatsRefresh() {
- loadBatteryStats();
- getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS);
- }
-
private void loadBatteryStats() {
LoaderManager loaderManager = LoaderManager.getInstance(this);
loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null,
new BatteryUsageStatsLoaderCallbacks());
+ getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS);
}
private static class BatteryUsageStatsLoader extends
@@ -183,9 +181,12 @@
} else {
mDetailsView.setVisibility(View.GONE);
}
- mIconView.setImageDrawable(
- batteryConsumerInfo.iconInfo.loadIcon(getPackageManager()));
-
+ if (batteryConsumerInfo.iconInfo != null) {
+ mIconView.setImageDrawable(
+ batteryConsumerInfo.iconInfo.loadIcon(getPackageManager()));
+ } else {
+ mIconView.setImageResource(R.drawable.gm_device_24);
+ }
if (batteryConsumerInfo.packages != null) {
mPackagesView.setText(batteryConsumerInfo.packages);
mPackagesView.setVisibility(View.VISIBLE);
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 2e2e6bd..6f17ea9 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -185,15 +185,6 @@
}
@Test
- public void testHandleActivity_assetsChanged() {
- relaunchActivityAndAssertPreserveWindow(activity -> {
- // Relaunches all activities.
- activity.getActivityThread().handleApplicationInfoChanged(
- activity.getApplicationInfo());
- });
- }
-
- @Test
public void testRecreateActivity() {
relaunchActivityAndAssertPreserveWindow(Activity::recreate);
}
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
index 3e2c4e9..5cee2c1 100644
--- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -16,6 +16,9 @@
package android.app.people;
+import static android.app.people.PeopleSpaceTile.SHOW_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
@@ -178,6 +181,71 @@
}
@Test
+ public void testUserQuieted() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+ assertFalse(tile.isUserQuieted());
+
+ tile = new PeopleSpaceTile
+ .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+ .setIsUserQuieted(true)
+ .build();
+ assertTrue(tile.isUserQuieted());
+ }
+
+ @Test
+ public void testCanBypassDnd() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+ assertFalse(tile.canBypassDnd());
+
+ tile = new PeopleSpaceTile
+ .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+ .setCanBypassDnd(true)
+ .build();
+ assertTrue(tile.canBypassDnd());
+ }
+
+ @Test
+ public void testNotificationPolicyState() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+
+ tile = new PeopleSpaceTile
+ .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+ .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+ .build();
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_IMPORTANT_CONVERSATIONS);
+ }
+
+ @Test
+ public void testPackageSuspended() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+ assertFalse(tile.isPackageSuspended());
+
+ tile = new PeopleSpaceTile
+ .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+ .setIsPackageSuspended(true)
+ .build();
+ assertTrue(tile.isPackageSuspended());
+ }
+
+ @Test
+ public void testContactAffinity() {
+ PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+ new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+ assertThat(tile.getContactAffinity()).isEqualTo(0f);
+
+ tile = new PeopleSpaceTile
+ .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+ .setContactAffinity(1f)
+ .build();
+ assertThat(tile.getContactAffinity()).isEqualTo(1f);
+ }
+
+ @Test
public void testStatuses() {
PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
@@ -238,6 +306,11 @@
.setNotificationDataUri(Uri.parse("data"))
.setMessagesCount(2)
.setIntent(new Intent())
+ .setIsUserQuieted(true)
+ .setCanBypassDnd(false)
+ .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+ .setIsPackageSuspended(true)
+ .setContactAffinity(1f)
.build();
Parcel parcel = Parcel.obtain();
@@ -261,6 +334,12 @@
assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri());
assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount());
assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString());
+ assertThat(readTile.isUserQuieted()).isEqualTo(tile.isUserQuieted());
+ assertThat(readTile.canBypassDnd()).isEqualTo(tile.canBypassDnd());
+ assertThat(readTile.getNotificationPolicyState()).isEqualTo(
+ tile.getNotificationPolicyState());
+ assertThat(readTile.isPackageSuspended()).isEqualTo(tile.isPackageSuspended());
+ assertThat(readTile.getContactAffinity()).isEqualTo(tile.getContactAffinity());
}
@Test
diff --git a/core/tests/coretests/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java
new file mode 100644
index 0000000..a70ecd72
--- /dev/null
+++ b/core/tests/coretests/src/android/colormodel/CamTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.graphics.cam;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CamTest {
+ static final int BLACK = 0xff000000;
+ static final int WHITE = 0xffffffff;
+ static final int MIDGRAY = 0xff777777;
+
+ static final int RED = 0xffff0000;
+ static final int GREEN = 0xff00ff00;
+ static final int BLUE = 0xff0000ff;
+
+ @Test
+ public void camFromIntToInt() {
+ Cam cam = Cam.fromInt(RED);
+ int color = cam.viewed(Frame.DEFAULT);
+ assertEquals(color, RED);
+ }
+
+ @Test
+ public void yFromMidgray() {
+ assertEquals(18.418f, CamUtils.yFromLstar(50.0f), 0.001);
+ }
+
+ @Test
+ public void yFromBlack() {
+ assertEquals(0.0f, CamUtils.yFromLstar(0.0f), 0.001);
+ }
+
+ @Test
+ public void yFromWhite() {
+ assertEquals(100.0f, CamUtils.yFromLstar(100.0f), 0.001);
+ }
+
+ @Test
+ public void camFromRed() {
+ Cam cam = Cam.fromInt(RED);
+ assertEquals(46.445f, cam.getJ(), 0.001f);
+ assertEquals(113.357f, cam.getChroma(), 0.001f);
+ assertEquals(27.408f, cam.getHue(), 0.001f);
+ assertEquals(89.494f, cam.getM(), 0.001f);
+ assertEquals(91.889f, cam.getS(), 0.001f);
+ assertEquals(105.988f, cam.getQ(), 0.001f);
+ }
+
+ @Test
+ public void camFromGreen() {
+ Cam cam = Cam.fromInt(GREEN);
+ assertEquals(79.331f, cam.getJ(), 0.001f);
+ assertEquals(108.409f, cam.getChroma(), 0.001f);
+ assertEquals(142.139f, cam.getHue(), 0.001f);
+ assertEquals(85.587f, cam.getM(), 0.001f);
+ assertEquals(78.604f, cam.getS(), 0.001f);
+ assertEquals(138.520, cam.getQ(), 0.001f);
+ }
+
+ @Test
+ public void camFromBlue() {
+ Cam cam = Cam.fromInt(BLUE);
+ assertEquals(25.465f, cam.getJ(), 0.001f);
+ assertEquals(87.230f, cam.getChroma(), 0.001f);
+ assertEquals(282.788f, cam.getHue(), 0.001f);
+ assertEquals(68.867f, cam.getM(), 0.001f);
+ assertEquals(93.674f, cam.getS(), 0.001f);
+ assertEquals(78.481f, cam.getQ(), 0.001f);
+ }
+
+ @Test
+ public void camFromBlack() {
+ Cam cam = Cam.fromInt(BLACK);
+ assertEquals(0.0f, cam.getJ(), 0.001f);
+ assertEquals(0.0f, cam.getChroma(), 0.001f);
+ assertEquals(0.0f, cam.getHue(), 0.001f);
+ assertEquals(0.0f, cam.getM(), 0.001f);
+ assertEquals(0.0f, cam.getS(), 0.001f);
+ assertEquals(0.0f, cam.getQ(), 0.001f);
+ }
+
+ @Test
+ public void camFromWhite() {
+ Cam cam = Cam.fromInt(WHITE);
+ assertEquals(100.0f, cam.getJ(), 0.001f);
+ assertEquals(2.869f, cam.getChroma(), 0.001f);
+ assertEquals(209.492f, cam.getHue(), 0.001f);
+ assertEquals(2.265f, cam.getM(), 0.001f);
+ assertEquals(12.068f, cam.getS(), 0.001f);
+ assertEquals(155.521, cam.getQ(), 0.001f);
+ }
+
+ @Test
+ public void getRedFromGamutMap() {
+ int colorToTest = RED;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void getGreenFromGamutMap() {
+ int colorToTest = GREEN;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void getBlueFromGamutMap() {
+ int colorToTest = BLUE;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void getWhiteFromGamutMap() {
+ int colorToTest = WHITE;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void getBlackFromGamutMap() {
+ int colorToTest = BLACK;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void getMidgrayFromGamutMap() {
+ int colorToTest = MIDGRAY;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void getRandomGreenFromGamutMap() {
+ int colorToTest = 0xff009200;
+ Cam cam = Cam.fromInt(colorToTest);
+ int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+ assertEquals(colorToTest, color);
+ }
+
+ @Test
+ public void gamutMapArbitraryHCL() {
+ int color = Cam.getInt(309.0f, 40.0f, 70.0f);
+ Cam cam = Cam.fromInt(color);
+
+ assertEquals(308.759f, cam.getHue(), 0.001);
+ assertEquals(40.148f, cam.getChroma(), 0.001);
+ assertEquals(70.029f, CamUtils.lstarFromInt(color), 0.001f);
+ }
+
+ @Test
+ public void ucsCoordinates() {
+ Cam cam = Cam.fromInt(RED);
+
+ assertEquals(59.584f, cam.getJstar(), 0.001f);
+ assertEquals(43.297f, cam.getAstar(), 0.001f);
+ assertEquals(22.451f, cam.getBstar(), 0.001f);
+ }
+
+ @Test
+ public void deltaEWhiteToBlack() {
+ assertEquals(25.661f, Cam.fromInt(WHITE).distance(Cam.fromInt(BLACK)), 0.001f);
+ }
+
+ @Test
+ public void deltaERedToBlue() {
+ assertEquals(21.415f, Cam.fromInt(RED).distance(Cam.fromInt(BLUE)), 0.001f);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index 236c3da..4a5528d 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
-import android.os.SystemBatteryConsumer;
import android.view.Display;
import androidx.test.filters.SmallTest;
@@ -62,15 +61,13 @@
mStatsRule.apply(calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(
- SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isEqualTo(90 * MINUTE_IN_MS);
// 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(27.777778);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
@@ -88,14 +85,12 @@
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(
- SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isEqualTo(90 * MINUTE_IN_MS);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(15.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
index c694d67..81940ed 100644
--- a/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
@@ -56,5 +56,19 @@
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
.isWithin(PRECISION).of(0.1);
+
+ final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isEqualTo(1000);
+ assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isWithin(PRECISION).of(0.1);
+
+ final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isEqualTo(1000);
+ assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+ .isWithin(PRECISION).of(0.1);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
index cf126c6..23fc35d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
@@ -36,18 +36,12 @@
@Rule
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
+ .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 1234.0); // Should be ignored
@Test
public void testDischargeTotals() {
- BatteryChargeCalculator calculator =
- new BatteryChargeCalculator(mStatsRule.getPowerProfile());
-
final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- mStatsRule.setTime(1000, 1000);
- batteryStats.resetAllStatsCmdLocked();
- batteryStats.setNoAutoReset(true);
batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
/* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
1_000_000, 1_000_000, 1_000_000);
@@ -58,8 +52,11 @@
/* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
2_000_000, 2_000_000, 2_000_000);
+ BatteryChargeCalculator calculator = new BatteryChargeCalculator();
BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator);
+ assertThat(batteryUsageStats.getConsumedPower())
+ .isWithin(PRECISION).of(380.0);
assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(10);
assertThat(batteryUsageStats.getDischargedPowerRange().getLower())
.isWithin(PRECISION).of(360.0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 41fe372..1a6408f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -23,10 +23,10 @@
import android.content.Context;
import android.net.NetworkStats;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
import android.util.SparseArray;
@@ -186,6 +186,16 @@
return mBatteryUsageStats;
}
+ public BatteryConsumer getDeviceBatteryConsumer() {
+ return mBatteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ }
+
+ public BatteryConsumer getAppsBatteryConsumer() {
+ return mBatteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+ }
+
public UidBatteryConsumer getUidBatteryConsumer(int uid) {
for (UidBatteryConsumer ubc : mBatteryUsageStats.getUidBatteryConsumers()) {
if (ubc.getUid() == uid) {
@@ -195,16 +205,6 @@
return null;
}
- public SystemBatteryConsumer getSystemBatteryConsumer(
- @SystemBatteryConsumer.DrainType int drainType) {
- for (SystemBatteryConsumer sbc : mBatteryUsageStats.getSystemBatteryConsumers()) {
- if (sbc.getDrainType() == drainType) {
- return sbc;
- }
- }
- return null;
- }
-
public UserBatteryConsumer getUserBatteryConsumer(int userId) {
for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) {
if (ubc.getUserId() == userId) {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 55302bc..127cea8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -23,7 +23,6 @@
import android.os.BatteryConsumer;
import android.os.BatteryUsageStats;
import android.os.Parcel;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import androidx.test.filters.SmallTest;
@@ -71,7 +70,6 @@
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
.setStatsStartTimestamp(1000);
-
builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid)
.setPackageWithHighestDrain("foo")
.setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000)
@@ -87,7 +85,8 @@
.setUsageDurationForCustomComponentMillis(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 800);
- builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_CAMERA)
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 10100)
.setConsumedPowerForCustomComponent(
@@ -95,17 +94,25 @@
.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_CPU, 10300)
.setUsageDurationForCustomComponentMillis(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400)
- .setPowerConsumedByApps(20000);
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
+
+ builder.getAggregateBatteryConsumerBuilder(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .setConsumedPower(30000)
+ .setConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU, 20100)
+ .setConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
+ .setUsageDurationMillis(
+ BatteryConsumer.POWER_COMPONENT_CPU, 20300)
+ .setUsageDurationForCustomComponentMillis(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400);
return builder.build();
}
public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) {
- // Camera: (10100 + 10200) - 20000 (consumed by apps) = 300
- // App: 300 + 400 + 500 = 1200
- // Total: 1500
- assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(1500);
+ assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(30000);
assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20);
assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000);
assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000);
@@ -139,28 +146,32 @@
}
}
- final List<SystemBatteryConsumer> systemBatteryConsumers =
- batteryUsageStats.getSystemBatteryConsumers();
- for (SystemBatteryConsumer systemBatteryConsumer : systemBatteryConsumers) {
- if (systemBatteryConsumer.getDrainType() == SystemBatteryConsumer.DRAIN_TYPE_CAMERA) {
- assertThat(systemBatteryConsumer.getConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100);
- assertThat(systemBatteryConsumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200);
- assertThat(systemBatteryConsumer.getUsageDurationMillis(
- BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10300);
- assertThat(systemBatteryConsumer.getUsageDurationForCustomComponentMillis(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10400);
- assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(20300);
- assertThat(systemBatteryConsumer.getPowerConsumedByApps()).isEqualTo(20000);
- assertThat(systemBatteryConsumer.getUsageDurationMillis())
- .isEqualTo(10400); // max
- assertThat(systemBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
- assertThat(systemBatteryConsumer.getCustomPowerComponentName(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
- } else {
- fail("Unexpected drain type " + systemBatteryConsumer.getDrainType());
- }
- }
+ final BatteryConsumer appsBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+ assertThat(appsBatteryConsumer.getConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100);
+ assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200);
+ assertThat(appsBatteryConsumer.getUsageDurationMillis(
+ BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10300);
+ assertThat(appsBatteryConsumer.getUsageDurationForCustomComponentMillis(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10400);
+ assertThat(appsBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
+ assertThat(appsBatteryConsumer.getCustomPowerComponentName(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
+
+ final BatteryConsumer deviceBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+ assertThat(deviceBatteryConsumer.getConsumedPower(
+ BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20100);
+ assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20200);
+ assertThat(deviceBatteryConsumer.getUsageDurationMillis(
+ BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20300);
+ assertThat(deviceBatteryConsumer.getUsageDurationForCustomComponentMillis(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20400);
+ assertThat(deviceBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
+ assertThat(deviceBatteryConsumer.getCustomPowerComponentName(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index 8723195..2de621c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -24,7 +24,6 @@
import android.os.BatteryConsumer;
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -65,13 +64,18 @@
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
- assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
+ assertBluetoothPowerAndDuration(
+ mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+ 0.11388, 6000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
0.24722, 15000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertBluetoothPowerAndDuration(
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
- 0.51944, 9000, 0.51944, 0.36111, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ mStatsRule.getDeviceBatteryConsumer(),
+ 0.40555, 24000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ assertBluetoothPowerAndDuration(
+ mStatsRule.getAppsBatteryConsumer(),
+ 0.36111, 21000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -93,13 +97,18 @@
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
- assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
+ assertBluetoothPowerAndDuration(
+ mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+ 0.1, 6000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
0.2, 15000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertBluetoothPowerAndDuration(
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
- 0.45, 9000, 0.45, 0.3, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ mStatsRule.getDeviceBatteryConsumer(),
+ 0.35, 24000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ assertBluetoothPowerAndDuration(
+ mStatsRule.getAppsBatteryConsumer(),
+ 0.3, 21000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -118,13 +127,18 @@
mStatsRule.apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(),
calculator);
- assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
+ assertBluetoothPowerAndDuration(
+ mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+ 0.10378, 3583, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
0.22950, 8416, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
assertBluetoothPowerAndDuration(
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
- 0.43712, 3584, 0.43712, 0.33329, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+ mStatsRule.getDeviceBatteryConsumer(),
+ 0.33333, 12000, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+ assertBluetoothPowerAndDuration(
+ mStatsRule.getAppsBatteryConsumer(),
+ 0.33329, 11999, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
private void setDurationsAndPower(
@@ -151,16 +165,4 @@
assertThat(usageDurationMillis).isEqualTo(durationMs);
}
-
- private void assertBluetoothPowerAndDuration(@Nullable SystemBatteryConsumer batteryConsumer,
- double powerMah, int durationMs, double consumedPower, double attributedPower,
- @BatteryConsumer.PowerModel int powerModel) {
- assertBluetoothPowerAndDuration(batteryConsumer, powerMah, durationMs, powerModel);
-
- assertThat(batteryConsumer.getConsumedPower())
- .isWithin(PRECISION).of(consumedPower);
-
- assertThat(batteryConsumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(attributedPower);
- }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
index 61eb173..c40d8a0 100644
--- a/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
@@ -42,9 +42,9 @@
@Test
public void testTimerBasedModel() {
- BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
- uidStats.noteCameraTurnedOnLocked(1000);
- uidStats.noteCameraTurnedOffLocked(2000);
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+ stats.noteCameraOnLocked(APP_UID, 1000, 1000);
+ stats.noteCameraOffLocked(APP_UID, 2000, 2000);
CameraPowerCalculator calculator =
new CameraPowerCalculator(mStatsRule.getPowerProfile());
@@ -56,5 +56,19 @@
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.1);
+
+ final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+ .isEqualTo(1000);
+ assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+ .isWithin(PRECISION).of(0.1);
+
+ final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+ .isEqualTo(1000);
+ assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+ .isWithin(PRECISION).of(0.1);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 1a99fb0f..152d246 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -160,6 +160,18 @@
assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+
+ final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(3.76455);
+ assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(3.76455);
+ assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -224,5 +236,17 @@
assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+
+ final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(10.62949);
+ assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isWithin(PRECISION).of(10.62949);
+ assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
index f011117..f8c2bc6 100644
--- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
@@ -20,7 +20,6 @@
import android.os.BatteryConsumer;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.util.SparseLongArray;
@@ -68,12 +67,19 @@
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
.isWithin(PRECISION).of(33.33333);
- SystemBatteryConsumer systemConsumer = mStatsRule.getSystemBatteryConsumer(
- SystemBatteryConsumer.DRAIN_TYPE_CUSTOM);
- assertThat(systemConsumer.getConsumedPowerForCustomComponent(
+ final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
.isWithin(PRECISION).of(27.77777);
- assertThat(systemConsumer.getConsumedPowerForCustomComponent(
+ assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
+ .isWithin(PRECISION).of(55.55555);
+
+ final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
+ .isWithin(PRECISION).of(27.77777);
+ assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
.isWithin(PRECISION).of(55.55555);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
index 7ea799f..1964430 100644
--- a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
@@ -62,6 +62,18 @@
.isWithin(PRECISION).of(0.1);
assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isWithin(PRECISION).of(0.1);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isWithin(PRECISION).of(0.1);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -97,5 +109,17 @@
.isWithin(PRECISION).of(5.55555);
assertThat(consumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isWithin(PRECISION).of(8.333333);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isWithin(PRECISION).of(8.333333);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
index 2331eeb..67b1e51 100644
--- a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
-import android.os.SystemBatteryConsumer;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -46,11 +45,16 @@
mStatsRule.apply(calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_IDLE);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE))
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE))
.isEqualTo(3000);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
.isWithin(PRECISION).of(0.7);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE))
+ .isEqualTo(0);
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
+ .isWithin(PRECISION).of(0);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
index 94e760a..4868d6a 100644
--- a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import android.os.BatteryConsumer;
-import android.os.SystemBatteryConsumer;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -53,8 +52,7 @@
mStatsRule.apply(calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MEMORY);
+ BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MEMORY))
.isEqualTo(3000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY))
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 5b84a1b..48a1da1 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -27,7 +27,6 @@
import android.net.NetworkStats;
import android.os.BatteryConsumer;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
@@ -100,22 +99,23 @@
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(2.2444);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
- assertThat(consumer.getConsumedPower())
- .isWithin(PRECISION).of(2.2444);
- assertThat(consumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(0.8);
-
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.8);
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(2.2444);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.8);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -159,21 +159,22 @@
mStatsRule.apply(calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
-
- // 100000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) + 1.53934 (apps)= 4.31711 mAh
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(4.31711);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
- assertThat(consumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(1.53934);
-
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.53934);
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(4.31711);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.53934);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 93c7106..4c29c20 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -21,7 +21,6 @@
import android.app.ActivityManager;
import android.os.BatteryConsumer;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.view.Display;
@@ -82,21 +81,6 @@
mStatsRule.apply(calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(80 * MINUTE_IN_MS);
-
- // 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isWithin(PRECISION).of(166.66666);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
- assertThat(consumer.getConsumedPower())
- .isWithin(PRECISION).of(166.66666);
- assertThat(consumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(166.66666);
-
UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(20 * MINUTE_IN_MS);
@@ -120,6 +104,25 @@
.isWithin(PRECISION).of(101.85185);
assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(80 * MINUTE_IN_MS);
+
+ // 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(166.66666);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(80 * MINUTE_IN_MS);
+
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(166.66666);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
@Test
@@ -151,19 +154,6 @@
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
- SystemBatteryConsumer consumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(80 * MINUTE_IN_MS);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isWithin(PRECISION).of(92.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
- assertThat(consumer.getConsumedPower())
- .isWithin(PRECISION).of(92.0);
- assertThat(consumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(92.0);
-
UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(20 * MINUTE_IN_MS);
@@ -185,6 +175,22 @@
.isWithin(PRECISION).of(69.0);
assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(80 * MINUTE_IN_MS);
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(92);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(80 * MINUTE_IN_MS);
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isWithin(PRECISION).of(92);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
diff --git a/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java
index 74235b2..7563e39 100644
--- a/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java
@@ -59,11 +59,11 @@
when(sensorManager.getSensorList(Sensor.TYPE_ALL))
.thenReturn(List.of(sensor1, sensor2));
- BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
- uidStats.noteStartSensor(SENSOR_HANDLE_1, 1000);
- uidStats.noteStopSensor(SENSOR_HANDLE_1, 2000);
- uidStats.noteStartSensor(SENSOR_HANDLE_2, 3000);
- uidStats.noteStopSensor(SENSOR_HANDLE_2, 5000);
+ final BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+ stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_1, 1000, 1000);
+ stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_1, 2000, 2000);
+ stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_2, 3000, 3000);
+ stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_2, 5000, 5000);
SensorPowerCalculator calculator = new SensorPowerCalculator(sensorManager);
@@ -74,6 +74,14 @@
.isEqualTo(3000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS))
.isWithin(PRECISION).of(0.5);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS))
+ .isWithin(PRECISION).of(0.5);
+
+ BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS))
+ .isWithin(PRECISION).of(0.5);
}
private Sensor createSensor(int handle, int type, double power) {
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index 58e2513..cd45060 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -133,6 +133,12 @@
assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS))
.isWithin(PRECISION).of(-18.888888);
+ assertThat(mStatsRule.getDeviceBatteryConsumer()
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
+ .isWithin(PRECISION).of(18.888888);
+ assertThat(mStatsRule.getAppsBatteryConsumer()
+ .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
+ .isWithin(PRECISION).of(18.888888);
}
private static class MockKernelCpuUidFreqTimeReader extends
diff --git a/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
index fa0dbc7..ae61d31 100644
--- a/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
@@ -56,5 +56,19 @@
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
.isWithin(PRECISION).of(0.1);
+
+ final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO))
+ .isEqualTo(1000);
+ assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
+ .isWithin(PRECISION).of(0.1);
+
+ final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsBatteryConsumer
+ .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO))
+ .isEqualTo(1000);
+ assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
+ .isWithin(PRECISION).of(0.1);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
index 9d3ed55..82830f2 100644
--- a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -72,5 +72,15 @@
.isEqualTo(5000);
assertThat(osConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
.isWithin(PRECISION).of(0.5);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+ .isEqualTo(6000);
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+ .isWithin(PRECISION).of(0.6);
+
+ BatteryConsumer appConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+ .isWithin(PRECISION).of(0.6);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
index 4a7cf1e..fc44ddc 100644
--- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
@@ -25,7 +25,6 @@
import android.net.NetworkStats;
import android.os.BatteryConsumer;
import android.os.Process;
-import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.WorkSource;
import android.os.connectivity.WifiActivityEnergyInfo;
@@ -94,16 +93,19 @@
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
- SystemBatteryConsumer systemConsumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
- assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(5577);
- assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isWithin(PRECISION).of(1.11153);
- assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isEqualTo(4002);
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.86666);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
- assertThat(systemConsumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(0.466333);
+
+ BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.866666);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -125,17 +127,19 @@
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
- SystemBatteryConsumer systemConsumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
- assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(5577);
- /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
- assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isWithin(PRECISION).of(1.11153 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
- assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isEqualTo(4002);
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.27777);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
- assertThat(systemConsumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(0.14946);
+
+ BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isWithin(PRECISION).of(0.277777);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
/** Sets up batterystats object with prepopulated network & timer data for Timer-model tests. */
@@ -168,17 +172,6 @@
.isWithin(PRECISION).of(0.8231573);
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
- SystemBatteryConsumer systemConsumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
- assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(2222);
- assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isWithin(PRECISION).of(2.575000);
- assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
- assertThat(systemConsumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(1.69907);
}
@Test
@@ -200,17 +193,5 @@
.isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
- SystemBatteryConsumer systemConsumer =
- mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
- assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(2222);
- /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
- assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isWithin(PRECISION).of(2.575000 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
- assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
- assertThat(systemConsumer.getPowerConsumedByApps())
- .isWithin(PRECISION).of(0.277777);
}
}
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index b9393ff..b3615ff 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -46,7 +46,7 @@
public static final int FLAGS = 0;
// Is this the first-draw following a window layout?
- public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+ public static final long FLAG_WINDOW_VISIBILITY_CHANGED = 1;
// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;
@@ -56,7 +56,7 @@
public static final long INVALID_VSYNC_ID = -1;
@LongDef(flag = true, value = {
- FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
+ FLAG_WINDOW_VISIBILITY_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 895cd3e..24d7780 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -864,7 +864,6 @@
boolean shouldExit = mExitingAnimation;
mRippleActive = false;
mExitingAnimation = false;
- getRipplePaint();
drawContent(canvas);
drawPatternedBackground(canvas, cx, cy);
if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
@@ -928,7 +927,7 @@
startBackgroundAnimation();
}
if (mBackgroundOpacity == 0) return;
- Paint p = mRipplePaint;
+ Paint p = getRipplePaint();
float newOpacity = mBackgroundOpacity;
final int origAlpha = p.getAlpha();
final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255);
@@ -957,7 +956,7 @@
@NonNull
private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
float x, float y, float cx, float cy, float w, float h) {
- Paint p = new Paint(mRipplePaint);
+ Paint p = new Paint(getRipplePaint());
float radius = getComputedRadius();
RippleAnimationSession.AnimationProperties<Float, Paint> properties;
RippleShader shader = new RippleShader();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java
index 1302461..4892e6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java
@@ -82,7 +82,7 @@
public void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "States: ");
+ pw.println(TAG);
pw.print(innerPrefix + "mPackageName=");
pw.println(mPackageName);
pw.print(innerPrefix + "mDescription=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index 703eba9..481b948 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -203,7 +203,7 @@
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "states: ");
+ pw.println(TAG);
pw.print(innerPrefix + "mIsShowing=");
pw.println(mIsShowing);
pw.print(innerPrefix + "mBkgBounds=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 04ec391..ae7ab52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -630,7 +630,8 @@
public void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "States: ");
+ pw.println();
+ pw.println(TAG);
pw.print(innerPrefix + "mOffSetFraction=");
pw.println(mOffSetFraction);
pw.print(innerPrefix + "mLockedDisabled=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 1c24338..b8da37f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -305,7 +305,7 @@
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "states: ");
+ pw.println(TAG);
pw.print(innerPrefix + "mDisplayLayout.rotation()=");
pw.println(mDisplayLayout.rotation());
pw.print(innerPrefix + "mDisplayAreaTokenMap=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 9e83a61..0383229 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -276,7 +276,7 @@
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "States: ");
+ pw.println(TAG);
pw.print(innerPrefix + "mAllowGesture=");
pw.println(mAllowGesture);
pw.print(innerPrefix + "mIsEnabled=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 1b2fcdd..2ab51f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -137,8 +137,8 @@
void dump(PrintWriter pw, String prefix, ContentResolver resolver,
int userId) {
- final String innerPrefix = prefix + " ";
- pw.println(innerPrefix + TAG);
+ final String innerPrefix = " ";
+ pw.println(TAG);
pw.print(innerPrefix + "isOneHandedModeEnable=");
pw.println(getSettingsOneHandedModeEnabled(resolver, userId));
pw.print(innerPrefix + "oneHandedTimeOut=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java
index cc87443..facc4bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java
@@ -90,7 +90,7 @@
/** Dumps internal state. */
public void dump(PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "states: ");
+ pw.println(TAG);
pw.println(innerPrefix + "sCurrentState=" + sCurrentState);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
index 4a98941..899c9ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
@@ -18,8 +18,6 @@
import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
-import android.os.Handler;
-
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -122,7 +120,7 @@
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "states: ");
+ pw.println(TAG);
pw.print(innerPrefix + "sTimeout=");
pw.println(mTimeout);
pw.print(innerPrefix + "sListeners=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index c7a49ff..0f9b320 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -156,7 +156,7 @@
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + "states: ");
+ pw.println(TAG);
pw.print(innerPrefix + "mLastUpdatedBounds=");
pw.println(mLastUpdatedBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index c60e903..e8cee8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -224,7 +224,7 @@
void dump(@NonNull PrintWriter pw) {
final String innerPrefix = " ";
- pw.println(TAG + " states: ");
+ pw.println(TAG);
pw.print(innerPrefix + "mTriggerState=");
pw.println(mTriggerState);
pw.print(innerPrefix + "mDisplayBounds=");
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java
index e29fc6a..aae1dd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java
@@ -24,6 +24,7 @@
import com.android.internal.logging.testing.UiEventLoggerFake;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -51,6 +52,7 @@
}
@Test
+ @Ignore("b/184813408, go/wm-tests showing test flaky")
public void testLogEvent() {
if (mUiEvent != null) {
assertEquals(1, mUiEventLogger.numLogs());
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 1e90b7c..17c1404 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -816,6 +816,11 @@
uint32_t mSourceResourceId;
};
+static inline bool operator==(const android::ResXMLParser::ResXMLPosition& lhs,
+ const android::ResXMLParser::ResXMLPosition& rhs) {
+ return lhs.curNode == rhs.curNode;
+}
+
class DynamicRefTable;
/**
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 2a134fa..540a88b 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -68,7 +68,7 @@
namespace FrameInfoFlags {
enum {
- WindowLayoutChanged = 1 << 0,
+ WindowVisibilityChanged = 1 << 0,
RTAnimation = 1 << 1,
SurfaceCanvas = 1 << 2,
SkippedFrame = 1 << 3,
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index c1d6725..5952479 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -360,6 +360,9 @@
}
}
+ private static volatile LocationEnabledCache sLocationEnabledCache =
+ new LocationEnabledCache(4);
+
@GuardedBy("sLocationListeners")
private static final WeakHashMap<LocationListener, WeakReference<LocationListenerTransport>>
sLocationListeners = new WeakHashMap<>();
@@ -386,20 +389,6 @@
final Context mContext;
final ILocationManager mService;
- private volatile PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
- new PropertyInvalidatedCache<Integer, Boolean>(
- 4,
- CACHE_KEY_LOCATION_ENABLED_PROPERTY) {
- @Override
- protected Boolean recompute(Integer userHandle) {
- try {
- return mService.isLocationEnabledForUser(userHandle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- };
-
/**
* @hide
*/
@@ -533,7 +522,7 @@
* @return true if location is enabled and false if location is disabled.
*/
public boolean isLocationEnabled() {
- return isLocationEnabledForUser(Process.myUserHandle());
+ return isLocationEnabledForUser(mContext.getUser());
}
/**
@@ -546,12 +535,17 @@
*/
@SystemApi
public boolean isLocationEnabledForUser(@NonNull UserHandle userHandle) {
- PropertyInvalidatedCache<Integer, Boolean> cache = mLocationEnabledCache;
- if (cache != null) {
- return cache.query(userHandle.getIdentifier());
+ // skip the cache for any "special" user ids - special ids like CURRENT_USER may change
+ // their meaning over time and should never be in the cache. we could resolve the special
+ // user ids here, but that would require an x-process call anyways, and the whole point of
+ // the cache is to avoid x-process calls.
+ if (userHandle.getIdentifier() >= 0) {
+ PropertyInvalidatedCache<Integer, Boolean> cache = sLocationEnabledCache;
+ if (cache != null) {
+ return cache.query(userHandle.getIdentifier());
+ }
}
- // fallback if cache is disabled
try {
return mService.isLocationEnabledForUser(userHandle.getIdentifier());
} catch (RemoteException e) {
@@ -3004,7 +2998,7 @@
ListenerExecutor, CancellationSignal.OnCancelListener {
private final Executor mExecutor;
- private volatile @Nullable Consumer<Location> mConsumer;
+ volatile @Nullable Consumer<Location> mConsumer;
GetCurrentLocationTransport(Executor executor, Consumer<Location> consumer,
@Nullable CancellationSignal cancellationSignal) {
@@ -3465,6 +3459,37 @@
}
}
+ private static class LocationEnabledCache extends PropertyInvalidatedCache<Integer, Boolean> {
+
+ // this is not loaded immediately because this class is created as soon as LocationManager
+ // is referenced for the first time, and within the system server, the ILocationManager
+ // service may not have been loaded yet at that time.
+ private @Nullable ILocationManager mManager;
+
+ LocationEnabledCache(int numEntries) {
+ super(numEntries, CACHE_KEY_LOCATION_ENABLED_PROPERTY);
+ }
+
+ @Override
+ protected Boolean recompute(Integer userId) {
+ Preconditions.checkArgument(userId >= 0);
+
+ if (mManager == null) {
+ try {
+ mManager = getService();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ try {
+ return mManager.isLocationEnabledForUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
/**
* @hide
*/
@@ -3475,7 +3500,7 @@
/**
* @hide
*/
- public void disableLocalLocationEnabledCaches() {
- mLocationEnabledCache = null;
+ public static void disableLocalLocationEnabledCaches() {
+ sLocationEnabledCache = null;
}
}
diff --git a/mime/java/android/content/type/DefaultMimeMapFactory.java b/mime/java/android/content/type/DefaultMimeMapFactory.java
index 11d20d4..bcd0eb0 100644
--- a/mime/java/android/content/type/DefaultMimeMapFactory.java
+++ b/mime/java/android/content/type/DefaultMimeMapFactory.java
@@ -96,7 +96,7 @@
specs.add(spec);
startIdx = endIdx + 1; // skip over the space
} while (startIdx < line.length());
- builder.put(specs.get(0), specs.subList(1, specs.size()));
+ builder.addMimeMapping(specs.get(0), specs.subList(1, specs.size()));
}
} catch (IOException | RuntimeException e) {
throw new RuntimeException("Failed to parse " + resourceName, e);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 399cf1f..33e5231 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -42,10 +42,17 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- File sourceFile = new File(getIntent().getData().getPath());
- sourceFile.delete();
-
setResult(resultCode, data);
finish();
}
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (isFinishing()) {
+ File sourceFile = new File(getIntent().getData().getPath());
+ new Thread(sourceFile::delete).start();
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f180776..f046f06 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -2027,6 +2027,7 @@
}
};
+ /* For the Storage Settings which shows category of app types. */
public static final AppFilter FILTER_OTHER_APPS =
new AppFilter() {
@Override
@@ -2046,4 +2047,21 @@
return !isCategorized;
}
};
+
+ /* For the Storage Settings which shows category of file types. */
+ public static final AppFilter FILTER_APPS_EXCEPT_GAMES =
+ new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry entry) {
+ boolean isCategorized;
+ synchronized (entry) {
+ isCategorized = FILTER_GAMES.filterApp(entry);
+ }
+ return !isCategorized;
+ }
+ };
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index d9ac262..f1e1e7d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -138,6 +138,34 @@
}
@Test
+ public void testAppsExceptGamesFilterRejectsGame() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+ assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testAppsExceptGamesFilterAcceptsImage() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_IMAGE;
+
+ assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testAppsExceptGamesFilterAcceptsVideo() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_VIDEO;
+
+ assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
+ public void testAppsExceptGamesFilterAcceptsAudio() {
+ mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
+
+ assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
+ }
+
+ @Test
public void testDownloadAndLauncherAndInstantAcceptsCorrectApps() {
// should include instant apps
mEntry.isHomeApp = false;
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 085263a..61a8e8e 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -17,7 +17,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:color="@color/notification_ripple_untinted_color">
+ android:color="?android:attr/colorControlHighlight">
<item>
<shape>
<solid android:color="?androidprv:attr/colorSurface" />
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 1c4c89f..8535426 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -37,7 +37,14 @@
android:orientation="vertical"
app:layout_constraintGuide_percent="0.25" />
- <ImageView
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/media_horizontal_center_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.5" />
+
+ <com.android.internal.widget.CachingIconView
android:id="@+id/recommendation_card_icon"
android:layout_width="@dimen/qs_aa_media_rec_header_icon_size"
android:layout_height="@dimen/qs_aa_media_rec_header_icon_size"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index ea644cf..c332f4c 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -254,7 +254,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
android:visibility="gone"
- android:text="@string/notification_channel_summary_priority"
android:clickable="false"
android:focusable="false"
android:ellipsize="end"
diff --git a/packages/SystemUI/res/layout/people_tile_empty_layout.xml b/packages/SystemUI/res/layout/people_tile_empty_layout.xml
new file mode 100644
index 0000000..7d3b919
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_empty_layout.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:background="@drawable/people_space_tile_view_card"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/item"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:padding="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
deleted file mode 100644
index 3f0e514..0000000
--- a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
+++ /dev/null
@@ -1,166 +0,0 @@
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/onboarding_half_shell_container"
- android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|bottom"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
- >
-
- <LinearLayout
- android:id="@+id/half_shell"
- android:layout_width="@dimen/qs_panel_width"
- android:layout_height="wrap_content"
- android:paddingTop="16dp"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:orientation="vertical"
- android:gravity="bottom"
- android:layout_gravity="center_horizontal|bottom"
- android:background="@drawable/rounded_bg_full"
- >
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:padding="12dp"
- android:layout_gravity="center_horizontal"
- >
-
- <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
- <ImageView
- android:id="@+id/conversation_icon"
- android:layout_width="@*android:dimen/conversation_avatar_size"
- android:layout_height="@*android:dimen/conversation_avatar_size"
- android:scaleType="centerCrop"
- android:importantForAccessibility="no"
- />
-
- <FrameLayout
- android:id="@+id/conversation_icon_badge"
- android:layout_width="@*android:dimen/conversation_icon_size_badged"
- android:layout_height="@*android:dimen/conversation_icon_size_badged"
- android:layout_marginLeft="@*android:dimen/conversation_badge_side_margin"
- android:layout_marginTop="@*android:dimen/conversation_badge_side_margin"
- android:clipChildren="false"
- android:clipToPadding="false"
- >
- <ImageView
- android:id="@+id/conversation_icon_badge_bg"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:src="@*android:drawable/conversation_badge_background"
- android:forceHasOverlappingRendering="false"
- />
- <ImageView
- android:id="@+id/icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="4dp"
- android:layout_gravity="center"
- android:forceHasOverlappingRendering="false"
- />
- <ImageView
- android:id="@+id/conversation_icon_badge_ring"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@*android:drawable/conversation_badge_ring"
- android:forceHasOverlappingRendering="false"
- android:clipToPadding="false"
- android:scaleType="center"
- />
- </FrameLayout>
- </FrameLayout>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:layout_marginTop="16dp"
- android:text="@string/priority_onboarding_title"
- style="@style/TextAppearance.NotificationImportanceChannel"
- />
-
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:layout_marginTop="20dp"
- android:layout_marginBottom="20dp"
- android:background="@color/material_grey_300" />
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="start"
- android:text="@string/priority_onboarding_behavior"
- style="@style/TextAppearance.NotificationImportanceChannelGroup"
- />
-
- <TextView
- android:id="@+id/behaviors"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="start"
- android:layout_marginTop="8dp"
- style="@style/TextAppearance.NotificationImportanceChannelGroup"
- />
-
- <!-- Bottom button container -->
- <RelativeLayout
- android:id="@+id/button_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="32dp"
- android:orientation="horizontal"
- >
- <TextView
- android:id="@+id/settings_button"
- android:text="@string/priority_onboarding_settings_button_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:gravity="start|center_vertical"
- android:minWidth="@dimen/notification_importance_toggle_size"
- android:minHeight="@dimen/notification_importance_toggle_size"
- android:maxWidth="125dp"
- style="@style/TextAppearance.NotificationInfo.Button"/>
- <TextView
- android:id="@+id/done_button"
- android:text="@string/priority_onboarding_done_button_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:gravity="end|center_vertical"
- android:minWidth="@dimen/notification_importance_toggle_size"
- android:minHeight="@dimen/notification_importance_toggle_size"
- android:maxWidth="125dp"
- style="@style/TextAppearance.NotificationInfo.Button"/>
-
- </RelativeLayout>
-
- </LinearLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 13c285f..edd8486 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1279,9 +1279,10 @@
<dimen name="qs_media_disabled_seekbar_vertical_padding">36dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
- <dimen name="qs_aa_media_rec_header_icon_padding">10dp</dimen>
+ <dimen name="qs_aa_media_rec_header_icon_start_margin">10dp</dimen>
<dimen name="qs_aa_media_rec_header_icon_size">18dp</dimen>
<dimen name="qs_aa_media_rec_album_size">72dp</dimen>
+ <dimen name="qs_aa_media_rec_album_vertical_margin">8dp</dimen>
<dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
<!-- Window magnification -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 673a03d..d9cc24f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1897,13 +1897,16 @@
<string name="notification_channel_summary_automatic_demoted"><b>Status:</b> Ranked Lower</string>
<!-- [CHAR LIMIT=150] Notification Importance title: important conversation level summary -->
- <string name="notification_channel_summary_priority">Always shown at the top of your notifications, even when Priority mode is on</string>
+ <string name="notification_channel_summary_priority_baseline">Shows at the top of conversation notifications and as a profile picture on lock screen</string>
+ <string name="notification_channel_summary_priority_bubble">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble</string>
+ <string name="notification_channel_summary_priority_dnd">Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Do Not Disturb</string>
+ <string name="notification_channel_summary_priority_all">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb</string>
<!--[CHAR LIMIT=30] Linkable text to Settings app -->
<string name="notification_conversation_channel_settings">Settings</string>
<!-- [CHAR LIMIT=150] Notification Importance title: important conversation level -->
- <string name="notification_priority_title">Priority conversations</string>
+ <string name="notification_priority_title">Priority</string>
<!-- Text shown in notification guts for conversation notifications that don't implement the full feature -->
<string name="no_shortcut"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> doesn\u2019t support conversation features</string>
@@ -2667,25 +2670,6 @@
<!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
<string name="inattentive_sleep_warning_title">Standby</string>
- <!-- Priority conversation onboarding screen -->
- <!-- title of priority onboarding [CHAR LIMIT=75] -->
- <string name="priority_onboarding_title">Conversation set to priority</string>
- <!-- Text explaining that the following actions are the behaviors of priority conversations.
- E.g. priority conversations will show at the top of the conversation section [CHAR LIMIT=75] -->
- <string name="priority_onboarding_behavior">Priority conversations</string>
- <!-- Text explaining that priority conversations show at the top of the conversation section [CHAR LIMIT=120] -->
- <string name="priority_onboarding_show_at_top_text">These conversations are shown at the top of your list and can always reach you when Priority mode is on</string>
- <!-- Text explaining that priority conversations show an avatar on the lock screen [CHAR LIMIT=120] -->
- <string name="priority_onboarding_show_avatar_text">Profile pictures are shown on the lock screen</string>
- <!-- Text explaining that priority conversations will appear as a bubble [CHAR LIMIT=120] -->
- <string name="priority_onboarding_appear_as_bubble_text">You can easily find these conversations in bubbles on your Home screen</string>
- <!-- Text explaining that priority conversations can interrupt DnD settings [CHAR LIMIT=120] -->
- <string name="priority_onboarding_ignores_dnd_text">Interrupt Do Not Disturb</string>
- <!-- Title for the affirmative button [CHAR LIMIT=50] -->
- <string name="priority_onboarding_done_button_title">Got it</string>
- <!-- Title for the settings button button [CHAR LIMIT=50] -->
- <string name="priority_onboarding_settings_button_title">Settings</string>
-
<!-- Window Magnification strings -->
<!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
<string name="magnification_window_title">Magnification Window</string>
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index afd800b..31a924c 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -22,8 +22,8 @@
android:id="@+id/recommendation_card_icon"
android:layout_width="@dimen/qs_aa_media_rec_header_icon_size"
android:layout_height="@dimen/qs_aa_media_rec_header_icon_size"
- android:layout_marginTop="@dimen/qs_aa_media_rec_header_icon_padding"
- android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+ android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -33,7 +33,7 @@
android:id="@+id/recommendation_card_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+ android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -45,9 +45,12 @@
android:layout_height="@dimen/qs_aa_media_rec_album_size"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
app:layout_constraintEnd_toStartOf="@id/media_cover2"
app:layout_constraintHorizontal_chainStyle="spread"
+ app:layout_constraintVertical_chainStyle="spread"
android:visibility="gone" />
<Constraint
@@ -64,6 +67,8 @@
android:id="@+id/media_cover2"
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
+ android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/media_cover1"
@@ -85,6 +90,8 @@
android:id="@+id/media_cover3"
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
+ android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/media_cover2"
@@ -112,7 +119,6 @@
app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
app:layout_constraintEnd_toStartOf="@id/media_cover5"
app:layout_constraintHorizontal_chainStyle="spread"
- app:layout_constraintHorizontal_bias="1"
android:visibility="gone" />
<Constraint
@@ -134,7 +140,6 @@
app:layout_constraintStart_toEndOf="@+id/media_cover4"
app:layout_constraintEnd_toStartOf="@+id/media_cover6"
app:layout_constraintHorizontal_chainStyle="spread"
- app:layout_constraintHorizontal_bias="1"
android:visibility="gone" />
<Constraint
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 04a4877..1411030 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -23,7 +23,7 @@
android:layout_width="@dimen/qs_aa_media_rec_header_icon_size"
android:layout_height="@dimen/qs_aa_media_rec_header_icon_size"
android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+ android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -33,7 +33,7 @@
android:id="@+id/recommendation_card_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+ android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -44,11 +44,14 @@
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginBottom="@dimen/qs_aa_media_rec_album_vertical_margin"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_cover4"
+ app:layout_constraintBottom_toTopOf="@+id/media_horizontal_center_guideline"
app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
app:layout_constraintEnd_toStartOf="@id/media_cover2"
app:layout_constraintHorizontal_chainStyle="spread"
+ app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_bias="0"
android:visibility="gone" />
<Constraint
@@ -66,11 +69,14 @@
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginBottom="@dimen/qs_aa_media_rec_album_vertical_margin"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_cover5"
+ app:layout_constraintBottom_toTopOf="@+id/media_horizontal_center_guideline"
app:layout_constraintStart_toEndOf="@id/media_cover1"
app:layout_constraintEnd_toStartOf="@id/media_cover3"
app:layout_constraintHorizontal_chainStyle="spread"
+ app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_bias="0"
android:visibility="gone" />
<Constraint
@@ -88,12 +94,15 @@
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginBottom="@dimen/qs_aa_media_rec_album_vertical_margin"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_cover6"
+ app:layout_constraintBottom_toTopOf="@+id/media_horizontal_center_guideline"
app:layout_constraintStart_toEndOf="@id/media_cover2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_bias="0"
android:visibility="gone" />
<Constraint
@@ -110,14 +119,15 @@
android:id="@+id/media_cover4"
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
- android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginTop="@dimen/qs_aa_media_rec_album_vertical_margin"
android:layout_marginBottom="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_cover1"
+ app:layout_constraintTop_toBottomOf="@+id/media_horizontal_center_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
app:layout_constraintEnd_toStartOf="@id/media_cover5"
app:layout_constraintHorizontal_chainStyle="spread"
- app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_bias="1"
android:visibility="gone" />
<Constraint
@@ -134,14 +144,15 @@
android:id="@+id/media_cover5"
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
- android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginTop="@dimen/qs_aa_media_rec_album_vertical_margin"
android:layout_marginBottom="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_cover2"
+ app:layout_constraintTop_toBottomOf="@+id/media_horizontal_center_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/media_cover4"
app:layout_constraintEnd_toStartOf="@+id/media_cover6"
app:layout_constraintHorizontal_chainStyle="spread"
- app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_bias="1"
android:visibility="gone" />
<Constraint
@@ -158,14 +169,16 @@
android:id="@+id/media_cover6"
android:layout_width="@dimen/qs_aa_media_rec_album_size"
android:layout_height="@dimen/qs_aa_media_rec_album_size"
- android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginTop="@dimen/qs_aa_media_rec_album_vertical_margin"
android:layout_marginBottom="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@id/media_cover3"
+ app:layout_constraintTop_toBottomOf="@id/media_horizontal_center_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/media_cover5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_bias="1"
android:visibility="gone" />
<Constraint
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index cce27e7..aa24bb4 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -26,5 +26,5 @@
android:previewLayout="@layout/people_space_placeholder_layout"
android:resizeMode="horizontal|vertical"
android:configure="com.android.systemui.people.PeopleSpaceActivity"
- android:initialLayout="@layout/people_space_initial_layout">
+ android:initialLayout="@layout/people_tile_empty_layout">
</appwidget-provider>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f28d113..7f18379 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -73,8 +73,7 @@
Key.TOUCHED_RINGER_TOGGLE,
Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
- Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
- Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S
+ Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
})
// TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key {
@@ -123,8 +122,6 @@
String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet";
String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
- /** Tracks whether the user has seen the onboarding screen for priority conversations */
- String HAS_SEEN_PRIORITY_ONBOARDING_IN_S = "HasUserSeenPriorityOnboardingInS";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 2a7023a..0cf3333 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -20,6 +20,7 @@
import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
+import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.screenshot.ActionProxyReceiver;
import com.android.systemui.screenshot.DeleteScreenshotReceiver;
import com.android.systemui.screenshot.SmartActionsReceiver;
@@ -79,4 +80,13 @@
public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(PeopleSpaceWidgetProvider.class)
+ public abstract BroadcastReceiver bindPeopleSpaceWidgetProvider(
+ PeopleSpaceWidgetProvider broadcastReceiver);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 8e4e308..c04201c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -23,6 +23,7 @@
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.people.PeopleProvider;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.InjectionInflationController;
import com.android.wm.shell.ShellCommandHandler;
@@ -156,4 +157,9 @@
* Member injection into the supplied argument.
*/
void inject(ClockOptionsProvider clockOptionsProvider);
+
+ /**
+ * Member injection into the supplied argument.
+ */
+ void inject(PeopleProvider peopleProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 8d9a4be..6a2d0af 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -20,6 +20,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
+import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Utils
@@ -156,6 +157,13 @@
}
}
+ var visibleToUser: Boolean = false
+ set(value) {
+ if (field != value) {
+ field = value
+ }
+ }
+
init {
mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
@@ -203,6 +211,9 @@
override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
Log.d(TAG, "My Smartspace media update is here")
addSmartspaceMediaRecommendations(key, data)
+ if (visibleToUser) {
+ logSmartspaceImpression()
+ }
}
override fun onMediaDataRemoved(key: String) {
@@ -567,6 +578,29 @@
mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
}
}
+
+ /**
+ * Log the user impression for media card.
+ */
+ fun logSmartspaceImpression() {
+ MediaPlayerData.players().forEach {
+ // Log every impression of media recommendation card since it will only be shown
+ // for 1 minute after each connection.
+ if (it.recommendationViewHolder?.recommendations?.visibility == View.VISIBLE) {
+ /* ktlint-disable max-line-length */
+ SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+ 800, // SMARTSPACE_CARD_SEEN
+ it.getInstanceId(),
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
+ it.getSurfaceForSmartspaceLogging(),
+ /* rank */ 0,
+ /* cardinality */ 1)
+ /* ktlint-disable max-line-length */
+ }
+
+ // TODO(shijieru): add logging for media control card
+ }
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index c713b22..ed16777 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -55,6 +55,7 @@
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.util.animation.TransitionLayout;
+import java.net.URISyntaxException;
import java.util.List;
import java.util.concurrent.Executor;
@@ -69,6 +70,9 @@
private static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
private static final String EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name";
+ private static final String EXTRAS_SMARTSPACE_INTENT =
+ "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
+ private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3;
private static final int MEDIA_RECOMMENDATION_MAX_NUM = 6;
@@ -100,6 +104,8 @@
private int mBackgroundColor;
private int mDevicePadding;
private int mAlbumArtSize;
+ // Instance id for logging purpose.
+ private int mInstanceId;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/**
@@ -468,6 +474,7 @@
mRecommendationViewHolder.getCardIcon().setColorFilter(primaryColor);
mRecommendationViewHolder.getCardText().setTextColor(primaryColor);
+ mInstanceId = target.getSmartspaceTargetId().hashCode();
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
mBackgroundColor = backgroundColor;
@@ -486,8 +493,10 @@
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
MEDIA_RECOMMENDATION_MAX_NUM);
- for (int i = 0; i < mediaRecommendationNum; i++) {
- SmartspaceAction recommendation = mediaRecommendationList.get(i);
+ for (int itemIndex = 0, uiComponentIndex = 0;
+ itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
+ itemIndex++) {
+ SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex);
if (recommendation.getIcon() == null) {
Log.w(TAG, "No media cover is provided. Skipping this item...");
continue;
@@ -511,31 +520,38 @@
}
// Set up media source app's logo.
- ImageView mediaSourceLogoImageView = mediaLogoItems.get(i);
+ ImageView mediaSourceLogoImageView = mediaLogoItems.get(uiComponentIndex);
mediaSourceLogoImageView.setImageDrawable(icon);
// TODO(b/186699032): Tint the app logo using the accent color.
mediaSourceLogoImageView.setColorFilter(backgroundColor, PorterDuff.Mode.XOR);
// Set up media item cover.
- ImageView mediaCoverImageView = mediaCoverItems.get(i);
+ ImageView mediaCoverImageView = mediaCoverItems.get(uiComponentIndex);
mediaCoverImageView.setImageIcon(recommendation.getIcon());
// Set up the click listener if applicable.
setSmartspaceRecItemOnClickListener(
mediaCoverImageView,
recommendation,
- target.getSmartspaceTargetId(),
null);
- if (i < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
- setVisibleAndAlpha(collapsedSet, mediaCoverItemsResIds.get(i), true);
- setVisibleAndAlpha(collapsedSet, mediaLogoItemsResIds.get(i), true);
+ if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
+ setVisibleAndAlpha(collapsedSet,
+ mediaCoverItemsResIds.get(uiComponentIndex), true);
+ setVisibleAndAlpha(collapsedSet,
+ mediaLogoItemsResIds.get(uiComponentIndex), true);
} else {
- setVisibleAndAlpha(collapsedSet, mediaCoverItemsResIds.get(i), false);
- setVisibleAndAlpha(collapsedSet, mediaLogoItemsResIds.get(i), false);
+ setVisibleAndAlpha(collapsedSet,
+ mediaCoverItemsResIds.get(uiComponentIndex), false);
+ setVisibleAndAlpha(collapsedSet,
+ mediaLogoItemsResIds.get(uiComponentIndex), false);
}
- setVisibleAndAlpha(expandedSet, mediaCoverItemsResIds.get(i), true);
- setVisibleAndAlpha(expandedSet, mediaLogoItemsResIds.get(i), true);
+ setVisibleAndAlpha(expandedSet,
+ mediaCoverItemsResIds.get(uiComponentIndex), true);
+ setVisibleAndAlpha(expandedSet,
+ mediaLogoItemsResIds.get(uiComponentIndex), true);
+
+ uiComponentIndex++;
}
// Set up long press to show guts setting panel.
@@ -635,7 +651,6 @@
private void setSmartspaceRecItemOnClickListener(
@NonNull View view,
@NonNull SmartspaceAction action,
- @NonNull String targetId,
@Nullable View.OnClickListener callback) {
if (view == null || action == null || action.getIntent() == null) {
Log.e(TAG, "No tap action can be set up");
@@ -646,24 +661,60 @@
// When media recommendation card is shown, there could be only one card.
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
760, // SMARTSPACE_CARD_CLICK
- targetId.hashCode(),
+ mInstanceId,
SysUiStatsLog
.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
- getSurfaceForSmartspaceLogging(mMediaViewController.getCurrentEndLocation()),
- /* rank */ 1,
+ getSurfaceForSmartspaceLogging(),
+ /* rank */ 0,
/* cardinality */ 1);
- mActivityStarter.postStartActivityDismissingKeyguard(
- action.getIntent(),
- 0 /* delay */,
- buildLaunchAnimatorController(mRecommendationViewHolder.getRecommendations()));
+ if (shouldSmartspaceRecItemOpenInForeground(action)) {
+ // Request to unlock the device if the activity needs to be opened in foreground.
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ action.getIntent(),
+ 0 /* delay */,
+ buildLaunchAnimatorController(
+ mRecommendationViewHolder.getRecommendations()));
+ } else {
+ // Otherwise, open the activity in background directly.
+ view.getContext().startActivity(action.getIntent());
+ }
+
if (callback != null) {
callback.onClick(v);
}
});
}
- private int getSurfaceForSmartspaceLogging(int currentEndLocation) {
+ /** Returns if the Smartspace action will open the activity in foreground. */
+ private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
+ if (action == null || action.getIntent() == null
+ || action.getIntent().getExtras() == null) {
+ return false;
+ }
+
+ String intentString = action.getIntent().getExtras().getString(EXTRAS_SMARTSPACE_INTENT);
+ if (intentString == null) {
+ return false;
+ }
+
+ try {
+ Intent wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
+ return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false);
+ } catch (URISyntaxException e) {
+ Log.wtf(TAG, "Failed to create intent from URI: " + intentString);
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the surface given the current end location for MediaViewController
+ * @return surface used for Smartspace logging
+ */
+ protected int getSurfaceForSmartspaceLogging() {
+ int currentEndLocation = mMediaViewController.getCurrentEndLocation();
if (currentEndLocation == MediaHierarchyManager.LOCATION_QQS
|| currentEndLocation == MediaHierarchyManager.LOCATION_QS) {
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE;
@@ -672,4 +723,8 @@
}
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
}
+
+ protected int getInstanceId() {
+ return mInstanceId;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 30bc8c1..a80a410 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
import javax.inject.Inject
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
/**
* Similarly to isShown but also excludes views that have 0 alpha
@@ -73,7 +74,8 @@
private val bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
- wakefulnessLifecycle: WakefulnessLifecycle
+ wakefulnessLifecycle: WakefulnessLifecycle,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
) {
/**
* The root overlay of the hierarchy. This is where the media notification is attached to
@@ -162,6 +164,26 @@
}
/**
+ * Is quick setting expanded?
+ */
+ var qsExpanded: Boolean = false
+ set(value) {
+ if (field != value) {
+ field = value
+ }
+ // Pull down shade from lock screen (exclude the case when shade is brought out by
+ // tapping twice on lock screen)
+ if (value && isLockScreenShadeVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression()
+ }
+ // Release shade and back to lock screen
+ if (isLockScreenVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression()
+ }
+ mediaCarouselController.visibleToUser = isVisibleToUser()
+ }
+
+ /**
* Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
* we wouldn't want to transition in that case.
*/
@@ -231,6 +253,11 @@
override fun onStateChanged(newState: Int) {
updateTargetState()
+ // Enters shade from lock screen
+ if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression()
+ }
+ mediaCarouselController.visibleToUser = isVisibleToUser()
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -240,9 +267,27 @@
override fun onDozingChanged(isDozing: Boolean) {
if (!isDozing) {
dozeAnimationRunning = false
+ // Enters lock screen from screen off
+ if (isLockScreenVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression()
+ }
} else {
updateDesiredLocation()
+ qsExpanded = false
}
+ mediaCarouselController.visibleToUser = isVisibleToUser()
+ }
+
+ override fun onExpandedChanged(isExpanded: Boolean) {
+ // Enters shade from home screen
+ if (isHomeScreenShadeVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression()
+ }
+ // Back to lock screen from bouncer
+ if (isLockScreenVisibleToUser()) {
+ mediaCarouselController.logSmartspaceImpression()
+ }
+ mediaCarouselController.visibleToUser = isVisibleToUser()
}
})
@@ -622,6 +667,36 @@
return location
}
+ /**
+ * Returns true when the media card could be visible to the user if existed.
+ */
+ private fun isVisibleToUser(): Boolean {
+ return isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() ||
+ isHomeScreenShadeVisibleToUser()
+ }
+
+ private fun isLockScreenVisibleToUser(): Boolean {
+ return !statusBarStateController.isDozing &&
+ !statusBarKeyguardViewManager.isBouncerShowing &&
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ notifLockscreenUserManager.shouldShowLockscreenNotifications() &&
+ statusBarStateController.isExpanded &&
+ !qsExpanded
+ }
+
+ private fun isLockScreenShadeVisibleToUser(): Boolean {
+ return !statusBarStateController.isDozing &&
+ !statusBarKeyguardViewManager.isBouncerShowing &&
+ (statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
+ (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
+ }
+
+ private fun isHomeScreenShadeVisibleToUser(): Boolean {
+ return !statusBarStateController.isDozing &&
+ statusBarStateController.state == StatusBarState.SHADE &&
+ statusBarStateController.isExpanded
+ }
+
companion object {
/**
* Attached in expanded quick settings
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
index a964056..0f66456 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
@@ -30,12 +30,15 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.shared.system.PeopleProviderUtils;
+import javax.inject.Inject;
+
/** API that returns a People Tile preview. */
public class PeopleProvider extends ContentProvider {
private static final String TAG = "PeopleProvider";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private static final String EMPTY_STRING = "";
+ @Inject
PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
@Override
@@ -76,7 +79,8 @@
}
if (mPeopleSpaceWidgetManager == null) {
- mPeopleSpaceWidgetManager = new PeopleSpaceWidgetManager(getContext());
+ Log.e(TAG, "Could not initialize people widget manager");
+ return null;
}
RemoteViews view =
mPeopleSpaceWidgetManager.getPreview(shortcutId, userHandle, packageName, extras);
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 99a17ffb..a6c6103 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -23,11 +23,11 @@
import static com.android.systemui.people.NotificationHelper.isMissedCall;
import static com.android.systemui.people.NotificationHelper.shouldMatchNotificationByUri;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
-import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
@@ -40,13 +40,11 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Bundle;
import android.os.UserManager;
import android.provider.ContactsContract;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Log;
-import android.widget.RemoteViews;
import androidx.preference.PreferenceManager;
@@ -56,7 +54,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
-import com.android.systemui.people.widget.AppWidgetOptionsHelper;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -76,45 +74,17 @@
public class PeopleSpaceUtils {
/** Turns on debugging information about People Space. */
public static final boolean DEBUG = true;
- private static final String TAG = "PeopleSpaceUtils";
public static final String PACKAGE_NAME = "package_name";
public static final String USER_ID = "user_id";
public static final String SHORTCUT_ID = "shortcut_id";
-
public static final String EMPTY_STRING = "";
public static final int INVALID_USER_ID = -1;
-
public static final PeopleTileKey EMPTY_KEY =
new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING);
-
- /** Represents whether {@link StatusBarNotification} was posted or removed. */
- public enum NotificationAction {
- POSTED,
- REMOVED
- }
-
- /**
- * The UiEvent enums that this class can log.
- */
- public enum PeopleSpaceWidgetEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "People space widget deleted")
- PEOPLE_SPACE_WIDGET_DELETED(666),
- @UiEvent(doc = "People space widget added")
- PEOPLE_SPACE_WIDGET_ADDED(667),
- @UiEvent(doc = "People space widget clicked to launch conversation")
- PEOPLE_SPACE_WIDGET_CLICKED(668);
-
- private final int mId;
-
- PeopleSpaceWidgetEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
+ static final float STARRED_CONTACT = 1f;
+ static final float VALID_CONTACT = .5f;
+ static final float DEFAULT_AFFINITY = 0f;
+ private static final String TAG = "PeopleSpaceUtils";
/** Returns stored widgets for the conversation specified. */
public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) {
@@ -127,6 +97,10 @@
/** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
int appWidgetId, Uri contactUri) {
+ if (!key.isValid()) {
+ Log.e(TAG, "Not storing for invalid key");
+ return;
+ }
// Write relevant persisted storage.
SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
Context.MODE_PRIVATE);
@@ -201,7 +175,7 @@
.collect(Collectors.toList());
}
- /** Returns the total messages in {@code notificationEntries}.*/
+ /** Returns the total messages in {@code notificationEntries}. */
public static int getMessagesCount(Set<NotificationEntry> notificationEntries) {
if (DEBUG) {
Log.d(TAG, "Calculating messages count from " + notificationEntries.size()
@@ -247,19 +221,34 @@
* {@code messagesCount}.
*/
public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile,
- PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount) {
+ PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount,
+ Optional<Integer> appWidgetId) {
if (notificationEntry == null || notificationEntry.getSbn().getNotification() == null) {
if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification is null");
return removeNotificationFields(tile);
}
Notification notification = notificationEntry.getSbn().getNotification();
+
+ PeopleSpaceTile.Builder updatedTile = tile.toBuilder();
+ String uriFromNotification = getContactUri(notificationEntry.getSbn());
+ if (appWidgetId.isPresent() && tile.getContactUri() == null && !TextUtils.isEmpty(
+ uriFromNotification)) {
+ if (DEBUG) Log.d(TAG, "Add uri from notification to tile: " + uriFromNotification);
+ Uri contactUri = Uri.parse(uriFromNotification);
+ // Update storage.
+ setSharedPreferencesStorageForTile(context, new PeopleTileKey(tile), appWidgetId.get(),
+ contactUri);
+ // Update cached tile in-memory.
+ updatedTile.setContactUri(contactUri);
+ }
+
boolean isMissedCall = isMissedCall(notification);
List<Notification.MessagingStyle.Message> messages =
getMessagingStyleMessages(notification);
if (!isMissedCall && ArrayUtils.isEmpty(messages)) {
if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification has no content");
- return removeNotificationFields(tile);
+ return removeNotificationFields(updatedTile.build());
}
// messages are in chronological order from most recent to least.
@@ -276,8 +265,7 @@
}
CharSequence sender = getSenderIfGroupConversation(notification, message);
- return tile
- .toBuilder()
+ return updatedTile
.setNotificationKey(notificationEntry.getSbn().getKey())
.setNotificationCategory(notification.category)
.setNotificationContent(content)
@@ -378,17 +366,18 @@
context.getString(R.string.birthday_status));
}
- /** Calls to retrieve birthdays on a background thread. */
- public static void getBirthdaysOnBackgroundThread(Context context,
- AppWidgetManager appWidgetManager,
+ /** Calls to retrieve birthdays & contact affinity on a background thread. */
+ public static void getDataFromContactsOnBackgroundThread(Context context,
+ PeopleSpaceWidgetManager manager,
Map<Integer, PeopleSpaceTile> peopleSpaceTiles, int[] appWidgetIds) {
ThreadUtils.postOnBackgroundThread(
- () -> getBirthdays(context, appWidgetManager, peopleSpaceTiles, appWidgetIds));
+ () -> getDataFromContacts(context, manager, peopleSpaceTiles, appWidgetIds));
}
- /** Queries the Contacts DB for any birthdays today. */
+ /** Queries the Contacts DB for any birthdays today & updates contact affinity. */
@VisibleForTesting
- public static void getBirthdays(Context context, AppWidgetManager appWidgetManager,
+ public static void getDataFromContacts(Context context,
+ PeopleSpaceWidgetManager peopleSpaceWidgetManager,
Map<Integer, PeopleSpaceTile> widgetIdToTile, int[] appWidgetIds) {
if (DEBUG) Log.d(TAG, "Get birthdays");
if (appWidgetIds.length == 0) return;
@@ -397,28 +386,37 @@
PeopleSpaceTile storedTile = widgetIdToTile.get(appWidgetId);
if (storedTile == null || storedTile.getContactUri() == null) {
if (DEBUG) Log.d(TAG, "No contact uri for: " + storedTile);
- removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
+ updateTileContactFields(peopleSpaceWidgetManager, context, storedTile,
+ appWidgetId, DEFAULT_AFFINITY, /* birthdayString= */ null);
continue;
}
- if (lookupKeysWithBirthdaysToday.isEmpty()) {
- if (DEBUG) Log.d(TAG, "No birthdays today");
- removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
- continue;
- }
- updateTileWithBirthday(context, appWidgetManager, lookupKeysWithBirthdaysToday,
+ updateTileWithBirthdayAndUpdateAffinity(context, peopleSpaceWidgetManager,
+ lookupKeysWithBirthdaysToday,
storedTile,
appWidgetId);
}
}
- /** Removes the birthday status if present in {@code storedTile} and pushes the update. */
- private static void removeBirthdayStatusIfPresent(AppWidgetManager appWidgetManager,
- Context context, PeopleSpaceTile storedTile, int appWidgetId) {
- if (hasBirthdayStatus(storedTile, context)) {
- if (DEBUG) Log.d(TAG, "Remove " + storedTile.getUserName() + "'s birthday");
- updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId,
+ /**
+ * Updates the {@code storedTile} with {@code affinity} & {@code birthdayString} if
+ * necessary.
+ */
+ private static void updateTileContactFields(PeopleSpaceWidgetManager manager,
+ Context context, PeopleSpaceTile storedTile, int appWidgetId, float affinity,
+ @Nullable String birthdayString) {
+ boolean outdatedBirthdayStatus = hasBirthdayStatus(storedTile, context)
+ && birthdayString == null;
+ boolean addBirthdayStatus = !hasBirthdayStatus(storedTile, context)
+ && birthdayString != null;
+ boolean shouldUpdate =
+ storedTile.getContactAffinity() != affinity || outdatedBirthdayStatus
+ || addBirthdayStatus;
+ if (shouldUpdate) {
+ if (DEBUG) Log.d(TAG, "Update " + storedTile.getUserName() + " from contacts");
+ manager.updateAppWidgetOptionsAndView(appWidgetId,
storedTile.toBuilder()
- .setBirthdayText(null)
+ .setBirthdayText(birthdayString)
+ .setContactAffinity(affinity)
.build());
}
}
@@ -427,7 +425,8 @@
* Update {@code storedTile} if the contact has a lookup key matched to any {@code
* lookupKeysWithBirthdays}.
*/
- private static void updateTileWithBirthday(Context context, AppWidgetManager appWidgetManager,
+ private static void updateTileWithBirthdayAndUpdateAffinity(Context context,
+ PeopleSpaceWidgetManager manager,
List<String> lookupKeysWithBirthdaysToday, PeopleSpaceTile storedTile,
int appWidgetId) {
Cursor cursor = null;
@@ -437,14 +436,16 @@
while (cursor != null && cursor.moveToNext()) {
String storedLookupKey = cursor.getString(
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.LOOKUP_KEY));
+ float affinity = getContactAffinity(cursor);
if (!storedLookupKey.isEmpty() && lookupKeysWithBirthdaysToday.contains(
storedLookupKey)) {
if (DEBUG) Log.d(TAG, storedTile.getUserName() + "'s birthday today!");
- updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId,
- storedTile.toBuilder()
- .setBirthdayText(context.getString(R.string.birthday_status))
- .build());
- return;
+ updateTileContactFields(manager, context, storedTile, appWidgetId,
+ affinity, /* birthdayString= */
+ context.getString(R.string.birthday_status));
+ } else {
+ updateTileContactFields(manager, context, storedTile, appWidgetId,
+ affinity, /* birthdayString= */ null);
}
}
} catch (SQLException e) {
@@ -454,51 +455,20 @@
cursor.close();
}
}
- removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
}
- /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
- public static void updateAppWidgetViews(AppWidgetManager appWidgetManager,
- Context context, int appWidgetId, PeopleSpaceTile tile, Bundle options) {
- if (tile == null) {
- if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ". Tile is null, skipping update");
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName() + ", "
- + tile.getPackageName() + ". Updating app widget view.");
- }
- RemoteViews views = new PeopleTileViewHelper(context, tile, appWidgetId,
- options).getViews();
-
- // Tell the AppWidgetManager to perform an update on the current app widget.
- appWidgetManager.updateAppWidget(appWidgetId, views);
- }
-
- /** Updates tile in app widget options and the current view. */
- public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
- Context context, int appWidgetId, PeopleSpaceTile tile) {
- if (tile == null) {
- if (DEBUG) {
- Log.w(TAG, "Widget: " + appWidgetId + "Tile is null, skipping storage and update.");
+ /** Pulls the contact affinity from {@code cursor}. */
+ private static float getContactAffinity(Cursor cursor) {
+ float affinity = VALID_CONTACT;
+ int starIdx = cursor.getColumnIndex(ContactsContract.Contacts.STARRED);
+ if (starIdx >= 0) {
+ boolean isStarred = cursor.getInt(starIdx) != 0;
+ if (isStarred) {
+ affinity = Math.max(affinity, STARRED_CONTACT);
}
- return;
}
- Bundle options = AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
- updateAppWidgetViews(appWidgetManager, context, appWidgetId, tile, options);
- }
-
- /** Wrapper around {@link #updateAppWidgetOptionsAndView} with optional tile as a parameter. */
- public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
- Context context, int appWidgetId, Optional<PeopleSpaceTile> optionalTile) {
- if (!optionalTile.isPresent()) {
- if (DEBUG) {
- Log.w(TAG, "Widget: " + appWidgetId
- + "Optional tile is not present, skipping storage and update.");
- }
- return;
- }
- updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, optionalTile.get());
+ if (DEBUG) Log.d(TAG, "Affinity is: " + affinity);
+ return affinity;
}
/**
@@ -546,4 +516,33 @@
public static int getUserId(PeopleSpaceTile tile) {
return tile.getUserHandle().getIdentifier();
}
+
+ /** Represents whether {@link StatusBarNotification} was posted or removed. */
+ public enum NotificationAction {
+ POSTED,
+ REMOVED
+ }
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ public enum PeopleSpaceWidgetEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "People space widget deleted")
+ PEOPLE_SPACE_WIDGET_DELETED(666),
+ @UiEvent(doc = "People space widget added")
+ PEOPLE_SPACE_WIDGET_ADDED(667),
+ @UiEvent(doc = "People space widget clicked to launch conversation")
+ PEOPLE_SPACE_WIDGET_CLICKED(668);
+
+ private final int mId;
+
+ PeopleSpaceWidgetEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index a23db63..6980d72 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -30,6 +30,8 @@
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
+import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
import static com.android.systemui.people.PeopleSpaceUtils.getUserId;
@@ -39,6 +41,8 @@
import android.app.people.PeopleSpaceTile;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
@@ -48,6 +52,7 @@
import android.icu.util.MeasureUnit;
import android.net.Uri;
import android.os.Bundle;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
@@ -57,6 +62,8 @@
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -142,7 +149,9 @@
private int mMediumVerticalPadding;
private Context mContext;
+ @Nullable
private PeopleSpaceTile mTile;
+ private PeopleTileKey mKey;
private float mDensity;
private int mAppWidgetId;
private int mWidth;
@@ -152,10 +161,11 @@
private Locale mLocale;
private NumberFormat mIntegerFormat;
- public PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
- int appWidgetId, Bundle options) {
+ public PeopleTileViewHelper(Context context, @Nullable PeopleSpaceTile tile,
+ int appWidgetId, Bundle options, PeopleTileKey key) {
mContext = context;
mTile = tile;
+ mKey = key;
mAppWidgetId = appWidgetId;
mDensity = mContext.getResources().getDisplayMetrics().density;
int display = mContext.getResources().getConfiguration().orientation;
@@ -184,8 +194,19 @@
* content, then birthdays, then the most recent status, and finally last interaction.
*/
private RemoteViews getViewForTile() {
- PeopleTileKey key = new PeopleTileKey(mTile);
- if (DEBUG) Log.d(TAG, "Creating view for tile key: " + key.toString());
+ if (DEBUG) Log.d(TAG, "Creating view for tile key: " + mKey.toString());
+ if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()) {
+ if (DEBUG) Log.d(TAG, "Create empty view: " + mTile);
+ return createEmptyView();
+ }
+
+ boolean dndBlockingTileData = isDndBlockingTileData(mTile);
+ if (dndBlockingTileData) {
+ if (DEBUG) Log.d(TAG, "Create DND view: " + mTile.getNotificationPolicyState());
+ // TODO: Create DND view.
+ return createEmptyView();
+ }
+
if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
if (DEBUG) Log.d(TAG, "Create missed call view");
return createMissedCallRemoteViews();
@@ -217,6 +238,58 @@
return createLastInteractionRemoteViews();
}
+ private boolean isDndBlockingTileData(PeopleSpaceTile tile) {
+ int notificationPolicyState = tile.getNotificationPolicyState();
+ if ((notificationPolicyState & PeopleSpaceTile.SHOW_CONVERSATIONS) != 0) {
+ // Not in DND, or all conversations
+ if (DEBUG) Log.d(TAG, "Tile can show all data: " + tile.getUserName());
+ return false;
+ }
+ if ((notificationPolicyState & PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS) != 0
+ && tile.isImportantConversation()) {
+ if (DEBUG) Log.d(TAG, "Tile can show important: " + tile.getUserName());
+ return false;
+ }
+ if ((notificationPolicyState & PeopleSpaceTile.SHOW_STARRED_CONTACTS) != 0
+ && tile.getContactAffinity() == STARRED_CONTACT) {
+ if (DEBUG) Log.d(TAG, "Tile can show starred: " + tile.getUserName());
+ return false;
+ }
+ if ((notificationPolicyState & PeopleSpaceTile.SHOW_CONTACTS) != 0
+ && (tile.getContactAffinity() == VALID_CONTACT
+ || tile.getContactAffinity() == STARRED_CONTACT)) {
+ if (DEBUG) Log.d(TAG, "Tile can show contacts: " + tile.getUserName());
+ return false;
+ }
+ if (DEBUG) Log.d(TAG, "Tile can show if can bypass DND: " + tile.getUserName());
+ return !tile.canBypassDnd();
+ }
+
+ private RemoteViews createEmptyView() {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(),
+ R.layout.people_tile_empty_layout);
+ Drawable appIcon = getAppBadge(mKey.getPackageName(), mKey.getUserId());
+ Bitmap appIconAsBitmap = convertDrawableToBitmap(appIcon);
+ FastBitmapDrawable drawable = new FastBitmapDrawable(
+ appIconAsBitmap);
+ drawable.setIsDisabled(true);
+ Bitmap convertedBitmap = convertDrawableToBitmap(drawable);
+ views.setImageViewBitmap(R.id.item, convertedBitmap);
+ return views;
+ }
+
+ private Drawable getAppBadge(String packageName, int userId) {
+ Drawable badge = null;
+ try {
+ final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userId);
+ badge = Utils.getBadgedIcon(mContext, appInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ badge = mContext.getPackageManager().getDefaultActivityIcon();
+ }
+ return badge;
+ }
+
private void setMaxLines(RemoteViews views, boolean showSender) {
int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
R.dimen.content_text_size_for_medium)
@@ -337,6 +410,9 @@
private RemoteViews setCommonRemoteViewsFields(RemoteViews views,
int maxAvatarSize) {
try {
+ if (mTile == null) {
+ return views;
+ }
boolean isAvailable =
mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
@@ -367,13 +443,16 @@
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NO_HISTORY
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mTile.getId());
+ activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mKey.getShortcutId());
activityIntent.putExtra(
- PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mTile.getPackageName());
+ PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mKey.getPackageName());
activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
- mTile.getUserHandle());
- activityIntent.putExtra(
- PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, mTile.getNotificationKey());
+ new UserHandle(mKey.getUserId()));
+ if (mTile != null) {
+ activityIntent.putExtra(
+ PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+ mTile.getNotificationKey());
+ }
views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
mContext,
mAppWidgetId,
@@ -727,6 +806,9 @@
c -> c.getActivity() == ACTIVITY_NEW_STORY);
Icon icon = tile.getUserIcon();
+ if (icon == null) {
+ return null;
+ }
PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
context.getPackageManager(),
IconDrawableFactory.newInstance(context, false),
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 64a6509..ea1724f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -17,6 +17,12 @@
package com.android.systemui.people.widget;
import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static com.android.systemui.people.NotificationHelper.getContactUri;
import static com.android.systemui.people.NotificationHelper.getHighestPriorityNotification;
@@ -30,13 +36,12 @@
import static com.android.systemui.people.PeopleSpaceUtils.getMessagesCount;
import static com.android.systemui.people.PeopleSpaceUtils.getNotificationsByUri;
import static com.android.systemui.people.PeopleSpaceUtils.removeNotificationFields;
-import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView;
-import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
import android.app.people.ConversationChannel;
@@ -44,8 +49,11 @@
import android.app.people.PeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
@@ -60,6 +68,8 @@
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.RemoteViews;
@@ -67,8 +77,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.Dependency;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.people.NotificationHelper;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.people.PeopleTileViewHelper;
@@ -85,15 +96,15 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
-import javax.inject.Singleton;
/** Manager for People Space widget. */
-@Singleton
+@SysUISingleton
public class PeopleSpaceWidgetManager {
private static final String TAG = "PeopleSpaceWidgetMgr";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
@@ -107,12 +118,15 @@
private PeopleManager mPeopleManager;
private NotificationEntryManager mNotificationEntryManager;
private PackageManager mPackageManager;
- private PeopleSpaceWidgetProvider mPeopleSpaceWidgetProvider;
private INotificationManager mINotificationManager;
private UserManager mUserManager;
+ private PeopleSpaceWidgetManager mManager;
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ private NotificationManager mNotificationManager;
+ private BroadcastDispatcher mBroadcastDispatcher;
+ private Executor mBgExecutor;
@GuardedBy("mLock")
- public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener>
+ public static Map<PeopleTileKey, TileConversationListener>
mListeners = new HashMap<>();
@GuardedBy("mLock")
@@ -120,46 +134,91 @@
// This is required because on notification removal, the contact Uri field is stripped and we
// only have the notification key to determine which widget IDs should be updated.
private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>();
- private boolean mIsForTesting;
+ private boolean mRegisteredReceivers;
@Inject
- public PeopleSpaceWidgetManager(Context context) {
+ public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
+ NotificationEntryManager notificationEntryManager,
+ PackageManager packageManager, UserManager userManager,
+ NotificationManager notificationManager, BroadcastDispatcher broadcastDispatcher,
+ @Background Executor bgExecutor) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
mAppWidgetManager = AppWidgetManager.getInstance(context);
mIPeopleManager = IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE));
- mLauncherApps = context.getSystemService(LauncherApps.class);
+ mLauncherApps = launcherApps;
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- mPeopleManager = mContext.getSystemService(PeopleManager.class);
- mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
- mPackageManager = mContext.getPackageManager();
- mPeopleSpaceWidgetProvider = new PeopleSpaceWidgetProvider();
+ mPeopleManager = context.getSystemService(PeopleManager.class);
+ mNotificationEntryManager = notificationEntryManager;
+ mPackageManager = packageManager;
mINotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- mUserManager = context.getSystemService(UserManager.class);
+ mUserManager = userManager;
+ mNotificationManager = notificationManager;
+ mManager = this;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mBgExecutor = bgExecutor;
+ }
+
+ /** Initializes {@PeopleSpaceWidgetManager}. */
+ public void init() {
+ synchronized (mLock) {
+ if (!mRegisteredReceivers) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+ filter.addAction(ACTION_BOOT_COMPLETED);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+ filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter,
+ null /* executor */, UserHandle.ALL);
+ mRegisteredReceivers = true;
+ }
+ }
+ }
+
+ /** Listener for the shortcut data changes. */
+ public class TileConversationListener implements PeopleManager.ConversationListener {
+
+ @Override
+ public void onConversationUpdate(@NonNull ConversationChannel conversation) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "Received updated conversation: "
+ + conversation.getShortcutInfo().getLabel());
+ }
+ updateWidgetsWithConversationChanged(conversation);
+ }
}
/**
- * AppWidgetManager setter used for testing.
+ * PeopleSpaceWidgetManager setter used for testing.
*/
@VisibleForTesting
- public void setAppWidgetManager(
+ PeopleSpaceWidgetManager(Context context,
AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
NotificationEntryManager notificationEntryManager, PackageManager packageManager,
- boolean isForTesting, PeopleSpaceWidgetProvider peopleSpaceWidgetProvider,
- UserManager userManager, INotificationManager notificationManager) {
+ UserManager userManager, INotificationManager iNotificationManager,
+ NotificationManager notificationManager, @Background Executor executor) {
+ mContext = context;
mAppWidgetManager = appWidgetManager;
mIPeopleManager = iPeopleManager;
mPeopleManager = peopleManager;
mLauncherApps = launcherApps;
mNotificationEntryManager = notificationEntryManager;
mPackageManager = packageManager;
- mIsForTesting = isForTesting;
- mPeopleSpaceWidgetProvider = peopleSpaceWidgetProvider;
mUserManager = userManager;
- mINotificationManager = notificationManager;
+ mINotificationManager = iNotificationManager;
+ mNotificationManager = notificationManager;
+ mManager = this;
+ mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ mBgExecutor = executor;
}
/**
@@ -173,7 +232,7 @@
return;
}
- if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets");
+ if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets: " + widgetIds);
synchronized (mLock) {
updateSingleConversationWidgets(widgetIds);
}
@@ -191,16 +250,47 @@
for (int appWidgetId : appWidgetIds) {
PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
if (tile == null) {
- if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
- //TODO: Delete app widget id when crash is fixed (b/172932636)
- continue;
+ Log.e(TAG, "Matching conversation not found for shortcut ID");
}
Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
- updateAppWidgetViews(mAppWidgetManager, mContext, appWidgetId, tile, options);
+ updateAppWidgetViews(appWidgetId, tile, options);
widgetIdToTile.put(appWidgetId, tile);
+ if (tile != null) {
+ registerConversationListenerIfNeeded(appWidgetId,
+ new PeopleTileKey(tile));
+ }
}
- PeopleSpaceUtils.getBirthdaysOnBackgroundThread(
- mContext, mAppWidgetManager, widgetIdToTile, appWidgetIds);
+ PeopleSpaceUtils.getDataFromContactsOnBackgroundThread(
+ mContext, mManager, widgetIdToTile, appWidgetIds);
+ }
+
+ /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
+ private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
+ PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
+ if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
+ if (!key.isValid()) {
+ Log.e(TAG, "Cannot update invalid widget");
+ return;
+ }
+ RemoteViews views = new PeopleTileViewHelper(mContext, tile, appWidgetId,
+ options, key).getViews();
+
+ // Tell the AppWidgetManager to perform an update on the current app widget.
+ mAppWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+
+ /** Updates tile in app widget options and the current view. */
+ public void updateAppWidgetOptionsAndViewOptional(int appWidgetId,
+ Optional<PeopleSpaceTile> tile) {
+ if (tile.isPresent()) {
+ updateAppWidgetOptionsAndView(appWidgetId, tile.get());
+ }
+ }
+
+ /** Updates tile in app widget options and the current view. */
+ public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+ Bundle options = AppWidgetOptionsHelper.setPeopleTile(mAppWidgetManager, appWidgetId, tile);
+ updateAppWidgetViews(appWidgetId, tile, options);
}
/**
@@ -226,7 +316,7 @@
widgetSp.getInt(USER_ID, INVALID_USER_ID),
widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
- return getTileFromPersistentStorage(key);
+ return getTileFromPersistentStorage(key, appWidgetId);
}
/**
@@ -234,7 +324,7 @@
* If a {@link PeopleTileKey} is not provided, fetch one from {@link SharedPreferences}.
*/
@Nullable
- public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key) {
+ public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId) {
if (!key.isValid()) {
Log.e(TAG, "PeopleTileKey invalid: " + key.toString());
return null;
@@ -254,7 +344,22 @@
return null;
}
- return new PeopleSpaceTile.Builder(channel, mLauncherApps).build();
+ // Get tile from shortcut & conversation storage.
+ PeopleSpaceTile.Builder storedTile = new PeopleSpaceTile.Builder(channel,
+ mLauncherApps);
+ if (storedTile == null) {
+ return storedTile.build();
+ }
+
+ // Supplement with our storage.
+ String contactUri = mSharedPrefs.getString(String.valueOf(appWidgetId), null);
+ if (contactUri != null && storedTile.build().getContactUri() == null) {
+ if (DEBUG) Log.d(TAG, "Restore contact uri from storage: " + contactUri);
+ storedTile.setContactUri(Uri.parse(contactUri));
+ }
+
+ // Add current state.
+ return updateWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
} catch (Exception e) {
Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
return null;
@@ -275,11 +380,7 @@
Log.d(TAG, "Notification removed, key: " + sbn.getKey());
}
}
- if (mIsForTesting) {
- updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction);
- return;
- }
- ThreadUtils.postOnBackgroundThread(
+ mBgExecutor.execute(
() -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
}
@@ -331,8 +432,7 @@
.collect(Collectors.toMap(
Function.identity(),
id -> getAugmentedTileForExistingWidget(id, groupedNotifications)))
- .forEach((id, tile) ->
- updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, id, tile));
+ .forEach((id, tile) -> updateAppWidgetOptionsAndViewOptional(id, tile));
} catch (Exception e) {
Log.e(TAG, "Exception updating widgets: " + e);
}
@@ -341,16 +441,20 @@
/**
* Augments {@code tile} based on notifications returned from {@code notificationEntryManager}.
*/
- public PeopleSpaceTile augmentTileFromNotificationEntryManager(PeopleSpaceTile tile) {
+ public PeopleSpaceTile augmentTileFromNotificationEntryManager(PeopleSpaceTile tile,
+ Optional<Integer> appWidgetId) {
PeopleTileKey key = new PeopleTileKey(tile);
- Log.d(TAG, "Augmenting tile from NotificationEntryManager widget: " + key.toString());
+ if (DEBUG) {
+ Log.d(TAG,
+ "Augmenting tile from NotificationEntryManager widget: " + key.toString());
+ }
Map<PeopleTileKey, Set<NotificationEntry>> notifications =
getGroupedConversationNotifications();
String contactUri = null;
if (tile.getContactUri() != null) {
contactUri = tile.getContactUri().toString();
}
- return augmentTileFromNotifications(tile, key, contactUri, notifications);
+ return augmentTileFromNotifications(tile, key, contactUri, notifications, appWidgetId);
}
/** Returns active and pending notifications grouped by {@link PeopleTileKey}. */
@@ -380,9 +484,11 @@
/** Augments {@code tile} based on {@code notifications}, matching {@code contactUri}. */
public PeopleSpaceTile augmentTileFromNotifications(PeopleSpaceTile tile, PeopleTileKey key,
- String contactUri, Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
+ String contactUri,
+ Map<PeopleTileKey, Set<NotificationEntry>> notifications,
+ Optional<Integer> appWidgetId) {
if (DEBUG) Log.d(TAG, "Augmenting tile from notifications. Tile key: " + key.toString());
- boolean hasReadContactsPermission = mPackageManager.checkPermission(READ_CONTACTS,
+ boolean hasReadContactsPermission = mPackageManager.checkPermission(READ_CONTACTS,
tile.getPackageName()) == PackageManager.PERMISSION_GRANTED;
List<NotificationEntry> notificationsByUri = new ArrayList<>();
@@ -413,7 +519,8 @@
NotificationEntry highestPriority = getHighestPriorityNotification(allNotifications);
if (DEBUG) Log.d(TAG, "Augmenting tile from notification, key: " + key.toString());
- return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount);
+ return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount,
+ appWidgetId);
}
/** Returns an augmented tile for an existing widget. */
@@ -434,7 +541,8 @@
PeopleTileKey key = new PeopleTileKey(tile);
if (DEBUG) Log.d(TAG, "Existing widget: " + widgetId + ". Tile key: " + key.toString());
return Optional.ofNullable(
- augmentTileFromNotifications(tile, key, contactUriString, notifications));
+ augmentTileFromNotifications(tile, key, contactUriString, notifications,
+ Optional.of(widgetId)));
}
/** Returns stored widgets for the conversation specified. */
@@ -552,10 +660,8 @@
.setStatuses(conversation.getStatuses())
.setLastInteractionTimestamp(conversation.getLastEventTimestamp())
.setIsImportantConversation(conversation.getParentNotificationChannel() != null
- && conversation.getParentNotificationChannel().isImportantConversation())
- .build();
- updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId,
- updatedTile.build());
+ && conversation.getParentNotificationChannel().isImportantConversation());
+ updateAppWidgetOptionsAndView(appWidgetId, updatedTile.build());
}
/**
@@ -640,11 +746,11 @@
/** Adds a widget based on {@code key} mapped to {@code appWidgetId}. */
public void addNewWidget(int appWidgetId, PeopleTileKey key) {
if (DEBUG) Log.d(TAG, "addNewWidget called with key for appWidgetId: " + appWidgetId);
- PeopleSpaceTile tile = getTileFromPersistentStorage(key);
+ PeopleSpaceTile tile = getTileFromPersistentStorage(key, appWidgetId);
if (tile == null) {
return;
}
- tile = augmentTileFromNotificationEntryManager(tile);
+ tile = augmentTileFromNotificationEntryManager(tile, Optional.of(appWidgetId));
PeopleTileKey existingKeyIfStored;
synchronized (mLock) {
@@ -665,6 +771,8 @@
PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
tile.getContactUri());
}
+ if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
+ registerConversationListenerIfNeeded(appWidgetId, key);
try {
if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + key.toString());
mLauncherApps.cacheShortcuts(tile.getPackageName(),
@@ -674,23 +782,17 @@
Log.w(TAG, "Exception caching shortcut:" + e);
}
- PeopleSpaceUtils.updateAppWidgetOptionsAndView(
- mAppWidgetManager, mContext, appWidgetId, tile);
- mPeopleSpaceWidgetProvider.onUpdate(mContext, mAppWidgetManager, new int[]{appWidgetId});
+ updateAppWidgetOptionsAndView(appWidgetId, tile);
}
/** Registers a conversation listener for {@code appWidgetId} if not already registered. */
- public void registerConversationListenerIfNeeded(int widgetId,
- PeopleSpaceWidgetProvider.TileConversationListener newListener) {
+ public void registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key) {
// Retrieve storage needed for registration.
- PeopleTileKey key;
- synchronized (mLock) {
- key = getKeyFromStorageByWidgetId(widgetId);
- if (!key.isValid()) {
- if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
- return;
- }
+ if (!key.isValid()) {
+ if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
+ return;
}
+ TileConversationListener newListener = new TileConversationListener();
synchronized (mListeners) {
if (mListeners.containsKey(key)) {
if (DEBUG) Log.d(TAG, "Already registered listener");
@@ -760,7 +862,7 @@
/** Unregisters the conversation listener for {@code appWidgetId}. */
private void unregisterConversationListener(PeopleTileKey key, int appWidgetId) {
- PeopleSpaceWidgetProvider.TileConversationListener registeredListener;
+ TileConversationListener registeredListener;
synchronized (mListeners) {
registeredListener = mListeners.get(key);
if (registeredListener == null) {
@@ -875,9 +977,153 @@
return null;
}
- PeopleSpaceTile augmentedTile = augmentTileFromNotificationEntryManager(tile);
+ PeopleSpaceTile augmentedTile = augmentTileFromNotificationEntryManager(tile,
+ Optional.empty());
if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
- return new PeopleTileViewHelper(mContext, augmentedTile, 0, options).getViews();
+ return new PeopleTileViewHelper(mContext, augmentedTile, 0, options,
+ new PeopleTileKey(augmentedTile)).getViews();
+ }
+
+ protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "Update widgets from: " + action);
+ mBgExecutor.execute(() -> updateWidgetsOnStateChange(action));
+ }
+ };
+
+ /** Updates any app widget based on the current state. */
+ @VisibleForTesting
+ void updateWidgetsOnStateChange(String entryPoint) {
+ int[] appWidgetIds = mAppWidgetManager.getAppWidgetIds(
+ new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+ if (appWidgetIds == null) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int appWidgetId : appWidgetIds) {
+ PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
+ if (tile == null) {
+ Log.e(TAG, "Matching conversation not found for shortcut ID");
+ } else {
+ tile = updateWithCurrentState(tile, entryPoint);
+ }
+ updateAppWidgetOptionsAndView(appWidgetId, tile);
+ }
+ }
+ }
+
+ /** Checks the current state of {@code tile} dependencies, updating fields as necessary. */
+ @Nullable
+ private PeopleSpaceTile updateWithCurrentState(PeopleSpaceTile tile,
+ String entryPoint) {
+ PeopleSpaceTile.Builder updatedTile = tile.toBuilder();
+ try {
+ switch (entryPoint) {
+ case NotificationManager
+ .ACTION_INTERRUPTION_FILTER_CHANGED:
+ updatedTile.setNotificationPolicyState(getNotificationPolicyState());
+ break;
+ case Intent.ACTION_PACKAGES_SUSPENDED:
+ case Intent.ACTION_PACKAGES_UNSUSPENDED:
+ updatedTile.setIsPackageSuspended(getPackageSuspended(tile));
+ break;
+ case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+ case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+ case Intent.ACTION_USER_UNLOCKED:
+ updatedTile.setIsUserQuieted(getUserQuieted(tile));
+ break;
+ case Intent.ACTION_LOCALE_CHANGED:
+ break;
+ case ACTION_BOOT_COMPLETED:
+ default:
+ updatedTile.setIsUserQuieted(getUserQuieted(tile)).setIsPackageSuspended(
+ getPackageSuspended(tile)).setNotificationPolicyState(
+ getNotificationPolicyState());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Package no longer found for tile: " + tile.toString() + e);
+ return null;
+ }
+ return updatedTile.build();
+ }
+
+ private boolean getPackageSuspended(PeopleSpaceTile tile) throws Exception {
+ boolean packageSuspended = !TextUtils.isEmpty(tile.getPackageName())
+ && mPackageManager.isPackageSuspended(tile.getPackageName());
+ if (DEBUG) Log.d(TAG, "Package suspended: " + packageSuspended);
+ return packageSuspended;
+ }
+
+ private boolean getUserQuieted(PeopleSpaceTile tile) {
+ boolean workProfileQuieted =
+ tile.getUserHandle() != null && mUserManager.isQuietModeEnabled(
+ tile.getUserHandle());
+ if (DEBUG) Log.d(TAG, "Work profile quiet: " + workProfileQuieted);
+ return workProfileQuieted;
+ }
+
+ private int getNotificationPolicyState() {
+ NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
+ boolean suppressVisualEffects =
+ NotificationManager.Policy.areAllVisualEffectsSuppressed(
+ policy.suppressedVisualEffects);
+ int notificationPolicyState = 0;
+ switch (mNotificationManager.getCurrentInterruptionFilter()) {
+ case INTERRUPTION_FILTER_ALL:
+ if (DEBUG) Log.d(TAG, "All interruptions allowed");
+ return PeopleSpaceTile.SHOW_CONVERSATIONS;
+ case INTERRUPTION_FILTER_PRIORITY:
+ if (policy.allowConversations()) {
+ // If the user sees notifications in DND, show notifications in tiles in DND.
+ if (!suppressVisualEffects) {
+ if (DEBUG) Log.d(TAG, "Visual effects not suppressed.");
+ return PeopleSpaceTile.SHOW_CONVERSATIONS;
+ }
+ if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) {
+ if (DEBUG) Log.d(TAG, "All conversations allowed");
+ // We only show conversations, so we can show everything.
+ return PeopleSpaceTile.SHOW_CONVERSATIONS;
+ } else if (policy.priorityConversationSenders
+ == NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT) {
+ if (DEBUG) Log.d(TAG, "Important conversations allowed");
+ notificationPolicyState |= PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+ }
+ }
+ if (policy.allowMessages()) {
+ switch (policy.allowMessagesFrom()) {
+ case ZenModeConfig.SOURCE_CONTACT:
+ if (DEBUG) Log.d(TAG, "All contacts allowed");
+ notificationPolicyState |= PeopleSpaceTile.SHOW_CONTACTS;
+ return notificationPolicyState;
+ case ZenModeConfig.SOURCE_STAR:
+ if (DEBUG) Log.d(TAG, "Starred contacts allowed");
+ notificationPolicyState |= PeopleSpaceTile.SHOW_STARRED_CONTACTS;
+ return notificationPolicyState;
+ case ZenModeConfig.SOURCE_ANYONE:
+ default:
+ if (DEBUG) Log.d(TAG, "All messages allowed");
+ return PeopleSpaceTile.SHOW_CONVERSATIONS;
+ }
+ }
+ if (notificationPolicyState != 0) {
+ if (DEBUG) Log.d(TAG, "Return block state: " + notificationPolicyState);
+ return notificationPolicyState;
+ }
+ // If only alarms or nothing can bypass DND, the tile shouldn't show conversations.
+ case INTERRUPTION_FILTER_NONE:
+ case INTERRUPTION_FILTER_ALARMS:
+ default:
+ // If the user sees notifications in DND, show notifications in tiles in DND.
+ if (!suppressVisualEffects) {
+ if (DEBUG) Log.d(TAG, "Visual effects not suppressed.");
+ return PeopleSpaceTile.SHOW_CONVERSATIONS;
+ }
+ if (DEBUG) Log.d(TAG, "Block conversations");
+ return PeopleSpaceTile.BLOCK_CONVERSATIONS;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 3bc5b29..3522b76 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,9 +16,6 @@
package com.android.systemui.people.widget;
-import android.annotation.NonNull;
-import android.app.people.ConversationChannel;
-import android.app.people.PeopleManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
@@ -28,6 +25,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.people.PeopleSpaceUtils;
+import javax.inject.Inject;
+
/** People Space Widget Provider class. */
public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
private static final String TAG = "PeopleSpaceWidgetPvd";
@@ -38,25 +37,11 @@
public static final String EXTRA_USER_HANDLE = "extra_user_handle";
public static final String EXTRA_NOTIFICATION_KEY = "extra_notification_key";
- public PeopleSpaceWidgetManager peopleSpaceWidgetManager;
+ public PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
- /** Listener for the shortcut data changes. */
- public class TileConversationListener implements PeopleManager.ConversationListener {
-
- @Override
- public void onConversationUpdate(@NonNull ConversationChannel conversation) {
- if (DEBUG) {
- Log.d(TAG,
- "Received updated conversation: "
- + conversation.getShortcutInfo().getLabel());
- }
- if (peopleSpaceWidgetManager == null) {
- // This shouldn't happen since onUpdate is called at reboot.
- Log.e(TAG, "Skipping conversation update: WidgetManager uninitialized");
- return;
- }
- peopleSpaceWidgetManager.updateWidgetsWithConversationChanged(conversation);
- }
+ @Inject
+ PeopleSpaceWidgetProvider(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+ mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
}
/** Called when widget updates. */
@@ -65,15 +50,8 @@
super.onUpdate(context, appWidgetManager, appWidgetIds);
if (DEBUG) Log.d(TAG, "onUpdate called");
- ensurePeopleSpaceWidgetManagerInitialized(context);
- peopleSpaceWidgetManager.updateWidgets(appWidgetIds);
- for (int appWidgetId : appWidgetIds) {
- if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
- PeopleSpaceWidgetProvider.TileConversationListener
- newListener = new PeopleSpaceWidgetProvider.TileConversationListener();
- peopleSpaceWidgetManager.registerConversationListenerIfNeeded(appWidgetId,
- newListener);
- }
+ ensurePeopleSpaceWidgetManagerInitialized();
+ mPeopleSpaceWidgetManager.updateWidgets(appWidgetIds);
}
/** Called when widget updates. */
@@ -81,25 +59,23 @@
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
- ensurePeopleSpaceWidgetManagerInitialized(context);
- peopleSpaceWidgetManager.onAppWidgetOptionsChanged(appWidgetId, newOptions);
+ ensurePeopleSpaceWidgetManagerInitialized();
+ mPeopleSpaceWidgetManager.onAppWidgetOptionsChanged(appWidgetId, newOptions);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
- ensurePeopleSpaceWidgetManagerInitialized(context);
- peopleSpaceWidgetManager.deleteWidgets(appWidgetIds);
+ ensurePeopleSpaceWidgetManagerInitialized();
+ mPeopleSpaceWidgetManager.deleteWidgets(appWidgetIds);
}
- private void ensurePeopleSpaceWidgetManagerInitialized(Context context) {
- if (peopleSpaceWidgetManager == null) {
- peopleSpaceWidgetManager = new PeopleSpaceWidgetManager(context);
- }
+ private void ensurePeopleSpaceWidgetManagerInitialized() {
+ mPeopleSpaceWidgetManager.init();
}
@VisibleForTesting
public void setPeopleSpaceWidgetManager(PeopleSpaceWidgetManager manager) {
- peopleSpaceWidgetManager = manager;
+ mPeopleSpaceWidgetManager = manager;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index c552e89..aa4fb71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -7,7 +7,6 @@
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
@@ -333,11 +332,6 @@
@Override
public boolean updateResources() {
- // Update bottom padding, useful for removing extra space once the panel page indicator is
- // hidden.
- Resources res = getContext().getResources();
- setPageMargin(res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal));
-
setPadding(0, 0, 0,
getContext().getResources().getDimensionPixelSize(
R.dimen.qs_paged_tile_layout_padding_bottom));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f486c53..3b43676 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -207,11 +207,12 @@
mContext.getResources().getDimensionPixelSize(R.dimen.qs_container_bottom_padding)
);
- mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+ int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
int padding = getResources().getDimensionPixelSize(
R.dimen.notification_shade_content_margin_horizontal);
- boolean marginsChanged = padding != mContentPadding;
+ boolean marginsChanged = padding != mContentPadding || sideMargins != mSideMargins;
mContentPadding = padding;
+ mSideMargins = sideMargins;
if (marginsChanged) {
updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index f078ccd..1ec785d47 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -213,7 +213,9 @@
// TODO move this logic to message queue
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
StatusBar statusBar = statusBarLazy.get();
- statusBar.getPanelController().startExpandLatencyTracking();
+ if (event.getActionMasked() == ACTION_DOWN) {
+ statusBar.getPanelController().startExpandLatencyTracking();
+ }
mHandler.post(()-> {
int action = event.getActionMasked();
if (action == ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6b68fc6..a072de8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -924,7 +924,8 @@
// check of whether non-strong biometric is allowed
return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
&& msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
- || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+ || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
+ || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED);
}
private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 5437ce6..7f31fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -34,7 +35,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
-import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
import java.util.List;
@@ -46,7 +46,6 @@
@SuppressLint("OverrideAbstract")
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
- private static final boolean DEBUG = StatusBar.DEBUG;
private final Context mContext;
private final NotificationManager mNotificationManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
index 1da42a7..78e85f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -168,7 +168,6 @@
when (topLevel) {
"list-prefs" -> listPrefs(pw)
- "set-pref" -> setPref(pw, args.drop(1))
else -> help(pw)
}
}
@@ -180,25 +179,4 @@
pw.println(field.get(Prefs.Key::class.java))
}
}
-
- /**
- * Sets a preference from [Prefs]
- */
- private fun setPref(pw: PrintWriter, args: List<String>) {
- if (args.isEmpty()) {
- pw.println("invalid arguments: $args")
- return
- }
- val pref = args[0]
-
- when (pref) {
- Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S -> {
- val value = Integer.parseInt(args[1])
- Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, value != 0)
- }
- else -> {
- pw.println("Cannot set pref ($pref)")
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index d95c265..d6356de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -16,9 +16,7 @@
package com.android.systemui.statusbar.notification.collection.legacy;
-import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
@@ -33,7 +31,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.wm.shell.bubbles.Bubbles;
@@ -42,12 +39,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.TreeSet;
import javax.inject.Inject;
@@ -63,21 +58,13 @@
public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, StateListener,
GroupMembershipManager, GroupExpansionManager, Dumpable {
- private static final String TAG = "NotifGroupManager";
- private static final boolean DEBUG = StatusBar.DEBUG;
- private static final boolean SPEW = StatusBar.SPEW;
- /**
- * The maximum amount of time (in ms) between the posting of notifications that can be
- * considered part of the same update batch.
- */
- private static final long POST_BATCH_MAX_AGE = 5000;
+ private static final String TAG = "NotificationGroupManager";
private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
new ArraySet<>();
private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
private final Optional<Bubbles> mBubblesOptional;
- private final EventBuffer mEventBuffer = new EventBuffer();
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
@@ -147,14 +134,8 @@
* When we want to remove an entry from being tracked for grouping
*/
public void onEntryRemoved(NotificationEntry removed) {
- if (SPEW) {
- Log.d(TAG, "onEntryRemoved: entry=" + removed);
- }
onEntryRemovedInternal(removed, removed.getSbn());
- StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey());
- if (oldSbn != null) {
- updateSuppression(mGroupMap.get(oldSbn.getGroupKey()));
- }
+ mIsolatedEntries.remove(removed.getKey());
}
/**
@@ -181,9 +162,6 @@
// the close future. See b/23676310 for reference.
return;
}
- if (SPEW) {
- Log.d(TAG, "onEntryRemovedInternal: entry=" + removed + " group=" + group.groupKey);
- }
if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
group.children.remove(removed.getKey());
} else {
@@ -204,9 +182,6 @@
* Notify the group manager that a new entry was added
*/
public void onEntryAdded(final NotificationEntry added) {
- if (SPEW) {
- Log.d(TAG, "onEntryAdded: entry=" + added);
- }
updateIsolation(added);
onEntryAddedInternal(added);
}
@@ -220,16 +195,13 @@
String groupKey = getGroupKey(sbn);
NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
- group = new NotificationGroup(groupKey);
+ group = new NotificationGroup();
mGroupMap.put(groupKey, group);
for (OnGroupChangeListener listener : mGroupChangeListeners) {
listener.onGroupCreated(group, groupKey);
}
}
- if (SPEW) {
- Log.d(TAG, "onEntryAddedInternal: entry=" + added + " group=" + group.groupKey);
- }
if (isGroupChild) {
NotificationEntry existing = group.children.get(added.getKey());
if (existing != null && existing != added) {
@@ -241,11 +213,9 @@
+ " added removed" + added.isRowRemoved(), new Throwable());
}
group.children.put(added.getKey(), added);
- addToPostBatchHistory(group, added);
updateSuppression(group);
} else {
group.summary = added;
- addToPostBatchHistory(group, added);
group.expanded = added.areChildrenExpanded();
updateSuppression(group);
if (!group.children.isEmpty()) {
@@ -261,27 +231,6 @@
}
}
- private void addToPostBatchHistory(NotificationGroup group, @Nullable NotificationEntry entry) {
- if (entry == null) {
- return;
- }
- boolean didAdd = group.postBatchHistory.add(new PostRecord(entry));
- if (didAdd) {
- trimPostBatchHistory(group.postBatchHistory);
- }
- }
-
- /** remove all history that's too old to be in the batch. */
- private void trimPostBatchHistory(@NonNull TreeSet<PostRecord> postBatchHistory) {
- if (postBatchHistory.size() <= 1) {
- return;
- }
- long batchStartTime = postBatchHistory.last().postTime - POST_BATCH_MAX_AGE;
- while (!postBatchHistory.isEmpty() && postBatchHistory.first().postTime < batchStartTime) {
- postBatchHistory.pollFirst();
- }
- }
-
private void onEntryBecomingChild(NotificationEntry entry) {
updateIsolation(entry);
}
@@ -290,9 +239,6 @@
if (group == null) {
return;
}
- NotificationEntry prevAlertOverride = group.alertOverride;
- group.alertOverride = getPriorityConversationAlertOverride(group);
-
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
@@ -309,148 +255,18 @@
group.suppressed = group.summary != null && !group.expanded
&& (childCount == 1
|| (childCount == 0
- && group.summary.getSbn().getNotification().isGroupSummary()
- && (hasIsolatedChildren(group) || hasBubbles)));
-
- boolean alertOverrideChanged = prevAlertOverride != group.alertOverride;
- boolean suppressionChanged = prevSuppressed != group.suppressed;
- if (alertOverrideChanged || suppressionChanged) {
- if (DEBUG && alertOverrideChanged) {
- Log.d(TAG, group + " alertOverride was=" + prevAlertOverride + " now="
- + group.alertOverride);
- }
- if (DEBUG && suppressionChanged) {
- Log.d(TAG, group + " suppressed changed to " + group.suppressed);
- }
- if (!mIsUpdatingUnchangedGroup) {
- if (alertOverrideChanged) {
- mEventBuffer.notifyAlertOverrideChanged(group, prevAlertOverride);
- }
- if (suppressionChanged) {
- for (OnGroupChangeListener listener : mGroupChangeListeners) {
- listener.onGroupSuppressionChanged(group, group.suppressed);
- }
- }
- mEventBuffer.notifyGroupsChanged();
- } else {
- if (DEBUG) {
- Log.d(TAG, group + " did not notify listeners of above change(s)");
+ && group.summary.getSbn().getNotification().isGroupSummary()
+ && (hasIsolatedChildren(group) || hasBubbles)));
+ if (prevSuppressed != group.suppressed) {
+ for (OnGroupChangeListener listener : mGroupChangeListeners) {
+ if (!mIsUpdatingUnchangedGroup) {
+ listener.onGroupSuppressionChanged(group, group.suppressed);
+ listener.onGroupsChanged();
}
}
}
}
- /**
- * Finds the isolated logical child of this group which is should be alerted instead.
- *
- * Notifications from priority conversations are isolated from their groups to make them more
- * prominent, however apps may post these with a GroupAlertBehavior that has the group receiving
- * the alert. This would lead to the group alerting even though the conversation that was
- * updated was not actually a part of that group. This method finds the best priority
- * conversation in this situation, if there is one, so they can be set as the alertOverride of
- * the group.
- *
- * @param group the group to check
- * @return the entry which should receive the alert instead of the group, if any.
- */
- @Nullable
- private NotificationEntry getPriorityConversationAlertOverride(NotificationGroup group) {
- // GOAL: if there is a priority child which wouldn't alert based on its groupAlertBehavior,
- // but which should be alerting (because priority conversations are isolated), find it.
- if (group == null || group.summary == null) {
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary");
- }
- return null;
- }
- if (isIsolated(group.summary.getKey())) {
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: isolated group");
- }
- return null;
- }
-
- // Precondiions:
- // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
- // * Only necessary when at least one notification in the group is on a priority channel
- if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
- != Notification.GROUP_ALERT_SUMMARY) {
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY");
- }
- return null;
- }
-
- // Get the important children first, copy the keys for the final importance check,
- // then add the non-isolated children to the map for unified lookup.
- HashMap<String, NotificationEntry> children = getImportantConversations(group);
- if (children == null || children.isEmpty()) {
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations");
- }
- return null;
- }
- HashSet<String> importantChildKeys = new HashSet<>(children.keySet());
- children.putAll(group.children);
-
- // Ensure all children have GROUP_ALERT_SUMMARY
- for (NotificationEntry child : children.values()) {
- if (child.getSbn().getNotification().getGroupAlertBehavior()
- != Notification.GROUP_ALERT_SUMMARY) {
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: "
- + "child != GROUP_ALERT_SUMMARY");
- }
- return null;
- }
- }
-
- // Create a merged post history from all the children
- TreeSet<PostRecord> combinedHistory = new TreeSet<>(group.postBatchHistory);
- for (String importantChildKey : importantChildKeys) {
- NotificationGroup importantChildGroup = mGroupMap.get(importantChildKey);
- combinedHistory.addAll(importantChildGroup.postBatchHistory);
- }
- trimPostBatchHistory(combinedHistory);
-
- // This is a streamlined implementation of the following idea:
- // * From the subset of notifications in the latest 'batch' of updates. A batch is:
- // * Notifs posted less than POST_BATCH_MAX_AGE before the most recently posted.
- // * Only including notifs newer than the second-to-last post of any notification.
- // * Find the newest child in the batch -- the with the largest 'when' value.
- // * If the newest child is a priority conversation, set that as the override.
- HashSet<String> batchKeys = new HashSet<>();
- long newestChildWhen = -1;
- NotificationEntry newestChild = null;
- // Iterate backwards through the post history, tracking the child with the smallest sort key
- for (PostRecord record : combinedHistory.descendingSet()) {
- if (batchKeys.contains(record.key)) {
- // Once you see a notification again, the batch has ended
- break;
- }
- batchKeys.add(record.key);
- NotificationEntry child = children.get(record.key);
- if (child != null) {
- long childWhen = child.getSbn().getNotification().when;
- if (newestChild == null || childWhen > newestChildWhen) {
- newestChildWhen = childWhen;
- newestChild = child;
- }
- }
- }
- if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) {
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: result=" + newestChild);
- }
- return newestChild;
- }
- if (SPEW) {
- Log.d(TAG, "getPriorityConversationAlertOverride: result=null, newestChild="
- + newestChild);
- }
- return null;
- }
-
private boolean hasIsolatedChildren(NotificationGroup group) {
return getNumberOfIsolatedChildren(group.summary.getSbn().getGroupKey()) != 0;
}
@@ -465,33 +281,12 @@
return count;
}
- @Nullable
- private HashMap<String, NotificationEntry> getImportantConversations(NotificationGroup group) {
- String groupKey = group.summary.getSbn().getGroupKey();
- HashMap<String, NotificationEntry> result = null;
- for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey)) {
- NotificationEntry entry = mGroupMap.get(sbn.getKey()).summary;
- if (isImportantConversation(entry)) {
- if (result == null) {
- result = new HashMap<>();
- }
- result.put(sbn.getKey(), entry);
- }
- }
- }
- return result;
- }
-
/**
* Update an entry's group information
* @param entry notification entry to update
* @param oldNotification previous notification info before this update
*/
public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
- if (SPEW) {
- Log.d(TAG, "onEntryUpdated: entry=" + entry);
- }
onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
oldNotification.getNotification().isGroupSummary());
}
@@ -530,17 +325,7 @@
* Whether the given notification is the summary of a group that is being suppressed
*/
public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
- return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn));
- }
-
- /**
- * If the given notification is a summary, get the group for it.
- */
- public NotificationGroup getGroupForSummary(StatusBarNotification sbn) {
- if (sbn.getNotification().isGroupSummary()) {
- return mGroupMap.get(getGroupKey(sbn));
- }
- return null;
+ return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
}
private boolean isOnlyChild(StatusBarNotification sbn) {
@@ -760,7 +545,9 @@
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
return false;
}
- if (isImportantConversation(entry)) {
+ int peopleNotificationType =
+ mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
+ if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) {
return true;
}
if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
@@ -773,25 +560,18 @@
|| isGroupNotFullyVisible(notificationGroup));
}
- private boolean isImportantConversation(NotificationEntry entry) {
- int peopleNotificationType =
- mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
- return peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON;
- }
-
/**
* Isolate a notification from its group so that it visually shows as its own group.
*
* @param entry the notification to isolate
*/
private void isolateNotification(NotificationEntry entry) {
- if (SPEW) {
- Log.d(TAG, "isolateNotification: entry=" + entry);
- }
+ StatusBarNotification sbn = entry.getSbn();
+
// We will be isolated now, so lets update the groups
onEntryRemovedInternal(entry, entry.getSbn());
- mIsolatedEntries.put(entry.getKey(), entry.getSbn());
+ mIsolatedEntries.put(sbn.getKey(), sbn);
onEntryAddedInternal(entry);
// We also need to update the suppression of the old group, because this call comes
@@ -808,14 +588,6 @@
* Update the isolation of an entry, splitting it from the group.
*/
public void updateIsolation(NotificationEntry entry) {
- // We need to buffer a few events because we do isolation changes in 3 steps:
- // removeInternal, update mIsolatedEntries, addInternal. This means that often the
- // alertOverride will update on the removal, however processing the event in that case can
- // cause problems because the mIsolatedEntries map is not in its final state, so the event
- // listener may be unable to correctly determine the true state of the group. By delaying
- // the alertOverride change until after the add phase, we can ensure that listeners only
- // have to handle a consistent state.
- mEventBuffer.startBuffering();
boolean isIsolated = isIsolated(entry.getSbn().getKey());
if (shouldIsolate(entry)) {
if (!isIsolated) {
@@ -824,7 +596,6 @@
} else if (isIsolated) {
stopIsolatingNotification(entry);
}
- mEventBuffer.flushAndStopBuffering();
}
/**
@@ -833,15 +604,15 @@
* @param entry the notification to un-isolate
*/
private void stopIsolatingNotification(NotificationEntry entry) {
- if (SPEW) {
- Log.d(TAG, "stopIsolatingNotification: entry=" + entry);
- }
- // not isolated anymore, we need to update the groups
- onEntryRemovedInternal(entry, entry.getSbn());
- mIsolatedEntries.remove(entry.getKey());
- onEntryAddedInternal(entry);
- for (OnGroupChangeListener listener : mGroupChangeListeners) {
- listener.onGroupsChanged();
+ StatusBarNotification sbn = entry.getSbn();
+ if (isIsolated(sbn.getKey())) {
+ // not isolated anymore, we need to update the groups
+ onEntryRemovedInternal(entry, entry.getSbn());
+ mIsolatedEntries.remove(sbn.getKey());
+ onEntryAddedInternal(entry);
+ for (OnGroupChangeListener listener : mGroupChangeListeners) {
+ listener.onGroupsChanged();
+ }
}
}
@@ -877,154 +648,33 @@
}
/**
- * A record of a notification being posted, containing the time of the post and the key of the
- * notification entry. These are stored in a TreeSet by the NotificationGroup and used to
- * calculate a batch of notifications.
- */
- public static class PostRecord implements Comparable<PostRecord> {
- public final long postTime;
- public final String key;
-
- /** constructs a record containing the post time and key from the notification entry */
- public PostRecord(@NonNull NotificationEntry entry) {
- this.postTime = entry.getSbn().getPostTime();
- this.key = entry.getKey();
- }
-
- @Override
- public int compareTo(PostRecord o) {
- int postTimeComparison = Long.compare(this.postTime, o.postTime);
- return postTimeComparison == 0
- ? String.CASE_INSENSITIVE_ORDER.compare(this.key, o.key)
- : postTimeComparison;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- PostRecord that = (PostRecord) o;
- return postTime == that.postTime && key.equals(that.key);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(postTime, key);
- }
- }
-
- /**
* Represents a notification group in the notification shade.
*/
public static class NotificationGroup {
- public final String groupKey;
public final HashMap<String, NotificationEntry> children = new HashMap<>();
- public final TreeSet<PostRecord> postBatchHistory = new TreeSet<>();
public NotificationEntry summary;
public boolean expanded;
/**
* Is this notification group suppressed, i.e its summary is hidden
*/
public boolean suppressed;
- /**
- * The child (which is isolated from this group) to which the alert should be transferred,
- * due to priority conversations.
- */
- public NotificationEntry alertOverride;
-
- NotificationGroup(String groupKey) {
- this.groupKey = groupKey;
- }
@Override
public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(" groupKey: ").append(groupKey);
- sb.append("\n summary:");
- appendEntry(sb, summary);
- sb.append("\n children size: ").append(children.size());
+ String result = " summary:\n "
+ + (summary != null ? summary.getSbn() : "null")
+ + (summary != null && summary.getDebugThrowable() != null
+ ? Log.getStackTraceString(summary.getDebugThrowable())
+ : "");
+ result += "\n children size: " + children.size();
for (NotificationEntry child : children.values()) {
- appendEntry(sb, child);
+ result += "\n " + child.getSbn()
+ + (child.getDebugThrowable() != null
+ ? Log.getStackTraceString(child.getDebugThrowable())
+ : "");
}
- sb.append("\n alertOverride:");
- appendEntry(sb, alertOverride);
- sb.append("\n summary suppressed: ").append(suppressed);
- return sb.toString();
- }
-
- private void appendEntry(StringBuilder sb, NotificationEntry entry) {
- sb.append("\n ").append(entry != null ? entry.getSbn() : "null");
- if (entry != null && entry.getDebugThrowable() != null) {
- sb.append(Log.getStackTraceString(entry.getDebugThrowable()));
- }
- }
- }
-
- /**
- * This class is a toggleable buffer for a subset of events of {@link OnGroupChangeListener}.
- * When buffering, instead of notifying the listeners it will set internal state that will allow
- * it to notify listeners of those events later
- */
- private class EventBuffer {
- private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>();
- private boolean mIsBuffering = false;
- private boolean mDidGroupsChange = false;
-
- void notifyAlertOverrideChanged(NotificationGroup group,
- NotificationEntry oldAlertOverride) {
- if (mIsBuffering) {
- // The value in this map is the override before the event. If there is an entry
- // already in the map, then we are effectively coalescing two events, which means
- // we need to preserve the original initial value.
- mOldAlertOverrideByGroup.putIfAbsent(group.groupKey, oldAlertOverride);
- } else {
- for (OnGroupChangeListener listener : mGroupChangeListeners) {
- listener.onGroupAlertOverrideChanged(group, oldAlertOverride,
- group.alertOverride);
- }
- }
- }
-
- void notifyGroupsChanged() {
- if (mIsBuffering) {
- mDidGroupsChange = true;
- } else {
- for (OnGroupChangeListener listener : mGroupChangeListeners) {
- listener.onGroupsChanged();
- }
- }
- }
-
- void startBuffering() {
- mIsBuffering = true;
- }
-
- void flushAndStopBuffering() {
- // stop buffering so that we can call our own helpers
- mIsBuffering = false;
- // alert all group alert override changes for groups that were not removed
- for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) {
- NotificationGroup group = mGroupMap.get(entry.getKey());
- if (group == null) {
- // The group can be null if this alertOverride changed before the group was
- // permanently removed, meaning that there's no guarantee that listeners will
- // that field clear.
- continue;
- }
- NotificationEntry oldAlertOverride = entry.getValue();
- if (group.alertOverride == oldAlertOverride) {
- // If the final alertOverride equals the initial, it means we coalesced two
- // events which undid the change, so we can drop it entirely.
- continue;
- }
- notifyAlertOverrideChanged(group, oldAlertOverride);
- }
- mOldAlertOverrideByGroup.clear();
- // alert that groups changed
- if (mDidGroupsChange) {
- notifyGroupsChanged();
- mDidGroupsChange = false;
- }
+ result += "\n summary suppressed: " + suppressed;
+ return result;
}
}
@@ -1064,18 +714,6 @@
boolean suppressed) {}
/**
- * The alert override of a group has changed.
- *
- * @param group the group that has changed
- * @param oldAlertOverride the previous notification to which the group's alerts were sent
- * @param newAlertOverride the notification to which the group's alerts should now be sent
- */
- default void onGroupAlertOverrideChanged(
- NotificationGroup group,
- @Nullable NotificationEntry oldAlertOverride,
- @Nullable NotificationEntry newAlertOverride) {}
-
- /**
* A group of children just received a summary notification and should therefore become
* children of it.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 617dadb..e2a37f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -69,7 +69,6 @@
import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -141,7 +140,6 @@
ShortcutManager shortcutManager,
ChannelEditorDialogController channelEditorDialogController,
UserContextProvider contextTracker,
- Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
@@ -161,7 +159,6 @@
shortcutManager,
channelEditorDialogController,
contextTracker,
- builderProvider,
assistantFeedbackController,
bubblesManagerOptional,
uiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 40be4bf..1f4f3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -22,6 +22,8 @@
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
@@ -34,7 +36,6 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -55,7 +56,6 @@
import android.transition.TransitionSet;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
@@ -64,7 +64,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -78,8 +77,6 @@
import java.lang.annotation.Retention;
import java.util.Optional;
-import javax.inject.Provider;
-
/**
* The guts of a conversation notification revealed when performing a long press.
*/
@@ -107,7 +104,6 @@
private StatusBarNotification mSbn;
@Nullable private Notification.BubbleMetadata mBubbleMetadata;
private Context mUserContext;
- private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
private boolean mIsDeviceProvisioned;
private int mAppBubble;
@@ -172,13 +168,9 @@
private OnClickListener mOnDone = v -> {
mPressedApply = true;
- // If the user selected Priority, maybe show the priority onboarding.
// If the user selected Priority and the previous selection was not priority, show a
- // People Tile add request. If showing the priority onboarding, however, delay the request
- // to when the onboarding dialog closes.
- if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) {
- showPriorityOnboarding();
- } else if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
+ // People Tile add request.
+ if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
mShadeController.animateCollapsePanels();
mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
}
@@ -229,7 +221,6 @@
OnSnoozeClickListener onSnoozeClickListener,
ConversationIconFactory conversationIconFactory,
Context userContext,
- Provider<PriorityOnboardingDialogController.Builder> builderProvider,
boolean isDeviceProvisioned,
@Main Handler mainHandler,
@Background Handler bgHandler,
@@ -258,7 +249,6 @@
mBubbleMetadata = bubbleMetadata;
mBubblesManagerOptional = bubblesManagerOptional;
mShadeController = shadeController;
- mBuilderProvider = builderProvider;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
mShortcutManager = shortcutManager;
@@ -342,6 +332,18 @@
// bindName();
bindPackage();
bindIcon(mNotificationChannel.isImportantConversation());
+
+ mPriorityDescriptionView = findViewById(R.id.priority_summary);
+ if (willShowAsBubble() && willBypassDnd()) {
+ mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_all);
+ } else if (willShowAsBubble()) {
+ mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_bubble);
+ } else if (willBypassDnd()) {
+ mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_dnd);
+ } else {
+ mPriorityDescriptionView.setText(
+ R.string.notification_channel_summary_priority_baseline);
+ }
}
private void bindIcon(boolean important) {
@@ -428,7 +430,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
- mPriorityDescriptionView = findViewById(R.id.priority_summary);
mDefaultDescriptionView = findViewById(R.id.default_summary);
mSilentDescriptionView = findViewById(R.id.silence_summary);
}
@@ -552,51 +553,22 @@
StackStateAnimator.ANIMATION_DURATION_STANDARD);
}
- private boolean shouldShowPriorityOnboarding() {
- return !Prefs.getBoolean(mUserContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, false);
- }
-
- private void showPriorityOnboarding() {
- View onboardingView = LayoutInflater.from(mContext)
- .inflate(R.layout.priority_onboarding_half_shell, null);
-
- boolean ignoreDnd = false;
+ private boolean willBypassDnd() {
+ boolean bypassesDnd = false;
try {
- ignoreDnd = mINotificationManager
- .getConsolidatedNotificationPolicy().priorityConversationSenders ==
- NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
+ int allowedSenders = mINotificationManager
+ .getConsolidatedNotificationPolicy().priorityConversationSenders;
+ bypassesDnd = allowedSenders == CONVERSATION_SENDERS_IMPORTANT
+ || allowedSenders == CONVERSATION_SENDERS_ANYONE;
} catch (RemoteException e) {
Log.e(TAG, "Could not check conversation senders", e);
}
+ return bypassesDnd;
+ }
- boolean showAsBubble = mBubbleMetadata != null
- && mBubbleMetadata.getAutoExpandBubble()
+ private boolean willShowAsBubble() {
+ return mBubbleMetadata != null
&& BubblesManager.areBubblesEnabled(mContext, mSbn.getUser());
-
- Drawable person = mIconFactory.getBaseIconDrawable(mShortcutInfo);
- if (person == null) {
- person = mContext.getDrawable(R.drawable.ic_person).mutate();
- TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- person.setTint(colorAccent);
- }
-
- PriorityOnboardingDialogController controller = mBuilderProvider.get()
- .setContext(mUserContext)
- .setView(onboardingView)
- .setIgnoresDnd(ignoreDnd)
- .setShowsAsBubble(showAsBubble)
- .setIcon(person)
- .setBadge(mIconFactory.getAppBadge(
- mPackageName, UserHandle.getUserId(mSbn.getUid())))
- .setOnSettingsClick(mOnConversationSettingsClickListener)
- .setPeopleSpaceWidgetManager(mPeopleSpaceWidgetManager)
- .setShadeController(mShadeController)
- .build();
-
- controller.init(mShortcutInfo);
- controller.show();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 1a7f5b0..59b88a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -129,7 +129,6 @@
private final LauncherApps mLauncherApps;
private final ShortcutManager mShortcutManager;
private final UserContextProvider mContextTracker;
- private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
private final AppWidgetManager mAppWidgetManager;
@@ -150,7 +149,6 @@
ShortcutManager shortcutManager,
ChannelEditorDialogController channelEditorDialogController,
UserContextProvider contextTracker,
- Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
@@ -168,7 +166,6 @@
mLauncherApps = launcherApps;
mShortcutManager = shortcutManager;
mContextTracker = contextTracker;
- mBuilderProvider = builderProvider;
mChannelEditorDialogController = channelEditorDialogController;
mAssistantFeedbackController = assistantFeedbackController;
mBubblesManagerOptional = bubblesManagerOptional;
@@ -503,7 +500,6 @@
onSnoozeClickListener,
iconFactoryLoader,
mContextTracker.getUserContext(),
- mBuilderProvider,
mDeviceProvisionedController.isDeviceProvisioned(),
mMainHandler,
mBgHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
deleted file mode 100644
index 270721f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.row
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
-import android.app.Dialog
-import android.content.Context
-import android.content.pm.ShortcutInfo
-import android.graphics.Color
-import android.graphics.PixelFormat
-import android.graphics.drawable.ColorDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.os.Bundle
-import android.text.SpannableStringBuilder
-import android.text.style.BulletSpan
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.Window
-import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager
-import android.view.animation.Interpolator
-import android.view.animation.PathInterpolator
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.systemui.Prefs
-import com.android.systemui.R
-import com.android.systemui.animation.Interpolators.LINEAR_OUT_SLOW_IN
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager
-import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener
-import com.android.systemui.statusbar.phone.ShadeController
-import javax.inject.Inject
-
-/**
- * Controller to handle presenting the priority conversations onboarding dialog
- */
-class PriorityOnboardingDialogController @Inject constructor(
- val view: View,
- val context: Context,
- private val ignoresDnd: Boolean,
- private val showsAsBubble: Boolean,
- val icon: Drawable,
- private val onConversationSettingsClickListener: OnConversationSettingsClickListener,
- val badge: Drawable,
- private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
- private val shadeController: ShadeController
-) {
-
- private lateinit var dialog: Dialog
- private lateinit var shortcutInfo: ShortcutInfo
- private val OVERSHOOT: Interpolator = PathInterpolator(0.4f, 0f, 0.2f, 1.4f)
- private val IMPORTANCE_ANIM_DELAY = 150L
- private val IMPORTANCE_ANIM_GROW_DURATION = 250L
- private val IMPORTANCE_ANIM_SHRINK_DURATION = 200L
- private val IMPORTANCE_ANIM_SHRINK_DELAY = 25L
-
- fun init(info: ShortcutInfo) {
- shortcutInfo = info
- initDialog()
- }
-
- fun show() {
- dialog.show()
- }
-
- private fun done() {
- // Log that the user has seen the onboarding
- Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true)
- dialog.dismiss()
- shadeController.animateCollapsePanels()
- peopleSpaceWidgetManager.requestPinAppWidget(shortcutInfo, Bundle())
- }
-
- private fun settings() {
- // Log that the user has seen the onboarding
- Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true)
- dialog.dismiss()
- onConversationSettingsClickListener?.onClick()
- }
-
- class Builder @Inject constructor() {
- private lateinit var view: View
- private lateinit var context: Context
- private var ignoresDnd = false
- private var showAsBubble = false
- private lateinit var icon: Drawable
- private lateinit var onConversationSettingsClickListener:
- OnConversationSettingsClickListener
- private lateinit var badge: Drawable
- private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
- private lateinit var shadeController: ShadeController
-
- fun setView(v: View): Builder {
- view = v
- return this
- }
-
- fun setContext(c: Context): Builder {
- context = c
- return this
- }
-
- fun setIgnoresDnd(ignore: Boolean): Builder {
- ignoresDnd = ignore
- return this
- }
-
- fun setShowsAsBubble(bubble: Boolean): Builder {
- showAsBubble = bubble
- return this
- }
-
- fun setIcon(draw: Drawable): Builder {
- icon = draw
- return this
- }
- fun setBadge(badge: Drawable): Builder {
- this.badge = badge
- return this
- }
-
- fun setOnSettingsClick(onClick: OnConversationSettingsClickListener): Builder {
- onConversationSettingsClickListener = onClick
- return this
- }
-
- fun setShadeController(shadeController: ShadeController): Builder {
- this.shadeController = shadeController
- return this
- }
-
- fun setPeopleSpaceWidgetManager(peopleSpaceWidgetManager: PeopleSpaceWidgetManager):
- Builder {
- this.peopleSpaceWidgetManager = peopleSpaceWidgetManager
- return this
- }
-
- fun build(): PriorityOnboardingDialogController {
- val controller = PriorityOnboardingDialogController(
- view, context, ignoresDnd, showAsBubble, icon,
- onConversationSettingsClickListener, badge, peopleSpaceWidgetManager,
- shadeController)
- return controller
- }
- }
-
- private fun initDialog() {
- dialog = Dialog(context)
-
- if (dialog.window == null) {
- throw IllegalStateException("Need a window for the onboarding dialog to show")
- }
-
- dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
- // Prevent a11y readers from reading the first element in the dialog twice
- dialog.setTitle("\u00A0")
- dialog.apply {
- setContentView(view)
- setCanceledOnTouchOutside(true)
-
- findViewById<TextView>(R.id.done_button)?.setOnClickListener {
- done()
- }
-
- findViewById<TextView>(R.id.settings_button)?.setOnClickListener {
- settings()
- }
-
- findViewById<ImageView>(R.id.conversation_icon)?.setImageDrawable(icon)
- findViewById<ImageView>(R.id.icon)?.setImageDrawable(badge)
- val mImportanceRingView = findViewById<ImageView>(R.id.conversation_icon_badge_ring)
- val conversationIconBadgeBg = findViewById<ImageView>(R.id.conversation_icon_badge_bg)
-
- val ring: GradientDrawable = mImportanceRingView.drawable as GradientDrawable
- ring.mutate()
- val bg = conversationIconBadgeBg.drawable as GradientDrawable
- bg.mutate()
- val ringColor = context.getResources()
- .getColor(com.android.internal.R.color.conversation_important_highlight)
- val standardThickness = context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width)
- val largeThickness = context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_anim_max_stroke_width)
- val standardSize = context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_size)
- val baseSize = standardSize - standardThickness * 2
- val largeSize = baseSize + largeThickness * 2
- val bgSize = context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.conversation_icon_size_badged)
-
- val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener =
- ValueAnimator.AnimatorUpdateListener { animation ->
- val strokeWidth = animation.animatedValue as Int
- ring.setStroke(strokeWidth, ringColor)
- val newSize = baseSize + strokeWidth * 2
- ring.setSize(newSize, newSize)
- mImportanceRingView.invalidate()
- }
-
- val growAnimation: ValueAnimator = ValueAnimator.ofInt(0, largeThickness)
- growAnimation.interpolator = LINEAR_OUT_SLOW_IN
- growAnimation.duration = IMPORTANCE_ANIM_GROW_DURATION
- growAnimation.addUpdateListener(animatorUpdateListener)
-
- val shrinkAnimation: ValueAnimator =
- ValueAnimator.ofInt(largeThickness, standardThickness)
- shrinkAnimation.duration = IMPORTANCE_ANIM_SHRINK_DURATION
- shrinkAnimation.startDelay = IMPORTANCE_ANIM_SHRINK_DELAY
- shrinkAnimation.interpolator = OVERSHOOT
- shrinkAnimation.addUpdateListener(animatorUpdateListener)
- shrinkAnimation.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?) {
- // Shrink the badge bg so that it doesn't peek behind the animation
- bg.setSize(baseSize, baseSize)
- conversationIconBadgeBg.invalidate()
- }
-
- override fun onAnimationEnd(animation: Animator?) {
- // Reset bg back to normal size
- bg.setSize(bgSize, bgSize)
- conversationIconBadgeBg.invalidate()
- }
- })
-
- val anims = AnimatorSet()
- anims.startDelay = IMPORTANCE_ANIM_DELAY
- anims.playSequentially(growAnimation, shrinkAnimation)
-
- val gapWidth = dialog.context.getResources().getDimensionPixelSize(
- R.dimen.conversation_onboarding_bullet_gap_width)
- val description = SpannableStringBuilder()
- description.append(context.getText(R.string.priority_onboarding_show_at_top_text),
- BulletSpan(gapWidth), /* flags */0)
- description.append(System.lineSeparator())
- description.append(context.getText(R.string.priority_onboarding_show_avatar_text),
- BulletSpan(gapWidth), /* flags */0)
- if (showsAsBubble) {
- description.append(System.lineSeparator())
- description.append(context.getText(
- R.string.priority_onboarding_appear_as_bubble_text),
- BulletSpan(gapWidth), /* flags */0)
- }
- if (ignoresDnd) {
- description.append(System.lineSeparator())
- description.append(context.getText(R.string.priority_onboarding_ignores_dnd_text),
- BulletSpan(gapWidth), /* flags */0)
- }
- findViewById<TextView>(R.id.behaviors).setText(description)
-
- window?.apply {
- setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
- addFlags(wmFlags)
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)
-
- attributes = attributes.apply {
- format = PixelFormat.TRANSLUCENT
- title = PriorityOnboardingDialogController::class.java.simpleName
- gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
- fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv()
- width = MATCH_PARENT
- height = WRAP_CONTENT
- }
- }
- anims.start()
- }
- }
-
- private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
- or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 7d586ba..c1a5f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -400,10 +400,14 @@
}
if (view instanceof FooterView) {
- viewState.yTranslation = Math.min(viewState.yTranslation,
- ambientState.getStackHeight());
- // Hide footer if shelf is showing
- viewState.hidden = algorithmState.firstViewInShelf != null;
+ final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
+
+ final float footerEnd = viewState.yTranslation + view.getIntrinsicHeight();
+ final boolean noSpaceForFooter = footerEnd > ambientState.getStackHeight();
+
+ viewState.hidden = isShelfShowing
+ || (!ambientState.isExpansionChanging() && noSpaceForFooter);
+
} else if (view != ambientState.getTrackedHeadsUpRow()) {
if (ambientState.isExpansionChanging()) {
// Show all views. Views below the shelf will later be clipped (essentially hidden)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 9787a944..3181f52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -22,12 +22,12 @@
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -41,21 +41,17 @@
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
/**
* A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
* and {@link HeadsUpManager}. In particular, this class deals with keeping
- * the correct notification in a group alerting based off the group suppression and alertOverride.
+ * the correct notification in a group alerting based off the group suppression.
*/
public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
StateListener {
private static final long ALERT_TRANSFER_TIMEOUT = 300;
- private static final String TAG = "NotifGroupAlertTransfer";
- private static final boolean DEBUG = StatusBar.DEBUG;
- private static final boolean SPEW = StatusBar.SPEW;
/**
* The list of entries containing group alert metadata for each group. Keyed by group key.
@@ -146,98 +142,41 @@
@Override
public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
- if (DEBUG) {
- Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary
- + " suppressed=" + suppressed);
+ if (suppressed) {
+ if (mHeadsUpManager.isAlerting(group.summary.getKey())) {
+ handleSuppressedSummaryAlerted(group.summary, mHeadsUpManager);
+ }
+ } else {
+ // Group summary can be null if we are no longer suppressed because the summary was
+ // removed. In that case, we don't need to alert the summary.
+ if (group.summary == null) {
+ return;
+ }
+ GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
+ group.summary.getSbn()));
+ // Group is no longer suppressed. We should check if we need to transfer the alert
+ // back to the summary now that it's no longer suppressed.
+ if (groupAlertEntry.mAlertSummaryOnNextAddition) {
+ if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
+ alertNotificationWhenPossible(group.summary, mHeadsUpManager);
+ }
+ groupAlertEntry.mAlertSummaryOnNextAddition = false;
+ } else {
+ checkShouldTransferBack(groupAlertEntry);
+ }
}
- NotificationEntry oldAlertOverride = group.alertOverride;
- onGroupChanged(group, oldAlertOverride);
- }
-
- @Override
- public void onGroupAlertOverrideChanged(NotificationGroup group,
- @Nullable NotificationEntry oldAlertOverride,
- @Nullable NotificationEntry newAlertOverride) {
- if (DEBUG) {
- Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary
- + " oldAlertOverride=" + oldAlertOverride
- + " newAlertOverride=" + newAlertOverride);
- }
- onGroupChanged(group, oldAlertOverride);
}
};
- /**
- * Called when either the suppressed or alertOverride fields of the group changed
- *
- * @param group the group which changed
- * @param oldAlertOverride the previous value of group.alertOverride
- */
- private void onGroupChanged(NotificationGroup group,
- NotificationEntry oldAlertOverride) {
- // Group summary can be null if we are no longer suppressed because the summary was
- // removed. In that case, we don't need to alert the summary.
- if (group.summary == null) {
- if (DEBUG) {
- Log.d(TAG, "onGroupChanged: summary is null");
- }
- return;
- }
- if (group.suppressed || group.alertOverride != null) {
- checkForForwardAlertTransfer(group.summary, oldAlertOverride);
- } else {
- if (DEBUG) {
- Log.d(TAG, "onGroupChanged: maybe transfer back");
- }
- GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
- group.summary.getSbn()));
- // Group is no longer suppressed or overridden.
- // We should check if we need to transfer the alert back to the summary.
- if (groupAlertEntry.mAlertSummaryOnNextAddition) {
- if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
- alertNotificationWhenPossible(group.summary);
- }
- groupAlertEntry.mAlertSummaryOnNextAddition = false;
- } else {
- checkShouldTransferBack(groupAlertEntry);
- }
- }
- }
-
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- if (DEBUG) {
- Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp);
- }
- if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
- // a group summary is alerting; trigger the forward transfer checks
- checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
- }
+ onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
}
- /**
- * Handles changes in a group's suppression or alertOverride, but where at least one of those
- * conditions is still true (either the group is suppressed, the group has an alertOverride,
- * or both). The method determined which kind of child needs to receive the alert, finds the
- * entry currently alerting, and makes the transfer.
- *
- * Internally, this is handled with two main cases: the override needs the alert, or there is
- * no override but the summary is suppressed (so an isolated child needs the alert).
- *
- * @param summary the notification entry of the summary of the logical group.
- * @param oldAlertOverride the former value of group.alertOverride, before whatever event
- * required us to check for for a transfer condition.
- */
- private void checkForForwardAlertTransfer(NotificationEntry summary,
- NotificationEntry oldAlertOverride) {
- if (DEBUG) {
- Log.d(TAG, "checkForForwardAlertTransfer: enter");
- }
- NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
- if (group != null && group.alertOverride != null) {
- handleOverriddenSummaryAlerted(summary);
- } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
- handleSuppressedSummaryAlerted(summary, oldAlertOverride);
+ private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting,
+ AlertingNotificationManager alertManager) {
+ if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.getSbn())) {
+ handleSuppressedSummaryAlerted(entry, alertManager);
}
}
@@ -247,16 +186,9 @@
// see as early as we can if we need to abort a transfer.
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
- if (DEBUG) {
- Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry);
- }
String groupKey = mGroupManager.getGroupKey(entry.getSbn());
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
- if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
- // new pending group entries require us to transfer back from the child to the
- // group, but alertOverrides are only present in very limited circumstances, so
- // while it's possible the group should ALSO alert, the previous detection which set
- // this alertOverride won't be invalidated by this notification added to this group.
+ if (groupAlertEntry != null) {
checkShouldTransferBack(groupAlertEntry);
}
}
@@ -330,128 +262,43 @@
}
/**
- * Handles the scenario where a summary that has been suppressed is itself, or has a former
- * alertOverride (in the form of an isolated logical child) which was alerted. A suppressed
+ * Handles the scenario where a summary that has been suppressed is alerted. A suppressed
* summary should for all intents and purposes be invisible to the user and as a result should
* not alert. When this is the case, it is our responsibility to pass the alert to the
* appropriate child which will be the representative notification alerting for the group.
*
- * @param summary the summary that is suppressed and (potentially) alerting
- * @param oldAlertOverride the alertOverride before whatever event triggered this method. If
- * the alert override was removed, this will be the entry that should
- * be transferred back from.
+ * @param summary the summary that is suppressed and alerting
+ * @param alertManager the alert manager that manages the alerting summary
*/
private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
- NotificationEntry oldAlertOverride) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary);
- }
+ @NonNull AlertingNotificationManager alertManager) {
+ StatusBarNotification sbn = summary.getSbn();
GroupAlertEntry groupAlertEntry =
- mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
-
+ mGroupAlertEntries.get(mGroupManager.getGroupKey(sbn));
if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
+ || !alertManager.isAlerting(sbn.getKey())
|| groupAlertEntry == null) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
- }
- return;
- }
- boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
- boolean priorityIsAlerting = oldAlertOverride != null
- && mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
- if (!summaryIsAlerting && !priorityIsAlerting) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
- }
return;
}
if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
// New children will actually be added to this group, let's not transfer the alert.
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
- }
return;
}
NotificationEntry child =
mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
- if (summaryIsAlerting) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
+ if (child != null) {
+ if (child.getRow().keepInParent()
+ || child.isRowRemoved()
+ || child.isRowDismissed()) {
+ // The notification is actually already removed. No need to alert it.
+ return;
}
- tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
- return;
- }
- // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
- // it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
- // the isolated child which should receive the alert.
- if (!canStillTransferBack(groupAlertEntry)) {
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
+ if (!alertManager.isAlerting(child.getKey()) && onlySummaryAlerts(summary)) {
+ groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
}
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
- }
- tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
- }
-
- /**
- * Checks for and handles the scenario where the given entry is the summary of a group which
- * has an alertOverride, and either the summary itself or one of its logical isolated children
- * is currently alerting (which happens if the summary is suppressed).
- */
- private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary);
- }
- GroupAlertEntry groupAlertEntry =
- mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
- NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
- if (group == null || group.alertOverride == null || groupAlertEntry == null) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
- }
- return;
- }
- boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
- if (summaryIsAlerting) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
- }
- tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
- return;
- }
- // Summary didn't have the alert, so we're in "transfer back" territory. First, make sure
- // it's not too late to transfer back, then remove the alert from any of the logical
- // children, and if one of them was alerting, we can alert the override.
- if (!canStillTransferBack(groupAlertEntry)) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
- }
- return;
- }
- List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
- if (children == null) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
- }
- return;
- }
- children.remove(group.alertOverride); // do not release the alert on our desired destination
- boolean releasedChild = releaseChildAlerts(children);
- if (releasedChild) {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
- }
- tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
- } else {
- if (DEBUG) {
- Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
- }
+ transferAlertState(summary, child, alertManager);
}
}
@@ -460,37 +307,14 @@
* immediately to have the incorrect one up as short as possible. The second should alert
* when possible.
*
- * @param summary entry of the summary
* @param fromEntry entry to transfer alert from
* @param toEntry entry to transfer to
+ * @param alertManager alert manager for the alert type
*/
- private void tryTransferAlertState(
- NotificationEntry summary,
- NotificationEntry fromEntry,
- NotificationEntry toEntry,
- GroupAlertEntry groupAlertEntry) {
- if (toEntry != null) {
- if (toEntry.getRow().keepInParent()
- || toEntry.isRowRemoved()
- || toEntry.isRowDismissed()) {
- // The notification is actually already removed. No need to alert it.
- return;
- }
- if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
- groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
- }
- if (DEBUG) {
- Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry);
- }
- transferAlertState(fromEntry, toEntry);
- }
- }
- private void transferAlertState(@Nullable NotificationEntry fromEntry,
- @NonNull NotificationEntry toEntry) {
- if (fromEntry != null) {
- mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
- }
- alertNotificationWhenPossible(toEntry);
+ private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry,
+ @NonNull AlertingNotificationManager alertManager) {
+ alertManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
+ alertNotificationWhenPossible(toEntry, alertManager);
}
/**
@@ -502,13 +326,11 @@
* more children are coming. Thus, if a child is added within a certain timeframe after we
* transfer, we back out and alert the summary again.
*
- * An alert can only transfer back within a small window of time after a transfer away from the
- * summary to a child happened.
- *
* @param groupAlertEntry group alert entry to check
*/
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
- if (canStillTransferBack(groupAlertEntry)) {
+ if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
+ < ALERT_TRANSFER_TIMEOUT) {
NotificationEntry summary = groupAlertEntry.mGroup.summary;
if (!onlySummaryAlerts(summary)) {
@@ -516,17 +338,30 @@
}
ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
summary.getSbn());
- int numActiveChildren = children.size();
+ int numChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
- int numChildren = numActiveChildren + numPendingChildren;
+ numChildren += numPendingChildren;
if (numChildren <= 1) {
return;
}
- boolean releasedChild = releaseChildAlerts(children);
+ boolean releasedChild = false;
+ for (int i = 0; i < children.size(); i++) {
+ NotificationEntry entry = children.get(i);
+ if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
+ releasedChild = true;
+ mHeadsUpManager.removeNotification(
+ entry.getKey(), true /* releaseImmediately */);
+ }
+ if (mPendingAlerts.containsKey(entry.getKey())) {
+ // This is the child that would've been removed if it was inflated.
+ releasedChild = true;
+ mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
+ }
+ }
if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
- boolean notifyImmediately = numActiveChildren > 1;
+ boolean notifyImmediately = (numChildren - numPendingChildren) > 1;
if (notifyImmediately) {
- alertNotificationWhenPossible(summary);
+ alertNotificationWhenPossible(summary, mHeadsUpManager);
} else {
// Should wait until the pending child inflates before alerting.
groupAlertEntry.mAlertSummaryOnNextAddition = true;
@@ -536,61 +371,25 @@
}
}
- private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
- return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
- < ALERT_TRANSFER_TIMEOUT;
- }
-
- private boolean releaseChildAlerts(List<NotificationEntry> children) {
- boolean releasedChild = false;
- if (SPEW) {
- Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
- }
- for (int i = 0; i < children.size(); i++) {
- NotificationEntry entry = children.get(i);
- if (SPEW) {
- Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
- + " onlySummaryAlerts=" + onlySummaryAlerts(entry)
- + " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
- + " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
- }
- if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
- releasedChild = true;
- mHeadsUpManager.removeNotification(
- entry.getKey(), true /* releaseImmediately */);
- }
- if (mPendingAlerts.containsKey(entry.getKey())) {
- // This is the child that would've been removed if it was inflated.
- releasedChild = true;
- mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
- }
- }
- if (SPEW) {
- Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
- }
- return releasedChild;
- }
-
/**
* Tries to alert the notification. If its content view is not inflated, we inflate and continue
* when the entry finishes inflating the view.
*
* @param entry entry to show
+ * @param alertManager alert manager for the alert type
*/
- private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
- @InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
+ private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
+ @NonNull AlertingNotificationManager alertManager) {
+ @InflationFlag int contentFlag = alertManager.getContentFlag();
final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
if ((params.getContentViews() & contentFlag) == 0) {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry);
- }
mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
params.requireContentViews(contentFlag);
mRowContentBindStage.requestRebind(entry, en -> {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
- alertNotificationWhenPossible(entry);
+ alertNotificationWhenPossible(entry, mHeadsUpManager);
} else {
// The transfer is no longer valid. Free the content.
mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
@@ -601,16 +400,10 @@
});
return;
}
- if (mHeadsUpManager.isAlerting(entry.getKey())) {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry);
- }
- mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
+ if (alertManager.isAlerting(entry.getKey())) {
+ alertManager.updateNotification(entry.getKey(), true /* alert */);
} else {
- if (DEBUG) {
- Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry);
- }
- mHeadsUpManager.showNotification(entry);
+ alertManager.showNotification(entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c79e503..56c0bd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -22,7 +22,6 @@
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static androidx.constraintlayout.widget.ConstraintSet.START;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -1730,7 +1729,6 @@
return;
}
mExpectingSynthesizedDown = true;
- InteractionJankMonitor.getInstance().begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
onTrackingStarted();
updatePanelExpanded();
}
@@ -2075,20 +2073,18 @@
setQsExpansionEnabled(mAmbientState.getScrollY() == 0);
int radius = mScrimCornerRadius;
- if (visible || !mShouldUseSplitNotificationShade) {
- if (!mShouldUseSplitNotificationShade) {
- top = (int) Math.min(qsPanelBottomY, notificationTop);
- bottom = getView().getBottom();
- left = getView().getLeft();
- right = getView().getRight();
- radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius,
- Math.min(top / (float) mScrimCornerRadius, 1f));
- } else {
- top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
- bottom = mNotificationStackScrollLayoutController.getHeight();
- left = mNotificationStackScrollLayoutController.getLeft();
- right = mNotificationStackScrollLayoutController.getRight();
- }
+ if (!mShouldUseSplitNotificationShade) {
+ top = (int) Math.min(qsPanelBottomY, notificationTop);
+ bottom = getView().getBottom();
+ left = getView().getLeft();
+ right = getView().getRight();
+ radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius,
+ Math.min(top / (float) mScrimCornerRadius, 1f));
+ } else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen
+ top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
+ bottom = mNotificationStackScrollLayoutController.getHeight();
+ left = mNotificationStackScrollLayoutController.getLeft();
+ right = mNotificationStackScrollLayoutController.getRight();
}
// Fancy clipping for quick settings
@@ -2698,6 +2694,7 @@
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
mIsExpanding = false;
mMediaHierarchyManager.setCollapsingShadeFromQS(false);
+ mMediaHierarchyManager.setQsExpanded(mQsExpanded);
if (isFullyCollapsed()) {
DejankUtils.postAfterTraversal(new Runnable() {
@Override
@@ -3709,6 +3706,12 @@
@Override
public void flingTopOverscroll(float velocity, boolean open) {
+ // in split shade mode we want to expand/collapse QS only when touch happens within QS
+ if (mShouldUseSplitNotificationShade
+ && (mInitialTouchX < mQsFrame.getX()
+ || mInitialTouchX > mQsFrame.getX() + mQsFrame.getWidth())) {
+ return;
+ }
mLastOverscroll = 0f;
mQsExpansionFromOverscroll = false;
setQsExpansion(mQsExpansionHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 4714c4b..2649309 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -42,6 +42,7 @@
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -139,6 +140,7 @@
*/
private boolean mInstantExpanding;
private boolean mAnimateAfterExpanding;
+ private boolean mIsFlinging;
PanelBar mBar;
@@ -596,6 +598,7 @@
notifyExpandingFinished();
return;
}
+ mIsFlinging = true;
mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
ValueAnimator animator = createHeightAnimator(target);
mFlingTarget = target;
@@ -635,6 +638,12 @@
private boolean mCancelled;
@Override
+ public void onAnimationStart(Animator animation) {
+ InteractionJankMonitor.getInstance()
+ .begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
@@ -679,6 +688,7 @@
}
private void onFlingEnd(boolean cancelled) {
+ mIsFlinging = false;
setAnimator(null);
mKeyguardStateController.notifyPanelFlingEnd();
if (!cancelled) {
@@ -751,6 +761,16 @@
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
+ if (mAmbientState.isExpansionChanging()
+ && !mIsFlinging // Fling already uses interpolated height from end of swipe
+ && !mAmbientState.isOnKeyguard()
+ && !mAmbientState.isDozing()
+ && !mAmbientState.isPulsing()) {
+ final float fraction = h / mView.getHeight();
+ final float interpolatedFraction = new PathInterpolator(0.2f, 0.8f, 0.8f, 1f)
+ .getInterpolation(fraction);
+ h = interpolatedFraction * mView.getHeight();
+ }
maybeOverScrollForShadeFlingOpen(h);
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
@@ -1398,11 +1418,14 @@
case MotionEvent.ACTION_CANCEL:
addMovement(event);
endMotionEvent(event, x, y, false /* forceCancel */);
- InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- } else {
- monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
+ if (mHeightAnimator == null) {
+ InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ } else {
+ monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
}
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f403cc94..1ef84701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -591,6 +591,8 @@
if (mStatusBar.isInLaunchTransition()
|| mKeyguardStateController.isFlingingToDismissKeyguard()) {
+ final boolean wasFlingingToDismissKeyguard =
+ mKeyguardStateController.isFlingingToDismissKeyguard();
mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
@Override
public void run() {
@@ -604,6 +606,11 @@
public void run() {
mStatusBar.hideKeyguard();
mNotificationShadeWindowController.setKeyguardFadingAway(false);
+
+ if (wasFlingingToDismissKeyguard) {
+ mStatusBar.finishKeyguardFadingAway();
+ }
+
mViewMediatorCallback.keyguardGone();
executeAfterKeyguardGoneAction();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index d86dfa5..406f40c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
import org.junit.Assert.assertNotNull
@@ -73,6 +74,8 @@
private lateinit var mediaCarouselController: MediaCarouselController
@Mock
private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock
+ private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@JvmField
@@ -90,7 +93,8 @@
bypassController,
mediaCarouselController,
notificationLockscreenUserManager,
- wakefulnessLifecycle)
+ wakefulnessLifecycle,
+ statusBarKeyguardViewManager)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
@@ -98,7 +102,6 @@
`when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
// We'll use the viewmanager to verify a few calls below, let's reset this.
clearInvocations(mediaCarouselController)
-
}
private fun setupHost(host: MediaHost, location: Int) {
@@ -125,7 +128,8 @@
observer.onStartedGoingToSleep()
clearInvocations(mediaCarouselController)
mediaHiearchyManager.qsExpansion = 0.0f
- verify(mediaCarouselController, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
+ verify(mediaCarouselController, times(0))
+ .onDesiredLocationChanged(ArgumentMatchers.anyInt(),
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index cc322620..0dd1f68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -59,6 +58,7 @@
import com.android.internal.appwidget.IAppWidgetService;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.people.widget.PeopleTileKey;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -73,6 +73,7 @@
import java.util.List;
import java.util.Map;
+import java.util.Optional;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@@ -188,6 +189,8 @@
private PackageManager mPackageManager;
@Mock
private NotificationEntryManager mNotificationEntryManager;
+ @Mock
+ private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private Bundle mOptions;
@@ -212,8 +215,8 @@
when(resources.getConfiguration()).thenReturn(configuration);
when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
- when(mMockContentResolver.query(any(Uri.class), any(), anyString(), any(),
- isNull())).thenReturn(mMockCursor);
+ when(mMockContentResolver.query(any(Uri.class), any(), any(), any(),
+ any())).thenReturn(mMockCursor);
when(mMockContext.getString(R.string.birthday_status)).thenReturn(
mContext.getString(R.string.birthday_status));
when(mMockContext.getString(R.string.basic_status)).thenReturn(
@@ -236,7 +239,8 @@
.build();
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
- .augmentTileFromNotification(mContext, tile, key, mNotificationEntry1, 0);
+ .augmentTileFromNotification(mContext, tile, key, mNotificationEntry1, 0,
+ Optional.empty());
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
assertThat(actual.getNotificationSender()).isEqualTo(null);
@@ -275,7 +279,8 @@
.build();
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
- .augmentTileFromNotification(mContext, tile, key, notificationEntry, 0);
+ .augmentTileFromNotification(mContext, tile, key, notificationEntry, 0,
+ Optional.empty());
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
assertThat(actual.getNotificationSender().toString()).isEqualTo("name");
@@ -291,7 +296,8 @@
.build();
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = PeopleSpaceUtils
- .augmentTileFromNotification(mContext, tile, key, mNotificationEntry3, 0);
+ .augmentTileFromNotification(mContext, tile, key, mNotificationEntry3, 0,
+ Optional.empty());
assertThat(actual.getNotificationContent()).isEqualTo(null);
}
@@ -308,10 +314,11 @@
Map<Integer, PeopleSpaceTile> widgetIdToTile = Map.of(WIDGET_ID_WITH_SHORTCUT,
new PeopleSpaceTile.Builder(mShortcutInfoWithoutPerson,
mContext.getSystemService(LauncherApps.class)).build());
- PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+ PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
widgetIdToTile, widgetIdsArray);
- verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ verify(mPeopleSpaceWidgetManager, never()).updateAppWidgetOptionsAndView(
+ eq(WIDGET_ID_WITH_SHORTCUT),
any());
}
@@ -328,10 +335,11 @@
new PeopleSpaceTile.Builder(mShortcutInfoWithoutPerson,
mContext.getSystemService(LauncherApps.class)).setBirthdayText(
mContext.getString(R.string.birthday_status)).build());
- PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+ PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
widgetIdToTile, widgetIdsArray);
- verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ verify(mPeopleSpaceWidgetManager, times(1)).updateAppWidgetOptionsAndView(
+ eq(WIDGET_ID_WITH_SHORTCUT),
any());
}
@@ -363,10 +371,11 @@
new PeopleSpaceTile.Builder(mShortcutInfo,
mContext.getSystemService(LauncherApps.class)).setBirthdayText(
mContext.getString(R.string.birthday_status)).build());
- PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+ PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
widgetIdToTile, widgetIdsArray);
- verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ verify(mPeopleSpaceWidgetManager, times(1)).updateAppWidgetOptionsAndView(
+ eq(WIDGET_ID_WITH_SHORTCUT),
any());
}
@@ -375,6 +384,9 @@
int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT};
when(mMockCursor.moveToNext()).thenReturn(true, false, true, false);
when(mMockCursor.getString(eq(TEST_COLUMN_INDEX))).thenReturn(TEST_LOOKUP_KEY);
+ when(mMockCursor.getInt(eq(TEST_COLUMN_INDEX + 1))).thenReturn(1);
+ when(mMockCursor.getColumnIndex(eq(ContactsContract.Contacts.STARRED))).thenReturn(
+ TEST_COLUMN_INDEX + 1);
when(mMockCursor.getColumnIndex(eq(ContactsContract.CommonDataKinds.Event.LOOKUP_KEY)
)).thenReturn(TEST_COLUMN_INDEX);
@@ -383,10 +395,11 @@
new PeopleSpaceTile.Builder(mShortcutInfo,
mContext.getSystemService(LauncherApps.class)).setBirthdayText(
mContext.getString(R.string.birthday_status)).build());
- PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+ PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
widgetIdToTile, widgetIdsArray);
- verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ verify(mPeopleSpaceWidgetManager, times(1)).updateAppWidgetOptionsAndView(
+ eq(WIDGET_ID_WITH_SHORTCUT),
any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 764cdee..228e5e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -21,13 +21,20 @@
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.app.people.PeopleSpaceTile.BLOCK_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_CONTACTS;
+import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_STARRED_CONTACTS;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
+import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -53,6 +60,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleTileKey;
import org.junit.Before;
import org.junit.Test;
@@ -148,14 +156,14 @@
TextView textView = mock(TextView.class);
when(textView.getLineHeight()).thenReturn(16);
when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
- mPeopleTileViewHelper = new PeopleTileViewHelper(mContext,
- PERSON_TILE, 0, mOptions);
+ mPeopleTileViewHelper = getPeopleTileViewHelper(
+ PERSON_TILE, mOptions);
}
@Test
public void testCreateRemoteViewsWithLastInteractionTimeUnderOneDayHidden() {
- RemoteViews views = new PeopleTileViewHelper(mContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ PERSON_TILE_WITHOUT_NOTIFICATION, mOptions).getViews();
View result = views.apply(mContext, null);
// Not showing last interaction.
@@ -165,8 +173,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ PERSON_TILE_WITHOUT_NOTIFICATION, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
// Not showing last interaction.
@@ -178,8 +186,8 @@
PeopleSpaceTile tileWithLastInteraction =
PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setLastInteractionTimestamp(
123445L).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithLastInteraction, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithLastInteraction, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -197,8 +205,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithLastInteraction, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithLastInteraction, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show name over predefined icon.
@@ -214,8 +222,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithLastInteraction, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithLastInteraction, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -240,8 +248,8 @@
new ConversationStatus.Builder(
PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
ACTIVITY_GAME).build())).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithAvailabilityAndNewStory, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -257,8 +265,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithAvailabilityAndNewStory, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show name rather than game type.
@@ -274,8 +282,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithAvailabilityAndNewStory, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -298,8 +306,8 @@
NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
ACTIVITY_BIRTHDAY).build())).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithStatusTemplate, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithStatusTemplate, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -318,8 +326,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithStatusTemplate, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithStatusTemplate, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
@@ -336,8 +344,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithStatusTemplate, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithStatusTemplate, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -362,8 +370,8 @@
PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithStatusTemplate, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithStatusTemplate, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -381,8 +389,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithStatusTemplate, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithStatusTemplate, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
@@ -399,8 +407,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithStatusTemplate, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithStatusTemplate, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -420,14 +428,128 @@
}
@Test
+ public void testCreateRemoteViewsWithPackageSuspended() {
+ PeopleSpaceTile tile = PERSON_TILE.toBuilder()
+ .setIsPackageSuspended(true)
+ .build();
+ RemoteViews views = getPeopleTileViewHelper(
+ tile, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithUserQuieted() {
+ PeopleSpaceTile tile = PERSON_TILE.toBuilder()
+ .setIsUserQuieted(true)
+ .build();
+ RemoteViews views = getPeopleTileViewHelper(
+ tile, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+ }
+
+ @Test
+ public void testCreateRemoteViewsWithDndBlocking() {
+ PeopleSpaceTile tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(BLOCK_CONVERSATIONS)
+ .build();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ View result = views.apply(mContext, null);
+
+ assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(BLOCK_CONVERSATIONS)
+ .setCanBypassDnd(true)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+ .setIsImportantConversation(true)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_STARRED_CONTACTS)
+ .setContactAffinity(VALID_CONTACT)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_STARRED_CONTACTS)
+ .setContactAffinity(STARRED_CONTACT)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_CONTACTS)
+ .setContactAffinity(STARRED_CONTACT)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_CONTACTS)
+ .setContactAffinity(VALID_CONTACT)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+ tileWithDndBlocking = PERSON_TILE.toBuilder()
+ .setNotificationPolicyState(SHOW_CONTACTS)
+ .build();
+ views = getPeopleTileViewHelper(
+ tileWithDndBlocking, mOptions).getViews();
+ result = views.apply(mContext, null);
+
+ assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+ }
+
+ @Test
public void testCreateRemoteViewsWithMissedCallNotification() {
PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
.setNotificationDataUri(null)
.setNotificationCategory(CATEGORY_MISSED_CALL)
.setNotificationContent(MISSED_CALL)
.build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithMissedCallNotification, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithMissedCallNotification, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -446,8 +568,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithMissedCallNotification, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithMissedCallNotification, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
@@ -463,8 +585,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithMissedCallNotification, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithMissedCallNotification, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -489,8 +611,8 @@
.setNotificationDataUri(null)
.setStatuses(Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -512,8 +634,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
@@ -531,8 +653,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -561,8 +683,8 @@
.setNotificationDataUri(null)
.setStatuses(Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY)).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -588,8 +710,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
@@ -607,8 +729,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -642,8 +764,8 @@
.setStatuses(Arrays.asList(GAME_STATUS,
NEW_STORY_WITH_AVAILABILITY))
.setMessagesCount(2).build();
- RemoteViews views = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews views = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View result = views.apply(mContext, null);
TextView name = (TextView) result.findViewById(R.id.name);
@@ -665,8 +787,8 @@
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
- RemoteViews smallView = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews smallView = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View smallResult = smallView.apply(mContext, null);
// Show icon instead of name.
@@ -684,8 +806,8 @@
getSizeInDp(R.dimen.required_width_for_large));
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_height_for_large));
- RemoteViews largeView = new PeopleTileViewHelper(mContext,
- tileWithStatusAndNotification, 0, mOptions).getViews();
+ RemoteViews largeView = getPeopleTileViewHelper(
+ tileWithStatusAndNotification, mOptions).getViews();
View largeResult = largeView.apply(mContext, null);
name = (TextView) largeResult.findViewById(R.id.name);
@@ -858,4 +980,9 @@
return (int) (mContext.getResources().getDimension(dimenResourceId)
/ mContext.getResources().getDisplayMetrics().density);
}
+
+ private PeopleTileViewHelper getPeopleTileViewHelper(PeopleSpaceTile tile, Bundle options) {
+ return new PeopleTileViewHelper(mContext, tile, 0, options,
+ new PeopleTileKey(tile.getId(), 0, tile.getPackageName()));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 411fb02..f31f326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -20,11 +20,32 @@
import static android.app.Notification.EXTRA_PEOPLE_LIST;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.PeopleSpaceTile.BLOCK_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_CONTACTS;
+import static android.app.people.PeopleSpaceTile.SHOW_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_STARRED_CONTACTS;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.content.Intent.ACTION_PACKAGES_SUSPENDED;
import static android.content.PermissionChecker.PERMISSION_GRANTED;
import static android.content.PermissionChecker.PERMISSION_HARD_DENIED;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
@@ -73,6 +94,7 @@
import android.os.UserManager;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import androidx.preference.PreferenceManager;
@@ -89,6 +111,7 @@
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -99,12 +122,14 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -158,6 +183,16 @@
// Same contact uri.
.setContactUri(URI)
.build();
+ private static final int ALL_SUPPRESSED_VISUAL_EFFECTS = SUPPRESSED_EFFECT_SCREEN_OFF
+ | SUPPRESSED_EFFECT_SCREEN_ON
+ | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
+ | SUPPRESSED_EFFECT_AMBIENT
+ | SUPPRESSED_EFFECT_STATUS_BAR
+ | SUPPRESSED_EFFECT_BADGE
+ | SUPPRESSED_EFFECT_LIGHTS
+ | SUPPRESSED_EFFECT_PEEK
+ | SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+
private ShortcutInfo mShortcutInfo;
private NotificationEntry mNotificationEntry;
@@ -182,9 +217,13 @@
@Mock
private PackageManager mPackageManager;
@Mock
- private INotificationManager mNotificationManager;
+ private INotificationManager mINotificationManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private NotificationManager.Policy mNotificationPolicy;
@Captor
private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -194,19 +233,16 @@
private final NoManSimulator mNoMan = new NoManSimulator();
private final FakeSystemClock mClock = new FakeSystemClock();
- private PeopleSpaceWidgetProvider mProvider;
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(mClock);
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLauncherApps = mock(LauncherApps.class);
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
- mManager = new PeopleSpaceWidgetManager(mContext);
- mProvider = new PeopleSpaceWidgetProvider();
- mProvider.setPeopleSpaceWidgetManager(mManager);
- mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager,
- mLauncherApps, mNotificationEntryManager, mPackageManager, true, mProvider,
- mUserManager, mNotificationManager);
+ mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
+ mPeopleManager, mLauncherApps, mNotificationEntryManager, mPackageManager,
+ mUserManager, mINotificationManager, mNotificationManager, mFakeExecutor);
mManager.attach(mListenerService);
verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
@@ -218,7 +254,19 @@
addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI);
when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
.thenReturn(new Bundle());
+
when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+ when(mPackageManager.isPackageSuspended(any())).thenReturn(false);
+ setFinalField("suppressedVisualEffects", ALL_SUPPRESSED_VISUAL_EFFECTS);
+ when(mNotificationPolicy.allowConversationsFrom()).thenReturn(CONVERSATION_SENDERS_ANYONE);
+ when(mNotificationPolicy.allowConversations()).thenReturn(false);
+ when(mNotificationPolicy.allowMessagesFrom()).thenReturn(ZenModeConfig.SOURCE_ANYONE);
+ when(mNotificationPolicy.allowMessages()).thenReturn(false);
+ when(mNotificationManager.getNotificationPolicy()).thenReturn(mNotificationPolicy);
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_ALL);
+ int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT};
+ when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_A);
when(mMockContext.getUserId()).thenReturn(0);
@@ -242,7 +290,7 @@
ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
SHORTCUT_ID + 2,
true, 1);
- when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+ when(mINotificationManager.getConversations(anyBoolean())).thenReturn(
new ParceledListSlice(Arrays.asList(
newerNonImportantConversation, newerImportantConversation,
olderImportantConversation)));
@@ -280,7 +328,7 @@
ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
SHORTCUT_ID + 2,
true, 1);
- when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+ when(mINotificationManager.getConversations(anyBoolean())).thenReturn(
new ParceledListSlice(Arrays.asList(
newerNonImportantConversation, newerImportantConversation,
olderImportantConversation)));
@@ -306,7 +354,7 @@
ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
SHORTCUT_ID + 2,
true, 1);
- when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+ when(mINotificationManager.getConversations(anyBoolean())).thenReturn(
new ParceledListSlice(Arrays.asList(
newerNonImportantConversation, newerImportantConversation,
olderImportantConversation)));
@@ -1027,8 +1075,7 @@
public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners()
throws Exception {
addSecondWidgetForPersonTile();
- mProvider.onUpdate(mContext, mAppWidgetManager,
- new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT});
+ mManager.updateWidgets(new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT});
// Delete only one widget for the conversation.
mManager.deleteWidgets(new int[]{WIDGET_ID_WITH_SHORTCUT});
@@ -1050,7 +1097,7 @@
eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS));
// Delete all widgets for the conversation.
- mProvider.onDeleted(mContext, new int[]{SECOND_WIDGET_ID_WITH_SHORTCUT});
+ mManager.deleteWidgets(new int[]{SECOND_WIDGET_ID_WITH_SHORTCUT});
// Check deleted storage.
SharedPreferences secondWidgetSp = mContext.getSharedPreferences(
@@ -1154,7 +1201,7 @@
new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A));
when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(channel);
PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A);
- PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key);
+ PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT);
assertThat(tile.getId()).isEqualTo(key.getShortcutId());
}
@@ -1162,7 +1209,7 @@
public void testGetPeopleTileFromPersistentStorageNoConversation() throws RemoteException {
when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(null);
PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A);
- PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key);
+ PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT);
assertThat(tile).isNull();
}
@@ -1195,18 +1242,25 @@
@Test
public void testAugmentTileFromNotifications() {
+ clearStorage();
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ assertThat(sp.getString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), null)).isEqualTo(null);
PeopleSpaceTile tile =
new PeopleSpaceTile
.Builder(SHORTCUT_ID, "userName", ICON, new Intent())
.setPackageName(TEST_PACKAGE_A)
.setUserHandle(new UserHandle(0))
.build();
+
PeopleTileKey key = new PeopleTileKey(tile);
PeopleSpaceTile actual = mManager.augmentTileFromNotifications(tile, key, EMPTY_STRING,
- Map.of(new PeopleTileKey(mNotificationEntry),
- new HashSet<>(Collections.singleton(mNotificationEntry))));
+ Map.of(new PeopleTileKey(mNotificationEntry),
+ new HashSet<>(Collections.singleton(mNotificationEntry))),
+ Optional.of(WIDGET_ID_WITH_SHORTCUT));
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_CONTENT_1);
+ assertThat(sp.getString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), null)).isEqualTo(
+ URI.toString());
}
@Test
@@ -1221,7 +1275,8 @@
PeopleSpaceTile actual = mManager
.augmentTileFromNotifications(tile, key, EMPTY_STRING,
Map.of(new PeopleTileKey(mNotificationEntry),
- new HashSet<>(Collections.singleton(mNotificationEntry))));
+ new HashSet<>(Collections.singleton(mNotificationEntry))),
+ Optional.empty());
assertThat(actual.getNotificationContent()).isEqualTo(null);
}
@@ -1238,7 +1293,8 @@
.thenReturn(List.of(mNotificationEntry));
PeopleSpaceTile actual =
- mManager.augmentTileFromNotificationEntryManager(tile);
+ mManager.augmentTileFromNotificationEntryManager(tile,
+ Optional.of(WIDGET_ID_WITH_SHORTCUT));
assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_CONTENT_1);
@@ -1246,6 +1302,202 @@
.getVisibleNotifications();
}
+ @Test
+ public void testUpdateWidgetsOnStateChange() {
+ mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.isPackageSuspended()).isFalse();
+ assertThat(tile.isUserQuieted()).isFalse();
+ assertThat(tile.canBypassDnd()).isFalse();
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+ verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+ any());
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeWithUserQuieted() {
+ when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
+
+ mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.isPackageSuspended()).isFalse();
+ assertThat(tile.isUserQuieted()).isTrue();
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeWithPackageSuspended() throws Exception {
+ when(mPackageManager.isPackageSuspended(any())).thenReturn(true);
+
+ mManager.updateWidgetsOnStateChange(ACTION_PACKAGES_SUSPENDED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ Bundle bundle = mBundleArgumentCaptor.getValue();
+ PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.isPackageSuspended()).isTrue();
+ assertThat(tile.isUserQuieted()).isFalse();
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeNotInDnd() {
+ int expected = 0;
+ mManager.updateWidgetsOnStateChange(NotificationManager
+ .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllConversations() {
+ int expected = 0;
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_PRIORITY);
+ when(mNotificationPolicy.allowConversations()).thenReturn(true);
+ setFinalField("priorityConversationSenders", CONVERSATION_SENDERS_ANYONE);
+
+ mManager.updateWidgetsOnStateChange(NotificationManager
+ .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllowOnlyImportantConversations() {
+ int expected = 0;
+ // Only allow important conversations.
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_PRIORITY);
+ when(mNotificationPolicy.allowConversations()).thenReturn(true);
+ setFinalField("priorityConversationSenders", CONVERSATION_SENDERS_IMPORTANT);
+
+ mManager.updateWidgetsOnStateChange(NotificationManager
+ .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(
+ expected | SHOW_IMPORTANT_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllowNoConversations() {
+ int expected = 0;
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_PRIORITY);
+ when(mNotificationPolicy.allowConversations()).thenReturn(false);
+
+ mManager.updateWidgetsOnStateChange(NotificationManager
+ .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | BLOCK_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllowNoConversationsAllowContactMessages() {
+ int expected = 0;
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_PRIORITY);
+ when(mNotificationPolicy.allowConversations()).thenReturn(false);
+ when(mNotificationPolicy.allowMessagesFrom()).thenReturn(ZenModeConfig.SOURCE_CONTACT);
+ when(mNotificationPolicy.allowMessages()).thenReturn(true);
+
+ mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONTACTS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllowNoConversationsAllowStarredContactMessages() {
+ int expected = 0;
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_PRIORITY);
+ when(mNotificationPolicy.allowConversations()).thenReturn(false);
+ when(mNotificationPolicy.allowMessagesFrom()).thenReturn(ZenModeConfig.SOURCE_STAR);
+ when(mNotificationPolicy.allowMessages()).thenReturn(true);
+
+ mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_STARRED_CONTACTS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllowAlarmsOnly() {
+ int expected = 0;
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_ALARMS);
+
+ mManager.updateWidgetsOnStateChange(NotificationManager
+ .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | BLOCK_CONVERSATIONS);
+ }
+
+ @Test
+ public void testUpdateWidgetsOnStateChangeAllowVisualEffectsAndAllowAlarmsOnly() {
+ int expected = 0;
+ // If we show visuals, but just only make sounds for alarms, still show content in tiles.
+ when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+ INTERRUPTION_FILTER_ALARMS);
+ setFinalField("suppressedVisualEffects", SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
+ | SUPPRESSED_EFFECT_AMBIENT);
+
+ mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+ verify(mAppWidgetManager, times(1))
+ .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+ mBundleArgumentCaptor.capture());
+ PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+ assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
+ }
+
+ private void setFinalField(String fieldName, int value) {
+ try {
+ Field field = NotificationManager.Policy.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(mNotificationPolicy, value);
+ } catch (Exception e) {
+ }
+ }
+
/**
* Adds another widget for {@code PERSON_TILE} with widget ID: {@code
* SECOND_WIDGET_ID_WITH_SHORTCUT}.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 68ed2a5..6f0ae22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -474,6 +474,22 @@
}
@Test
+ public void transientIndication_visibleWhenDozing_ignoresFingerprintCancellation() {
+ createController();
+
+ mController.setVisible(true);
+ reset(mRotateTextViewController);
+ mController.getKeyguardCallback().onBiometricError(
+ FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, "foo",
+ BiometricSourceType.FINGERPRINT);
+ mController.getKeyguardCallback().onBiometricError(
+ FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
+ BiometricSourceType.FINGERPRINT);
+
+ verifyNoTransientMessage();
+ }
+
+ @Test
public void transientIndication_swipeUpToRetry() {
createController();
String message = mContext.getString(R.string.keyguard_retry);
@@ -668,4 +684,8 @@
private void verifyTransientMessage(String message) {
verify(mRotateTextViewController).showTransient(eq(message), anyBoolean());
}
+
+ private void verifyNoTransientMessage() {
+ verify(mRotateTextViewController, never()).showTransient(any(), anyBoolean());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 5d29f52..e85e19f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -21,6 +21,8 @@
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -150,9 +152,6 @@
private ShadeController mShadeController;
@Mock
private ConversationIconFactory mIconFactory;
- @Mock(answer = Answers.RETURNS_SELF)
- private PriorityOnboardingDialogController.Builder mBuilder;
- private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider = () -> mBuilder;
@Mock
private Notification.BubbleMetadata mBubbleMetadata;
private Handler mTestHandler;
@@ -236,8 +235,6 @@
when(mMockINotificationManager.getConsolidatedNotificationPolicy())
.thenReturn(mock(NotificationManager.Policy.class));
- when(mBuilder.build()).thenReturn(mock(PriorityOnboardingDialogController.class));
-
when(mPeopleSpaceWidgetManager.requestPinAppWidget(any(), any())).thenReturn(true);
}
@@ -258,7 +255,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -285,7 +281,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -340,7 +335,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -368,7 +362,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -395,7 +388,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -433,7 +425,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -464,7 +455,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -493,7 +483,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -523,7 +512,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
false,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -551,7 +539,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -582,7 +569,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -616,7 +602,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -626,6 +611,110 @@
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
mContext.getString(R.string.notification_channel_summary_default_with_bubbles,
"App Name"));
+ assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+ .isEqualTo(mContext.getString(
+ R.string.notification_channel_summary_priority_bubble));
+ }
+
+ @Test
+ public void testBindNotification_priorityDnd() throws Exception {
+ NotificationManager.Policy policy = new NotificationManager.Policy(
+ PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_ANYONE);
+ when(mMockINotificationManager.getConsolidatedNotificationPolicy())
+ .thenReturn(policy);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mConversationChannel.setImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportantConversation(false);
+ mConversationChannel.setAllowBubbles(false);
+ mNotificationInfo.bindNotification(
+ -1,
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+ assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+ .isEqualTo(mContext.getString(
+ R.string.notification_channel_summary_priority_dnd));
+ }
+
+ @Test
+ public void testBindNotification_priorityBaseline() throws Exception {
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mConversationChannel.setImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportantConversation(false);
+ mConversationChannel.setAllowBubbles(false);
+ mNotificationInfo.bindNotification(
+ -1,
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+ assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+ .isEqualTo(mContext.getString(
+ R.string.notification_channel_summary_priority_baseline));
+ }
+
+ @Test
+ public void testBindNotification_priorityDndAndBubble() throws Exception {
+ NotificationManager.Policy policy = new NotificationManager.Policy(
+ PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_ANYONE);
+ when(mMockINotificationManager.getConsolidatedNotificationPolicy())
+ .thenReturn(policy);
+
+ when(mMockINotificationManager.getBubblePreferenceForPackage(anyString(), anyInt()))
+ .thenReturn(BUBBLE_PREFERENCE_ALL);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mConversationChannel.setImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportantConversation(false);
+ mConversationChannel.setAllowBubbles(true);
+ mNotificationInfo.bindNotification(
+ -1,
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+ assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+ .isEqualTo(mContext.getString(
+ R.string.notification_channel_summary_priority_all));
}
@Test
@@ -649,7 +738,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -696,7 +784,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -742,7 +829,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -789,7 +875,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -829,7 +914,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -868,7 +952,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -911,7 +994,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -944,7 +1026,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -976,7 +1057,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1015,7 +1095,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1054,7 +1133,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1092,7 +1170,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1129,7 +1206,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1157,7 +1233,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1168,97 +1243,8 @@
}
@Test
- public void testSelectPriorityPresentsOnboarding_firstTime() {
- // GIVEN pref is false
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, false);
-
- // GIVEN the priority onboarding screen is present
- PriorityOnboardingDialogController.Builder b =
- mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
- PriorityOnboardingDialogController controller =
- mock(PriorityOnboardingDialogController.class);
- when(b.build()).thenReturn(controller);
-
- // GIVEN the user is changing conversation settings
- mNotificationInfo.bindNotification(
- -1,
- mShortcutManager,
- mMockPackageManager,
- mPeopleSpaceWidgetManager,
- mMockINotificationManager,
- mOnUserInteractionCallback,
- TEST_PACKAGE_NAME,
- mNotificationChannel,
- mEntry,
- mBubbleMetadata,
- null,
- null,
- mIconFactory,
- mContext,
- () -> b,
- true,
- mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
-
- // WHEN user clicks "priority"
- mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
- verify(controller, never()).show();
-
- // and then done
- mNotificationInfo.findViewById(R.id.done).performClick();
-
- // THEN the user is presented with the priority onboarding screen
- verify(controller, atLeastOnce()).show();
- }
-
- @Test
- public void testSelectPriorityDoesNotShowOnboarding_secondTime() {
- //WHEN pref is true
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
-
- PriorityOnboardingDialogController.Builder b =
- mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
- PriorityOnboardingDialogController controller =
- mock(PriorityOnboardingDialogController.class);
- when(b.build()).thenReturn(controller);
-
- mNotificationInfo.bindNotification(
- -1,
- mShortcutManager,
- mMockPackageManager,
- mPeopleSpaceWidgetManager,
- mMockINotificationManager,
- mOnUserInteractionCallback,
- TEST_PACKAGE_NAME,
- mNotificationChannel,
- mEntry,
- mBubbleMetadata,
- null,
- null,
- mIconFactory,
- mContext,
- () -> b,
- true,
- mTestHandler,
- mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
-
- // WHEN user clicks "priority"
- mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
- verify(controller, never()).show();
-
- // and then done
- mNotificationInfo.findViewById(R.id.done).performClick();
-
- // THEN the user is presented with the priority onboarding screen
- verify(controller, never()).show();
- }
-
- @Test
public void testSelectPriorityRequestsPinPeopleTile() {
- //WHEN pref is true and channel is default importance
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
+ //WHEN channel is default importance
mNotificationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
-1,
@@ -1275,7 +1261,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1293,9 +1278,6 @@
@Test
public void testSelectDefaultDoesNotRequestPinPeopleTile() {
- //WHEN pref is true
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
-
mNotificationInfo.bindNotification(
-1,
mShortcutManager,
@@ -1311,7 +1293,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
@@ -1329,8 +1310,6 @@
@Test
public void testSelectPriority_AlreadyPriority_DoesNotRequestPinPeopleTile() {
- //WHEN pref is true and channel is priority
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(true);
@@ -1350,7 +1329,6 @@
null,
mIconFactory,
mContext,
- mBuilderProvider,
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index bfce2a5..9f537f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -139,9 +139,6 @@
@Mock private BubblesManager mBubblesManager;
@Mock private ShadeController mShadeController;
@Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
- @Mock(answer = Answers.RETURNS_SELF)
- private PriorityOnboardingDialogController.Builder mBuilder;
- private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder;
@Mock private AssistantFeedbackController mAssistantFeedbackController;
@Before
@@ -163,7 +160,7 @@
() -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
mINotificationManager, mNotificationEntryManager, mPeopleSpaceWidgetManager,
mLauncherApps, mShortcutManager, mChannelEditorDialogController, mContextTracker,
- mProvider, mAssistantFeedbackController, Optional.of(mBubblesManager),
+ mAssistantFeedbackController, Optional.of(mBubblesManager),
new UiEventLoggerFake(), mOnUserInteractionCallback, mShadeController);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mCheckSaveListener, mOnSettingsClickListener);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 61de53a..7403af7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,6 +16,9 @@
package com.android.server.accessibility;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+
import android.annotation.MainThread;
import android.content.Context;
import android.graphics.Region;
@@ -557,12 +560,16 @@
MagnificationGestureHandler magnificationGestureHandler;
if (mAms.getMagnificationMode(displayId)
== Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
- magnificationGestureHandler = new WindowMagnificationGestureHandler(displayContext,
+ final Context uiContext = displayContext.createWindowContext(
+ TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
+ magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext,
mAms.getWindowMagnificationMgr(), mAms.getMagnificationController(),
detectControlGestures, triggerable,
displayId);
} else {
- magnificationGestureHandler = new FullScreenMagnificationGestureHandler(displayContext,
+ final Context uiContext = displayContext.createWindowContext(
+ TYPE_MAGNIFICATION_OVERLAY, null /* options */);
+ magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext,
mAms.getFullScreenMagnificationController(), mAms.getMagnificationController(),
detectControlGestures, triggerable,
new WindowMagnificationPromptController(displayContext, mUserId), displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 2434e2c..f7d1b9a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -34,6 +34,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UiContext;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -139,7 +140,7 @@
private PointerCoords[] mTempPointerCoords;
private PointerProperties[] mTempPointerProperties;
- public FullScreenMagnificationGestureHandler(Context context,
+ public FullScreenMagnificationGestureHandler(@UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
Callback callback,
boolean detectTripleTap,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
index 07f22dc..c5495d9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
@@ -19,6 +19,7 @@
import static java.lang.Math.abs;
import android.annotation.NonNull;
+import android.annotation.UiContext;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
@@ -63,8 +64,8 @@
private boolean mScaling;
private boolean mEnable;
- PanningScalingHandler(Context context, float maxScale, float minScale, boolean blockScroll,
- @NonNull MagnificationDelegate magnificationDelegate) {
+ PanningScalingHandler(@UiContext Context context, float maxScale, float minScale,
+ boolean blockScroll, @NonNull MagnificationDelegate magnificationDelegate) {
mDisplayId = context.getDisplayId();
mMaxScale = maxScale;
mMinScale = minScale;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index fa34062..4fb9a03 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -24,6 +24,7 @@
import static java.util.Arrays.copyOfRange;
import android.annotation.Nullable;
+import android.annotation.UiContext;
import android.content.Context;
import android.graphics.Point;
import android.provider.Settings;
@@ -86,7 +87,7 @@
private final Context mContext;
private final Point mTempPoint = new Point();
- public WindowMagnificationGestureHandler(Context context,
+ public WindowMagnificationGestureHandler(@UiContext Context context,
WindowMagnificationManager windowMagnificationMgr,
Callback callback,
boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) {
@@ -342,7 +343,7 @@
*/
private final boolean mDetectTripleTap;
- DetectingState(Context context, boolean detectTripleTap) {
+ DetectingState(@UiContext Context context, boolean detectTripleTap) {
mDetectTripleTap = detectTripleTap;
final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1,
mDetectTripleTap
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ceb12c8..9e8b9c6 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -36,6 +36,15 @@
}
],
"file_patterns": ["ClipboardService\\.java"]
+ },
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.sensorprivacy"
+ }
+ ],
+ "file_patterns": ["SensorPrivacyService\\.java"]
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 6cd7eb7..f7ce6dd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -73,10 +73,16 @@
private static final String KEY_POWER_CHECK_MAX_CPU_2 = "power_check_max_cpu_2";
private static final String KEY_POWER_CHECK_MAX_CPU_3 = "power_check_max_cpu_3";
private static final String KEY_POWER_CHECK_MAX_CPU_4 = "power_check_max_cpu_4";
- private static final String KEY_SERVICE_USAGE_INTERACTION_TIME
- = "service_usage_interaction_time";
- private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL
- = "usage_stats_interaction_interval";
+ /** Used for all apps on R and earlier versions. */
+ private static final String KEY_SERVICE_USAGE_INTERACTION_TIME_PRE_S =
+ "service_usage_interaction_time";
+ private static final String KEY_SERVICE_USAGE_INTERACTION_TIME_POST_S =
+ "service_usage_interaction_time_post_s";
+ /** Used for all apps on R and earlier versions. */
+ private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL_PRE_S =
+ "usage_stats_interaction_interval";
+ private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL_POST_S =
+ "usage_stats_interaction_interval_post_s";
private static final String KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES =
"imperceptible_kill_exempt_packages";
private static final String KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES =
@@ -120,8 +126,10 @@
private static final int DEFAULT_POWER_CHECK_MAX_CPU_2 = 25;
private static final int DEFAULT_POWER_CHECK_MAX_CPU_3 = 10;
private static final int DEFAULT_POWER_CHECK_MAX_CPU_4 = 2;
- private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
- private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL = 2*60*60*1000L;
+ private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME_PRE_S = 30 * 60 * 1000;
+ private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME_POST_S = 60 * 1000;
+ private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_PRE_S = 2 * 60 * 60 * 1000;
+ private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_POST_S = 10 * 60 * 1000;
private static final long DEFAULT_SERVICE_RESTART_DURATION = 1*1000;
private static final long DEFAULT_SERVICE_RESET_RUN_DURATION = 60*1000;
private static final int DEFAULT_SERVICE_RESTART_DURATION_FACTOR = 4;
@@ -303,11 +311,23 @@
// This is the amount of time an app needs to be running a foreground service before
// we will consider it to be doing interaction for usage stats.
- long SERVICE_USAGE_INTERACTION_TIME = DEFAULT_SERVICE_USAGE_INTERACTION_TIME;
+ // Only used for apps targeting pre-S versions.
+ long SERVICE_USAGE_INTERACTION_TIME_PRE_S = DEFAULT_SERVICE_USAGE_INTERACTION_TIME_PRE_S;
+
+ // This is the amount of time an app needs to be running a foreground service before
+ // we will consider it to be doing interaction for usage stats.
+ // Only used for apps targeting versions S and above.
+ long SERVICE_USAGE_INTERACTION_TIME_POST_S = DEFAULT_SERVICE_USAGE_INTERACTION_TIME_POST_S;
// Maximum amount of time we will allow to elapse before re-reporting usage stats
// interaction with foreground processes.
- long USAGE_STATS_INTERACTION_INTERVAL = DEFAULT_USAGE_STATS_INTERACTION_INTERVAL;
+ // Only used for apps targeting pre-S versions.
+ long USAGE_STATS_INTERACTION_INTERVAL_PRE_S = DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
+
+ // Maximum amount of time we will allow to elapse before re-reporting usage stats
+ // interaction with foreground processes.
+ // Only used for apps targeting versions S and above.
+ long USAGE_STATS_INTERACTION_INTERVAL_POST_S = DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_POST_S;
// How long a service needs to be running until restarting its process
// is no longer considered to be a relaunch of the service.
@@ -817,10 +837,18 @@
DEFAULT_POWER_CHECK_MAX_CPU_3);
POWER_CHECK_MAX_CPU_4 = mParser.getInt(KEY_POWER_CHECK_MAX_CPU_4,
DEFAULT_POWER_CHECK_MAX_CPU_4);
- SERVICE_USAGE_INTERACTION_TIME = mParser.getLong(KEY_SERVICE_USAGE_INTERACTION_TIME,
- DEFAULT_SERVICE_USAGE_INTERACTION_TIME);
- USAGE_STATS_INTERACTION_INTERVAL = mParser.getLong(KEY_USAGE_STATS_INTERACTION_INTERVAL,
- DEFAULT_USAGE_STATS_INTERACTION_INTERVAL);
+ SERVICE_USAGE_INTERACTION_TIME_PRE_S = mParser.getLong(
+ KEY_SERVICE_USAGE_INTERACTION_TIME_PRE_S,
+ DEFAULT_SERVICE_USAGE_INTERACTION_TIME_PRE_S);
+ SERVICE_USAGE_INTERACTION_TIME_POST_S = mParser.getLong(
+ KEY_SERVICE_USAGE_INTERACTION_TIME_POST_S,
+ DEFAULT_SERVICE_USAGE_INTERACTION_TIME_POST_S);
+ USAGE_STATS_INTERACTION_INTERVAL_PRE_S = mParser.getLong(
+ KEY_USAGE_STATS_INTERACTION_INTERVAL_PRE_S,
+ DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_PRE_S);
+ USAGE_STATS_INTERACTION_INTERVAL_POST_S = mParser.getLong(
+ KEY_USAGE_STATS_INTERACTION_INTERVAL_POST_S,
+ DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_POST_S);
SERVICE_RESTART_DURATION = mParser.getLong(KEY_SERVICE_RESTART_DURATION,
DEFAULT_SERVICE_RESTART_DURATION);
SERVICE_RESET_RUN_DURATION = mParser.getLong(KEY_SERVICE_RESET_RUN_DURATION,
@@ -1135,10 +1163,14 @@
pw.println(POWER_CHECK_MAX_CPU_3);
pw.print(" "); pw.print(KEY_POWER_CHECK_MAX_CPU_4); pw.print("=");
pw.println(POWER_CHECK_MAX_CPU_4);
- pw.print(" "); pw.print(KEY_SERVICE_USAGE_INTERACTION_TIME); pw.print("=");
- pw.println(SERVICE_USAGE_INTERACTION_TIME);
- pw.print(" "); pw.print(KEY_USAGE_STATS_INTERACTION_INTERVAL); pw.print("=");
- pw.println(USAGE_STATS_INTERACTION_INTERVAL);
+ pw.print(" "); pw.print(KEY_SERVICE_USAGE_INTERACTION_TIME_PRE_S); pw.print("=");
+ pw.println(SERVICE_USAGE_INTERACTION_TIME_PRE_S);
+ pw.print(" "); pw.print(KEY_SERVICE_USAGE_INTERACTION_TIME_POST_S); pw.print("=");
+ pw.println(SERVICE_USAGE_INTERACTION_TIME_POST_S);
+ pw.print(" "); pw.print(KEY_USAGE_STATS_INTERACTION_INTERVAL_PRE_S); pw.print("=");
+ pw.println(USAGE_STATS_INTERACTION_INTERVAL_PRE_S);
+ pw.print(" "); pw.print(KEY_USAGE_STATS_INTERACTION_INTERVAL_POST_S); pw.print("=");
+ pw.println(USAGE_STATS_INTERACTION_INTERVAL_POST_S);
pw.print(" "); pw.print(KEY_SERVICE_RESTART_DURATION); pw.print("=");
pw.println(SERVICE_RESTART_DURATION);
pw.print(" "); pw.print(KEY_SERVICE_RESET_RUN_DURATION); pw.print("=");
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 649d050..b413010 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -80,6 +80,7 @@
import android.app.usage.UsageEvents;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -122,7 +123,7 @@
/**
* All of the code required to compute proc states and oom_adj values.
*/
-public final class OomAdjuster {
+public class OomAdjuster {
static final String TAG = "OomAdjuster";
static final String OOM_ADJ_REASON_METHOD = "updateOomAdj";
static final String OOM_ADJ_REASON_NONE = OOM_ADJ_REASON_METHOD + "_meh";
@@ -164,6 +165,14 @@
static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L;
/**
+ * For apps targeting S+, this determines whether to use a shorter timeout before elevating the
+ * standby bucket to ACTIVE when apps start a foreground service.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+ static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
+
+ /**
* For some direct access we need to power manager.
*/
PowerManagerInternal mLocalPowerManager;
@@ -249,7 +258,9 @@
private final PlatformCompatCache mPlatformCompatCache;
- private static class PlatformCompatCache {
+ /** Overrideable by a test */
+ @VisibleForTesting
+ static class PlatformCompatCache {
private final PlatformCompat mPlatformCompat;
private final IPlatformCompat mIPlatformCompatProxy;
private final LongSparseArray<CacheItem> mCaches = new LongSparseArray<>();
@@ -278,6 +289,20 @@
: mIPlatformCompatProxy.isChangeEnabled(changeId, app);
}
+ /**
+ * Same as {@link #isChangeEnabled(long, ApplicationInfo)} but instead of throwing a
+ * RemoteException from platform compat, it returns the default value provided.
+ */
+ boolean isChangeEnabled(long changeId, ApplicationInfo app, boolean defaultValue) {
+ try {
+ return mCacheEnabled ? mCaches.get(changeId).isChangeEnabled(app)
+ : mIPlatformCompatProxy.isChangeEnabled(changeId, app);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error reading platform compat change " + changeId, e);
+ return defaultValue;
+ }
+ }
+
void invalidate(ApplicationInfo app) {
for (int i = mCaches.size() - 1; i >= 0; i--) {
mCaches.valueAt(i).invalidate(app);
@@ -335,6 +360,12 @@
}
}
+ /** Overrideable by a test */
+ @VisibleForTesting
+ protected PlatformCompatCache getPlatformCompatCache() {
+ return mPlatformCompatCache;
+ }
+
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
}
@@ -383,7 +414,8 @@
mNumSlots = ((ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1) >> 1)
/ ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
mPlatformCompatCache = new PlatformCompatCache(new long[] {
- PROCESS_CAPABILITY_CHANGE_ID, CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID
+ PROCESS_CAPABILITY_CHANGE_ID, CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME
});
}
@@ -755,7 +787,7 @@
if (app != null) {
mPendingProcessSet.remove(app);
if (procDied) {
- mPlatformCompatCache.invalidate(app.info);
+ getPlatformCompatCache().invalidate(app.info);
}
}
}
@@ -1924,7 +1956,7 @@
boolean enabled = false;
try {
- enabled = mPlatformCompatCache.isChangeEnabled(
+ enabled = getPlatformCompatCache().isChangeEnabled(
CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
} catch (RemoteException e) {
}
@@ -2147,7 +2179,7 @@
state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
boolean enabled = false;
try {
- enabled = mPlatformCompatCache.isChangeEnabled(
+ enabled = getPlatformCompatCache().isChangeEnabled(
PROCESS_CAPABILITY_CHANGE_ID, client.info);
} catch (RemoteException e) {
}
@@ -2787,15 +2819,27 @@
} else {
state.setProcStateChanged(true);
}
- } else if (state.hasReportedInteraction() && (nowElapsed - state.getInteractionEventTime())
- > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
+ } else if (state.hasReportedInteraction()) {
+ final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final long interactionThreshold = fgsInteractionChangeEnabled
+ ? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
+ : mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
// For apps that sit around for a long time in the interactive state, we need
// to report this at least once a day so they don't go idle.
- maybeUpdateUsageStatsLSP(app, nowElapsed);
- } else if (!state.hasReportedInteraction() && (nowElapsed - state.getFgInteractionTime())
- > mConstants.SERVICE_USAGE_INTERACTION_TIME) {
+ if ((nowElapsed - state.getInteractionEventTime()) > interactionThreshold) {
+ maybeUpdateUsageStatsLSP(app, nowElapsed);
+ }
+ } else {
+ final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final long interactionThreshold = fgsInteractionChangeEnabled
+ ? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
+ : mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
// For foreground services that sit around for a long time but are not interacted with.
- maybeUpdateUsageStatsLSP(app, nowElapsed);
+ if ((nowElapsed - state.getFgInteractionTime()) > interactionThreshold) {
+ maybeUpdateUsageStatsLSP(app, nowElapsed);
+ }
}
if (state.getCurCapability() != state.getSetCapability()) {
@@ -2877,6 +2921,8 @@
if (mService.mUsageStatsService == null) {
return;
}
+ final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
boolean isInteraction;
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
@@ -2890,18 +2936,22 @@
state.setFgInteractionTime(nowElapsed);
isInteraction = false;
} else {
- isInteraction = nowElapsed > state.getFgInteractionTime()
- + mConstants.SERVICE_USAGE_INTERACTION_TIME;
+ final long interactionTime = fgsInteractionChangeEnabled
+ ? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
+ : mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
+ isInteraction = nowElapsed > state.getFgInteractionTime() + interactionTime;
}
} else {
isInteraction =
state.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
state.setFgInteractionTime(0);
}
+ final long interactionThreshold = fgsInteractionChangeEnabled
+ ? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
+ : mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
if (isInteraction
&& (!state.hasReportedInteraction()
- || (nowElapsed - state.getInteractionEventTime())
- > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
+ || (nowElapsed - state.getInteractionEventTime()) > interactionThreshold)) {
state.setInteractionEventTime(nowElapsed);
String[] packages = app.getPackageList();
if (packages != null) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6765ad0..cc98abf 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -135,6 +135,7 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.wm.ActivityServiceConnectionsHolder;
import com.android.server.wm.WindowManagerService;
+import com.android.server.wm.WindowProcessController;
import dalvik.system.VMRuntime;
@@ -4626,6 +4627,7 @@
@GuardedBy(anyOf = {"mService", "mProcLock"})
void updateApplicationInfoLOSP(List<String> packagesToUpdate, int userId,
boolean updateFrameworkRes) {
+ final ArrayList<WindowProcessController> targetProcesses = new ArrayList<>();
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
final ProcessRecord app = mLruProcesses.get(i);
if (app.getThread() == null) {
@@ -4646,6 +4648,7 @@
if (ai.packageName.equals(app.info.packageName)) {
app.info = ai;
}
+ targetProcesses.add(app.getWindowProcessController());
}
} catch (RemoteException e) {
Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
@@ -4654,6 +4657,9 @@
}
});
}
+
+ mService.mActivityTaskManager.updateAssetConfiguration(
+ updateFrameworkRes ? null : targetProcesses);
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index f566080..a139589 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -257,8 +257,8 @@
return false;
}
- // don't allow attention check in screen off state
- if (!mPowerManager.isInteractive()) {
+ // don't allow attention check in screen off state or power save mode
+ if (!mPowerManager.isInteractive() || mPowerManager.isPowerSaveMode()) {
return false;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 39b7a74..ce06d06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -811,7 +811,7 @@
for (String instance : instances) {
final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
final IFingerprint fp = IFingerprint.Stub.asInterface(
- ServiceManager.waitForDeclaredService(fqName));
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
if (fp == null) {
Slog.e(TAG, "Unable to get declared service: " + fqName);
continue;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c23c113..083df19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -37,6 +37,7 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -186,8 +187,9 @@
Slog.d(getTag(), "Daemon was null, reconnecting");
mDaemon = IFingerprint.Stub.asInterface(
- ServiceManager.waitForDeclaredService(IFingerprint.DESCRIPTOR
- + "/" + mHalInstanceName));
+ Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(
+ IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)));
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
diff --git a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
index 78c4144..5a0069a 100644
--- a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
+++ b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
@@ -104,7 +104,8 @@
}
/**
- * {@inheritDoc}
+ * If permissions are not released explicitly via {@link #release()}, release automatically
+ * whenever there are no more references to this object.
*/
@Override
protected void finalize() throws Throwable {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 864aa33..172a68a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -171,8 +171,7 @@
// client caching behavior is only enabled after seeing the first invalidate
LocationManager.invalidateLocalLocationEnabledCaches();
// disable caching for our own process
- Objects.requireNonNull(getContext().getSystemService(LocationManager.class))
- .disableLocalLocationEnabledCaches();
+ LocationManager.disableLocalLocationEnabledCaches();
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 0be325f..4b772f2 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -2271,22 +2271,28 @@
}
if (mOnLocationTagsChangeListener != null) {
- if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)) {
+ if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)
+ || !Objects.equals(oldState.identity, newState.identity)) {
if (oldState.identity != null) {
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
OnProviderLocationTagsChangeListener::onLocationTagsChanged,
mOnLocationTagsChangeListener, new LocationTagInfo(
oldState.identity.getUid(), oldState.identity.getPackageName(),
Collections.emptySet())
- ));
+ ));
}
if (newState.identity != null) {
+ ArraySet<String> attributionTags = new ArraySet<>(
+ newState.extraAttributionTags.size() + 1);
+ attributionTags.addAll(newState.extraAttributionTags);
+ attributionTags.add(newState.identity.getAttributionTag());
+
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
OnProviderLocationTagsChangeListener::onLocationTagsChanged,
mOnLocationTagsChangeListener, new LocationTagInfo(
newState.identity.getUid(), newState.identity.getPackageName(),
- newState.extraAttributionTags)
- ));
+ attributionTags)
+ ));
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 08a7d9e..0840e75 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -123,7 +123,7 @@
import android.Manifest;
import android.Manifest.permission;
-import android.annotation.CallbackExecutor;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -492,7 +492,7 @@
private DeviceIdleManager mDeviceIdleManager;
private IUriGrantsManager mUgm;
private UriGrantsManagerInternal mUgmInternal;
- private RoleObserver mRoleObserver;
+ private volatile RoleObserver mRoleObserver;
private UserManager mUm;
private IPlatformCompat mPlatformCompat;
private ShortcutHelper mShortcutHelper;
@@ -629,6 +629,8 @@
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
+ final Object mBufferLock = new Object();
+ @GuardedBy("mBufferLock")
final LinkedList<Pair<StatusBarNotification, Integer>> mBuffer;
public Archive(int size) {
@@ -651,14 +653,16 @@
if (!mEnabled.get(sbn.getNormalizedUserId(), false)) {
return;
}
- if (mBuffer.size() == mBufferSize) {
- mBuffer.removeFirst();
- }
+ synchronized (mBufferLock) {
+ if (mBuffer.size() == mBufferSize) {
+ mBuffer.removeFirst();
+ }
- // We don't want to store the heavy bits of the notification in the archive,
- // but other clients in the system process might be using the object, so we
- // store a (lightened) copy.
- mBuffer.addLast(new Pair<>(sbn.cloneLight(), reason));
+ // We don't want to store the heavy bits of the notification in the archive,
+ // but other clients in the system process might be using the object, so we
+ // store a (lightened) copy.
+ mBuffer.addLast(new Pair<>(sbn.cloneLight(), reason));
+ }
}
public Iterator<Pair<StatusBarNotification, Integer>> descendingIterator() {
@@ -666,27 +670,31 @@
}
public StatusBarNotification[] getArray(int count, boolean includeSnoozed) {
- if (count == 0) count = mBufferSize;
- List<StatusBarNotification> a = new ArrayList();
- Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator();
- int i=0;
- while (iter.hasNext() && i < count) {
- Pair<StatusBarNotification, Integer> pair = iter.next();
- if (pair.second != REASON_SNOOZED || includeSnoozed) {
- i++;
- a.add(pair.first);
+ synchronized (mBufferLock) {
+ if (count == 0) count = mBufferSize;
+ List<StatusBarNotification> a = new ArrayList();
+ Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator();
+ int i = 0;
+ while (iter.hasNext() && i < count) {
+ Pair<StatusBarNotification, Integer> pair = iter.next();
+ if (pair.second != REASON_SNOOZED || includeSnoozed) {
+ i++;
+ a.add(pair.first);
+ }
}
+ return a.toArray(new StatusBarNotification[a.size()]);
}
- return a.toArray(new StatusBarNotification[a.size()]);
}
public void updateHistoryEnabled(@UserIdInt int userId, boolean enabled) {
mEnabled.put(userId, enabled);
if (!enabled) {
- for (int i = mBuffer.size() - 1; i >= 0; i--) {
- if (userId == mBuffer.get(i).first.getNormalizedUserId()) {
- mBuffer.remove(i);
+ synchronized (mBufferLock) {
+ for (int i = mBuffer.size() - 1; i >= 0; i--) {
+ if (userId == mBuffer.get(i).first.getNormalizedUserId()) {
+ mBuffer.remove(i);
+ }
}
}
}
@@ -695,15 +703,18 @@
// Remove notifications with the specified user & channel ID.
public void removeChannelNotifications(String pkg, @UserIdInt int userId,
String channelId) {
- Iterator<Pair<StatusBarNotification, Integer>> bufferIter = mBuffer.iterator();
- while (bufferIter.hasNext()) {
- final Pair<StatusBarNotification, Integer> pair = bufferIter.next();
- if (pair.first != null
- && userId == pair.first.getNormalizedUserId()
- && pkg != null && pkg.equals(pair.first.getPackageName())
- && pair.first.getNotification() != null
- && Objects.equals(channelId, pair.first.getNotification().getChannelId())) {
- bufferIter.remove();
+ synchronized (mBufferLock) {
+ Iterator<Pair<StatusBarNotification, Integer>> bufferIter = descendingIterator();
+ while (bufferIter.hasNext()) {
+ final Pair<StatusBarNotification, Integer> pair = bufferIter.next();
+ if (pair.first != null
+ && userId == pair.first.getNormalizedUserId()
+ && pkg != null && pkg.equals(pair.first.getPackageName())
+ && pair.first.getNotification() != null
+ && Objects.equals(channelId,
+ pair.first.getNotification().getChannelId())) {
+ bufferIter.remove();
+ }
}
}
}
@@ -2640,6 +2651,11 @@
@Override
public void onBootPhase(int phase) {
+ onBootPhase(phase, Looper.getMainLooper());
+ }
+
+ @VisibleForTesting
+ void onBootPhase(int phase, Looper mainLooper) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
// no beeping until we're basically done booting
mSystemReady = true;
@@ -2649,9 +2665,11 @@
mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mZenModeHelper.onSystemReady();
- mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
- mPackageManager, getContext().getMainExecutor());
- mRoleObserver.init();
+ RoleObserver roleObserver = new RoleObserver(getContext(),
+ getContext().getSystemService(RoleManager.class),
+ mPackageManager, mainLooper);
+ roleObserver.init();
+ mRoleObserver = roleObserver;
LauncherApps launcherApps =
(LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener, getLocalService(
@@ -10677,26 +10695,40 @@
// Role name : user id : list of approved packages
private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;
+ /**
+ * Writes should be pretty rare (only when default browser changes) and reads are done
+ * during activity start code-path, so we're optimizing for reads. This means this set is
+ * immutable once written and we'll recreate the set every time there is a role change and
+ * then assign that new set to the volatile below, so reads can be done without needing to
+ * hold a lock. Every write is done on the main-thread, so write atomicity is guaranteed.
+ *
+ * Didn't use unmodifiable set to enforce immutability to avoid iterating via iterators.
+ */
+ private volatile ArraySet<Integer> mTrampolineExemptUids = new ArraySet<>();
+
private final RoleManager mRm;
private final IPackageManager mPm;
private final Executor mExecutor;
+ private final Looper mMainLooper;
- RoleObserver(@NonNull RoleManager roleManager,
- @NonNull IPackageManager pkgMgr,
- @NonNull @CallbackExecutor Executor executor) {
+ RoleObserver(Context context, @NonNull RoleManager roleManager,
+ @NonNull IPackageManager pkgMgr, @NonNull Looper mainLooper) {
mRm = roleManager;
mPm = pkgMgr;
- mExecutor = executor;
+ mExecutor = context.getMainExecutor();
+ mMainLooper = mainLooper;
}
+ /** Should be called from the main-thread. */
+ @MainThread
public void init() {
- List<UserInfo> users = mUm.getUsers();
+ List<UserHandle> users = mUm.getUserHandles(/* excludeDying */ true);
mNonBlockableDefaultApps = new ArrayMap<>();
for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
final ArrayMap<Integer, ArraySet<String>> userToApprovedList = new ArrayMap<>();
mNonBlockableDefaultApps.put(NON_BLOCKABLE_DEFAULT_ROLES[i], userToApprovedList);
for (int j = 0; j < users.size(); j++) {
- Integer userId = users.get(j).getUserHandle().getIdentifier();
+ Integer userId = users.get(j).getIdentifier();
ArraySet<String> approvedForUserId = new ArraySet<>(mRm.getRoleHoldersAsUser(
NON_BLOCKABLE_DEFAULT_ROLES[i], UserHandle.of(userId)));
ArraySet<Pair<String, Integer>> approvedAppUids = new ArraySet<>();
@@ -10707,7 +10739,7 @@
mPreferencesHelper.updateDefaultApps(userId, null, approvedAppUids);
}
}
-
+ updateTrampolineExemptUidsForUsers(users.toArray(new UserHandle[0]));
mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
}
@@ -10716,6 +10748,11 @@
return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
}
+ @VisibleForTesting
+ public boolean isUidExemptFromTrampolineRestrictions(int uid) {
+ return mTrampolineExemptUids.contains(uid);
+ }
+
/**
* Convert the assistant-role holder into settings. The rest of the system uses the
* settings.
@@ -10725,6 +10762,12 @@
*/
@Override
public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ onRoleHoldersChangedForNonBlockableDefaultApps(roleName, user);
+ onRoleHoldersChangedForTrampolines(roleName, user);
+ }
+
+ private void onRoleHoldersChangedForNonBlockableDefaultApps(@NonNull String roleName,
+ @NonNull UserHandle user) {
// we only care about a couple of the roles they'll tell us about
boolean relevantChange = false;
for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
@@ -10772,6 +10815,41 @@
// write of the notification policy xml for this change
}
+ private void onRoleHoldersChangedForTrampolines(@NonNull String roleName,
+ @NonNull UserHandle user) {
+ if (!RoleManager.ROLE_BROWSER.equals(roleName)) {
+ return;
+ }
+ updateTrampolineExemptUidsForUsers(user);
+ }
+
+ private void updateTrampolineExemptUidsForUsers(UserHandle... users) {
+ Preconditions.checkState(mMainLooper.isCurrentThread());
+ ArraySet<Integer> oldUids = mTrampolineExemptUids;
+ ArraySet<Integer> newUids = new ArraySet<>();
+ // Add the uids from previous set for the users that we won't update.
+ for (int i = 0, n = oldUids.size(); i < n; i++) {
+ int uid = oldUids.valueAt(i);
+ UserHandle user = UserHandle.of(UserHandle.getUserId(uid));
+ if (!ArrayUtils.contains(users, user)) {
+ newUids.add(uid);
+ }
+ }
+ // Now lookup the new uids for the users that we want to update.
+ for (int i = 0, n = users.length; i < n; i++) {
+ UserHandle user = users[i];
+ for (String pkg : mRm.getRoleHoldersAsUser(RoleManager.ROLE_BROWSER, user)) {
+ int uid = getUidForPackage(pkg, user.getIdentifier());
+ if (uid != -1) {
+ newUids.add(uid);
+ } else {
+ Slog.e(TAG, "Bad uid (-1) for browser package " + pkg);
+ }
+ }
+ }
+ mTrampolineExemptUids = newUids;
+ }
+
private int getUidForPackage(String pkg, int userId) {
try {
return mPm.getPackageUid(pkg, MATCH_ALL, userId);
@@ -10936,7 +11014,7 @@
}
String logcatMessage =
"Indirect notification activity start (trampoline) from " + packageName;
- if (CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid)) {
+ if (blockTrampoline(uid)) {
// Post toast() call to mHandler to offload PM lookup from the activity start path
mHandler.post(() -> toast(packageName, uid));
Slog.e(TAG, logcatMessage + " blocked");
@@ -10947,6 +11025,13 @@
}
}
+ private boolean blockTrampoline(int uid) {
+ if (mRoleObserver != null && mRoleObserver.isUidExemptFromTrampolineRestrictions(uid)) {
+ return false;
+ }
+ return CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
+ }
+
@Override
public boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid) {
// If the start is allowed via notification, we allow the app to close system dialogs
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 656f347..b6f5e99 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -20,6 +20,7 @@
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -88,6 +89,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.utils.RequestThrottle;
import libcore.io.IoUtils;
@@ -220,6 +222,14 @@
}
}
+ @NonNull
+ private final RequestThrottle mSettingsWriteRequest = new RequestThrottle(IoThread.getHandler(),
+ () -> {
+ synchronized (mSessions) {
+ return writeSessionsLocked();
+ }
+ });
+
public PackageInstallerService(Context context, PackageManagerService pm,
Supplier<PackageParser2> apexParserSupplier) {
mContext = context;
@@ -275,7 +285,7 @@
// Invalid sessions might have been marked while parsing. Re-write the database with
// the updated information.
- writeSessionsLocked();
+ mSettingsWriteRequest.runNow();
}
}
@@ -464,7 +474,7 @@
}
@GuardedBy("mSessions")
- private void writeSessionsLocked() {
+ private boolean writeSessionsLocked() {
if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
FileOutputStream fos = null;
@@ -483,28 +493,20 @@
out.endDocument();
mSessionsFile.finishWrite(fos);
+ return true;
} catch (IOException e) {
if (fos != null) {
mSessionsFile.failWrite(fos);
}
}
+
+ return false;
}
private File buildAppIconFile(int sessionId) {
return new File(mSessionsDir, "app_icon." + sessionId + ".png");
}
- private void writeSessionsAsync() {
- IoThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- synchronized (mSessions) {
- writeSessionsLocked();
- }
- }
- });
- }
-
@Override
public int createSession(SessionParams params, String installerPackageName,
String callingAttributionTag, int userId) {
@@ -764,7 +766,7 @@
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
- writeSessionsAsync();
+ mSettingsWriteRequest.schedule();
return sessionId;
}
@@ -1374,7 +1376,7 @@
class InternalCallback {
public void onSessionBadgingChanged(PackageInstallerSession session) {
mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
- writeSessionsAsync();
+ mSettingsWriteRequest.schedule();
}
public void onSessionActiveChanged(PackageInstallerSession session, boolean active) {
@@ -1389,7 +1391,7 @@
public void onStagedSessionChanged(PackageInstallerSession session) {
session.markUpdated();
- writeSessionsAsync();
+ mSettingsWriteRequest.schedule();
if (mOkToSendBroadcasts && !session.isDestroyed()) {
// we don't scrub the data here as this is sent only to the installer several
// privileged system packages
@@ -1419,7 +1421,7 @@
appIconFile.delete();
}
- writeSessionsLocked();
+ mSettingsWriteRequest.runNow();
}
}
});
@@ -1428,16 +1430,14 @@
public void onSessionPrepared(PackageInstallerSession session) {
// We prepared the destination to write into; we want to persist
// this, but it's not critical enough to block for.
- writeSessionsAsync();
+ mSettingsWriteRequest.schedule();
}
public void onSessionSealedBlocking(PackageInstallerSession session) {
// It's very important that we block until we've recorded the
// session as being sealed, since we never want to allow mutation
// after sealing.
- synchronized (mSessions) {
- writeSessionsLocked();
- }
+ mSettingsWriteRequest.runNow();
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dad37f4..51288de 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -134,6 +134,7 @@
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
import static com.android.server.pm.PackageManagerServiceUtils.makeDirRecursive;
import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
+import static com.android.server.pm.parsing.PackageInfoUtils.checkUseInstalledOrHidden;
import android.Manifest;
import android.annotation.AppIdInt;
@@ -2510,8 +2511,8 @@
if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
+ PackageSetting ps = a == null ? null : mSettings.getPackageLPr(a.getPackageName());
if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
- PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
if (ps == null) return null;
if (shouldFilterApplicationLocked(
ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
@@ -2521,8 +2522,8 @@
a, flags, ps.readUserState(userId), userId, ps);
}
if (resolveComponentName().equals(component)) {
- return PackageParser.generateActivityInfo(
- mResolveActivity, flags, new PackageUserState(), userId);
+ return generateDelegateActivityInfo(pkg, ps, new PackageUserState(),
+ mResolveActivity, flags, userId);
}
return null;
}
@@ -3168,8 +3169,8 @@
return result;
}
final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
- ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo(
- instantAppInstallerActivity(), 0, ps.readUserState(userId), userId);
+ ephemeralInstaller.activityInfo = generateDelegateActivityInfo(ps.getPkg(), ps,
+ ps.readUserState(userId), instantAppInstallerActivity(), 0 /*flags*/, userId);
ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
| IntentFilter.MATCH_ADJUSTMENT_NORMAL;
// add a non-generic filter
@@ -3253,7 +3254,7 @@
ai.flags = ps.pkgFlags;
ai.privateFlags = ps.pkgPrivateFlags;
pi.applicationInfo =
- PackageParser.generateApplicationInfo(ai, flags, state, userId);
+ PackageInfoUtils.generateApplicationInfo(p, flags, state, userId, ps);
if (DEBUG_PACKAGE_INFO) Log.v(TAG, "ps.pkg is n/a for ["
+ ps.name + "]. Provides a minimum info.");
@@ -3369,6 +3370,19 @@
return getInstalledPackagesBody(flags, userId, callingUid);
}
+ private static ActivityInfo generateDelegateActivityInfo(@Nullable AndroidPackage pkg,
+ @Nullable PackageSetting ps, @NonNull PackageUserState state,
+ @Nullable ActivityInfo activity, int flags, int userId) {
+ if (activity == null || pkg == null
+ || !checkUseInstalledOrHidden(pkg, ps, state, flags)) {
+ return null;
+ }
+ final ActivityInfo info = new ActivityInfo(activity);
+ info.applicationInfo =
+ PackageInfoUtils.generateApplicationInfo(pkg, flags, state, userId, ps);
+ return info;
+ }
+
public ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId,
int callingUid) {
// writer
@@ -23604,7 +23618,7 @@
boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1;
- PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled);
+ ParsingPackageUtils.setCompatibilityModeEnabled(compatibilityModeEnabled);
if (DEBUG_SETTINGS) {
Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 3dfb835e..1bd9e5e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -319,7 +319,7 @@
}
final Icon icon = si.getIcon();
if (icon != null && icon.getType() != Icon.TYPE_BITMAP
- && icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) {
continue;
}
if (icon == null && !si.hasIconFile()) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 61f51e3..b89dbdc 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -417,7 +417,7 @@
* Returns true if the package is installed and not hidden, or if the caller
* explicitly wanted all uninstalled and hidden packages as well.
*/
- private static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
+ public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
PackageSetting pkgSetting, PackageUserState state,
@PackageManager.PackageInfoFlags int flags) {
// Returns false if the package is hidden system app until installed.
diff --git a/services/core/java/com/android/server/pm/utils/RequestThrottle.java b/services/core/java/com/android/server/pm/utils/RequestThrottle.java
new file mode 100644
index 0000000..f1dd402
--- /dev/null
+++ b/services/core/java/com/android/server/pm/utils/RequestThrottle.java
@@ -0,0 +1,154 @@
+/*
+ * 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.pm.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.server.IoThread;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+/**
+ * Loose throttle latest behavior for success/fail requests, with options to schedule or force a
+ * request through. Throttling is implicit and not configurable. This means requests are dispatched
+ * to the {@link Handler} immediately when received, and only batched while waiting on the next
+ * message execution or running request.
+ *
+ * This also means there is no explicit debouncing. Implicit debouncing is available through the
+ * same runtime delays in the {@link Handler} instance and the request execution, where multiple
+ * requests prior to the execution point are collapsed.
+ *
+ * Callers provide a {@link Handler} with which to schedule tasks on. This may be a highly
+ * contentious thread like {@link IoThread#getHandler()}, but note that there are no guarantees
+ * that the request will be handled before the system server dies. Ideally callers should handle
+ * re-initialization from stale state with no consequences to the user.
+ *
+ * This class will retry requests if they don't succeed, as provided by a true/false response from
+ * the block provided to run the request. This uses an exponential backoff mechanism, assuming that
+ * state write should be attempted immediately, but not retried so heavily as to potentially block
+ * other system server callers. Exceptions are not considered and will not result in a retry if
+ * thrown from inside the block. Caller should wrap with try-catch and rollback and transaction
+ * state before returning false to signal a retry.
+ *
+ * The caller is strictly responsible for data synchronization, as this class will not synchronize
+ * the request block, potentially running it multiple times or on multiple threads simultaneously
+ * if requests come in asynchronously.
+ */
+public class RequestThrottle {
+
+ private static final int DEFAULT_RETRY_MAX_ATTEMPTS = 5;
+ private static final int DEFAULT_DELAY_MS = 1000;
+ private static final int DEFAULT_BACKOFF_BASE = 2;
+
+ private final AtomicInteger mLastRequest = new AtomicInteger(0);
+ private final AtomicInteger mLastCommitted = new AtomicInteger(-1);
+
+ private final int mMaxAttempts;
+ private final int mFirstDelay;
+ private final int mBackoffBase;
+
+ private final AtomicInteger mCurrentRetry = new AtomicInteger(0);
+
+ @NonNull
+ private final Handler mHandler;
+
+ @NonNull
+ private final Supplier<Boolean> mBlock;
+
+ @NonNull
+ private final Runnable mRunnable;
+
+ /**
+ * @see #RequestThrottle(Handler, int, int, int, Supplier)
+ */
+ public RequestThrottle(@NonNull Handler handler, @NonNull Supplier<Boolean> block) {
+ this(handler, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_DELAY_MS, DEFAULT_BACKOFF_BASE,
+ block);
+ }
+
+ /**
+ * Backoff timing is calculated as firstDelay * (backoffBase ^ retryAttempt).
+ *
+ * @param handler Representing the thread to run the provided block.
+ * @param block The action to run when scheduled, returning whether or not the request was
+ * successful. Note that any thrown exceptions will be ignored and not
+ * retried, since it's not easy to tell how destructive or retry-able an
+ * exception is.
+ * @param maxAttempts Number of times to re-attempt any single request.
+ * @param firstDelay The first delay used after the initial attempt.
+ * @param backoffBase The base of the backoff calculation, where retry attempt count is the
+ * exponent.
+ */
+ public RequestThrottle(@NonNull Handler handler, int maxAttempts, int firstDelay,
+ int backoffBase, @NonNull Supplier<Boolean> block) {
+ mHandler = handler;
+ mBlock = block;
+ mMaxAttempts = maxAttempts;
+ mFirstDelay = firstDelay;
+ mBackoffBase = backoffBase;
+ mRunnable = this::runInternal;
+ }
+
+ /**
+ * Schedule the intended action on the provided {@link Handler}.
+ */
+ public void schedule() {
+ // To avoid locking the Handler twice by pre-checking hasCallbacks, instead just queue
+ // the Runnable again. It will no-op if the request has already been written to disk.
+ mLastRequest.incrementAndGet();
+ mHandler.post(mRunnable);
+ }
+
+ /**
+ * Run the intended action immediately on the calling thread. Note that synchronization and
+ * deadlock between threads is not handled. This will immediately call the request block, and
+ * also potentially schedule a retry. The caller must not block itself.
+ *
+ * @return true if the write succeeded or the last request was already written
+ */
+ public boolean runNow() {
+ mLastRequest.incrementAndGet();
+ return runInternal();
+ }
+
+ private boolean runInternal() {
+ int lastRequest = mLastRequest.get();
+ int lastCommitted = mLastCommitted.get();
+ if (lastRequest == lastCommitted) {
+ return true;
+ }
+
+ if (mBlock.get()) {
+ mCurrentRetry.set(0);
+ mLastCommitted.set(lastRequest);
+ return true;
+ } else {
+ int currentRetry = mCurrentRetry.getAndIncrement();
+ if (currentRetry < mMaxAttempts) {
+ long nextDelay =
+ (long) (mFirstDelay * Math.pow(mBackoffBase, currentRetry));
+ mHandler.postDelayed(mRunnable, nextDelay);
+ } else {
+ mCurrentRetry.set(0);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index 0e12584..3c9b106 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.hardware.ICameraService;
import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -75,7 +76,7 @@
DeviceStateManager deviceStateManager = context.getSystemService(DeviceStateManager.class);
deviceStateManager.registerCallback(new HandlerExecutor(handler),
- new DeviceStateListener(context));
+ new FoldStateListener(context, folded -> setDeviceFolded(folded)));
}
void finishedGoingToSleep() {
@@ -202,30 +203,4 @@
return new DisplayFoldController(context, windowManagerService, displayService,
cameraServiceProxy, displayId, foldedArea, DisplayThread.getHandler());
}
-
- /**
- * Listens to changes in device state and reports the state as folded if the device state
- * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState}
- * resource.
- */
- private class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
- private final int[] mFoldedDeviceStates;
-
- DeviceStateListener(Context context) {
- mFoldedDeviceStates = context.getResources().getIntArray(
- com.android.internal.R.array.config_foldedDeviceStates);
- }
-
- @Override
- public void onStateChanged(int deviceState) {
- boolean folded = false;
- for (int i = 0; i < mFoldedDeviceStates.length; i++) {
- if (deviceState == mFoldedDeviceStates[i]) {
- folded = true;
- break;
- }
- }
- setDeviceFolded(folded);
- }
- }
}
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index f0c96e1..32ad702 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -31,6 +31,7 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
import android.util.IntArray;
import android.util.Slog;
@@ -44,6 +45,9 @@
import com.android.server.pm.UserManagerService;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Starts the telecom component by binding to its ITelecomService implementation. Telecom is setup
* to run in the system-server process so once it is loaded into memory it will stay running.
@@ -208,13 +212,23 @@
return null;
}
}
+ SubscriptionManager subscriptionManager =
+ mContext.getSystemService(SubscriptionManager.class);
+ if (subscriptionManager == null) {
+ return null;
+ }
TelecomManager telecomManager =
(TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- PhoneAccountHandle phoneAccount = telecomManager.getSimCallManager(userId);
- if (phoneAccount != null) {
- return new String[]{phoneAccount.getComponentName().getPackageName()};
+ List<String> packages = new ArrayList<>();
+ int[] subIds = subscriptionManager.getActiveSubscriptionIdList();
+ for (int subId : subIds) {
+ PhoneAccountHandle phoneAccount =
+ telecomManager.getSimCallManagerForSubscription(subId);
+ if (phoneAccount != null) {
+ packages.add(phoneAccount.getComponentName().getPackageName());
+ }
}
- return null;
+ return packages.toArray(new String[] {});
});
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6a1b106..8f3702a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -91,6 +91,7 @@
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
import static android.content.res.Configuration.EMPTY;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -6618,9 +6619,15 @@
@Override
void onCancelFixedRotationTransform(int originalDisplayRotation) {
- if (this != mDisplayContent.getLastOrientationSource()
- || getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED) {
- // Only need to handle the activity that should be rotated with display.
+ if (this != mDisplayContent.getLastOrientationSource()) {
+ // This activity doesn't affect display rotation.
+ return;
+ }
+ final int requestedOrientation = getRequestedConfigurationOrientation();
+ if (requestedOrientation != ORIENTATION_UNDEFINED
+ && requestedOrientation != mDisplayContent.getConfiguration().orientation) {
+ // Only need to handle the activity that can be rotated with display or the activity
+ // has requested the same orientation.
return;
}
@@ -6858,6 +6865,11 @@
@Override
void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+ final Configuration requestedOverrideConfig = getRequestedOverrideConfiguration();
+ if (requestedOverrideConfig.assetsSeq != ASSETS_SEQ_UNDEFINED
+ && newParentConfiguration.assetsSeq > requestedOverrideConfig.assetsSeq) {
+ requestedOverrideConfig.assetsSeq = ASSETS_SEQ_UNDEFINED;
+ }
super.resolveOverrideConfiguration(newParentConfiguration);
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
if (isFixedRotationTransforming()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e249e67..c6a66c5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -472,6 +472,10 @@
/** Current sequencing integer of the configuration, for skipping old configurations. */
private int mConfigurationSeq;
+
+ /** Current sequencing integer of the asset changes, for skipping old resources overlays. */
+ private int mGlobalAssetsSeq;
+
// To cache the list of supported system locales
private String[] mSupportedSystemLocales = null;
@@ -4129,6 +4133,35 @@
return changes;
}
+ private int increaseAssetConfigurationSeq() {
+ mGlobalAssetsSeq = Math.max(++mGlobalAssetsSeq, 1);
+ return mGlobalAssetsSeq;
+ }
+
+ /**
+ * Update the asset configuration and increase the assets sequence number.
+ * @param processes the processes that needs to update the asset configuration, if none
+ * updates the global configuration for all processes.
+ */
+ public void updateAssetConfiguration(List<WindowProcessController> processes) {
+ synchronized (mGlobalLock) {
+ final int assetSeq = increaseAssetConfigurationSeq();
+
+ // Update the global configuration if the no target processes
+ if (processes == null) {
+ Configuration newConfig = new Configuration();
+ newConfig.assetsSeq = assetSeq;
+ updateConfiguration(newConfig);
+ return;
+ }
+
+ for (int i = processes.size() - 1; i >= 0; i--) {
+ final WindowProcessController wpc = processes.get(i);
+ wpc.updateAssetConfiguration(assetSeq);
+ }
+ }
+ }
+
void startLaunchPowerMode(@PowerModeReason int reason) {
if (mPowerManagerInternal == null) return;
mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true);
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 62e4a85..87670d2 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -32,8 +32,7 @@
import java.util.ArrayList;
-class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
- implements IBinder.DeathRecipient {
+class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub {
private static final String TAG = "DragAndDrop";
private static final boolean DEBUG = false;
@@ -49,7 +48,6 @@
private IBinder mActivityToken = null;
private IBinder mPermissionOwnerToken = null;
- private IBinder mAppToken = null;
DragAndDropPermissionsHandler(WindowManagerGlobalLock lock, ClipData clipData, int sourceUid,
String targetPackage, int mode, int sourceUserId, int targetUserId) {
@@ -94,18 +92,15 @@
}
@Override
- public void takeTransient(IBinder appToken) throws RemoteException {
+ public void takeTransient() throws RemoteException {
if (mActivityToken != null || mPermissionOwnerToken != null) {
return;
}
if (DEBUG) {
- Log.d(TAG, this + ": taking permissions bound to app process: "
- + toHexString(appToken.hashCode()));
+ Log.d(TAG, this + ": taking transient permissions");
}
mPermissionOwnerToken = LocalServices.getService(UriGrantsManagerInternal.class)
.newUriPermissionOwner("drop");
- mAppToken = appToken;
- mAppToken.linkToDeath(this, 0);
doTake(mPermissionOwnerToken);
}
@@ -132,10 +127,8 @@
} else {
permissionOwner = mPermissionOwnerToken;
mPermissionOwnerToken = null;
- mAppToken.unlinkToDeath(this, 0);
- mAppToken = null;
if (DEBUG) {
- Log.d(TAG, this + ": releasing process-bound permissions");
+ Log.d(TAG, this + ": releasing transient permissions");
}
}
@@ -157,15 +150,18 @@
}
}
+ /**
+ * If permissions are not tied to an activity, release whenever there are no more references
+ * to this object (if not already released).
+ */
@Override
- public void binderDied() {
+ protected void finalize() throws Throwable {
if (DEBUG) {
- Log.d(TAG, this + ": app process died: " + toHexString(mAppToken.hashCode()));
+ Log.d(TAG, this + ": running finalizer");
}
- try {
- release();
- } catch (RemoteException e) {
- // Cannot happen, local call.
+ if (mActivityToken != null || mPermissionOwnerToken == null) {
+ return;
}
+ release();
}
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index fb66c04..d67a0d3 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -163,14 +163,8 @@
originalWidth = displayInfo.logicalWidth;
originalHeight = displayInfo.logicalHeight;
}
- if (realOriginalRotation == Surface.ROTATION_90
- || realOriginalRotation == Surface.ROTATION_270) {
- mWidth = originalHeight;
- mHeight = originalWidth;
- } else {
- mWidth = originalWidth;
- mHeight = originalHeight;
- }
+ mWidth = originalWidth;
+ mHeight = originalHeight;
mOriginalRotation = originalRotation;
// If the delta is not zero, the rotation of display may not change, but we still want to
@@ -189,8 +183,14 @@
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
+ SurfaceControl.LayerCaptureArgs args =
+ new SurfaceControl.LayerCaptureArgs.Builder(displayContent.getSurfaceControl())
+ .setCaptureSecureLayers(true)
+ .setAllowProtected(true)
+ .setSourceCrop(new Rect(0, 0, mWidth, mHeight))
+ .build();
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
- mService.mDisplayManagerInternal.systemScreenshot(displayId);
+ SurfaceControl.captureLayers(args);
if (screenshotBuffer == null) {
Slog.w(TAG, "Unable to take screenshot of display " + displayId);
return;
@@ -236,9 +236,6 @@
GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
screenshotBuffer.getHardwareBuffer());
- // Scale the layer to the display size.
- float dsdx = (float) mWidth / hardwareBuffer.getWidth();
- float dsdy = (float) mHeight / hardwareBuffer.getHeight();
t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
@@ -247,7 +244,6 @@
t.setAlpha(mBackColorSurface, 1);
t.setBuffer(mScreenshotLayer, buffer);
t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
- t.setMatrix(mScreenshotLayer, dsdx, 0, 0, dsdy);
t.show(mScreenshotLayer);
t.show(mBackColorSurface);
@@ -330,9 +326,8 @@
// Compute the transformation matrix that must be applied
// to the snapshot to make it stay in the same original position
// with the current screen rotation.
- int delta = deltaRotation(rotation, Surface.ROTATION_0);
+ int delta = deltaRotation(rotation, mOriginalRotation);
RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
-
setRotationTransform(t, mSnapshotInitialMatrix);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9aabdac..2c592d0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2258,7 +2258,6 @@
mTmpPrevBounds.set(getBounds());
final boolean wasInMultiWindowMode = inMultiWindowMode();
final boolean wasInPictureInPicture = inPinnedWindowingMode();
- final int oldOrientation = getOrientation();
super.onConfigurationChanged(newParentConfig);
// Only need to update surface size here since the super method will handle updating
// surface position.
@@ -2301,11 +2300,6 @@
mForceNotOrganized = false;
}
- // Report orientation change such as changing from freeform to fullscreen.
- if (oldOrientation != getOrientation()) {
- onDescendantOrientationChanged(this);
- }
-
saveLaunchingStateIfNeeded();
final boolean taskOrgChanged = updateTaskOrganizerState(false /* forceUpdate */);
if (taskOrgChanged) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 4dc6007..d5965494 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -130,6 +130,12 @@
return runResetLetterboxStyle(pw);
case "set-sandbox-display-apis":
return runSandboxDisplayApis(pw);
+ case "set-multi-window-config":
+ return runSetMultiWindowConfig();
+ case "get-multi-window-config":
+ return runGetMultiWindowConfig(pw);
+ case "reset-multi-window-config":
+ return runResetMultiWindowConfig();
case "reset":
return runReset(pw);
case "disable-blur":
@@ -815,6 +821,80 @@
return 0;
}
+ private int runSetMultiWindowConfig() {
+ if (peekNextArg() == null) {
+ getErrPrintWriter().println("Error: No arguments provided.");
+ }
+ int result = 0;
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "--supportsNonResizable":
+ result += runSetSupportsNonResizableMultiWindow();
+ break;
+ case "--respectsActivityMinWidthHeight":
+ result += runSetRespectsActivityMinWidthHeightMultiWindow();
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized multi window option: " + arg);
+ return -1;
+ }
+ }
+ return result == 0 ? 0 : -1;
+ }
+
+ private int runSetSupportsNonResizableMultiWindow() {
+ final String arg = getNextArg();
+ if (!arg.equals("-1") && !arg.equals("0") && !arg.equals("1")) {
+ getErrPrintWriter().println("Error: a config value of [-1, 0, 1] must be provided as"
+ + " an argument for supportsNonResizableMultiWindow");
+ return -1;
+ }
+ final int configValue = Integer.parseInt(arg);
+ synchronized (mInternal.mAtmService.mGlobalLock) {
+ mInternal.mAtmService.mSupportsNonResizableMultiWindow = configValue;
+ }
+ return 0;
+ }
+
+ private int runSetRespectsActivityMinWidthHeightMultiWindow() {
+ final String arg = getNextArg();
+ if (!arg.equals("-1") && !arg.equals("0") && !arg.equals("1")) {
+ getErrPrintWriter().println("Error: a config value of [-1, 0, 1] must be provided as"
+ + " an argument for respectsActivityMinWidthHeightMultiWindow");
+ return -1;
+ }
+ final int configValue = Integer.parseInt(arg);
+ synchronized (mInternal.mAtmService.mGlobalLock) {
+ mInternal.mAtmService.mRespectsActivityMinWidthHeightMultiWindow = configValue;
+ }
+ return 0;
+ }
+
+ private int runGetMultiWindowConfig(PrintWriter pw) {
+ synchronized (mInternal.mAtmService.mGlobalLock) {
+ pw.println("Supports non-resizable in multi window: "
+ + mInternal.mAtmService.mSupportsNonResizableMultiWindow);
+ pw.println("Respects activity min width/height in multi window: "
+ + mInternal.mAtmService.mRespectsActivityMinWidthHeightMultiWindow);
+ }
+ return 0;
+ }
+
+ private int runResetMultiWindowConfig() {
+ final int supportsNonResizable = mInternal.mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_supportsNonResizableMultiWindow);
+ final int respectsActivityMinWidthHeight = mInternal.mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_respectsActivityMinWidthHeightMultiWindow);
+ synchronized (mInternal.mAtmService.mGlobalLock) {
+ mInternal.mAtmService.mSupportsNonResizableMultiWindow = supportsNonResizable;
+ mInternal.mAtmService.mRespectsActivityMinWidthHeightMultiWindow =
+ respectsActivityMinWidthHeight;
+ }
+ return 0;
+ }
+
private void resetLetterboxStyle() {
synchronized (mInternal.mGlobalLock) {
mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
@@ -879,6 +959,9 @@
// set-sandbox-display-apis
mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
+ // set-multi-window-config
+ runResetMultiWindowConfig();
+
pw.println("Reset all settings for displayId=" + displayId);
return 0;
}
@@ -916,6 +999,7 @@
pw.println(" Size Compat Mode.");
printLetterboxHelp(pw);
+ printMultiWindowConfigHelp(pw);
pw.println(" reset [-d DISPLAY_ID]");
pw.println(" Reset all override settings.");
@@ -969,4 +1053,31 @@
pw.println(" get-letterbox-style");
pw.println(" Prints letterbox style configuration.");
}
+
+ private void printMultiWindowConfigHelp(PrintWriter pw) {
+ pw.println(" set-multi-window-config");
+ pw.println(" Sets options to determine if activity should be shown in multi window:");
+ pw.println(" --supportsNonResizable [configValue]");
+ pw.println(" Whether the device supports non-resizable activity in multi window.");
+ pw.println(" -1: The device doesn't support non-resizable in multi window.");
+ pw.println(" 0: The device supports non-resizable in multi window only if");
+ pw.println(" this is a large screen device.");
+ pw.println(" 1: The device always supports non-resizable in multi window.");
+ pw.println(" --respectsActivityMinWidthHeight [configValue]");
+ pw.println(" Whether the device checks the activity min width/height to determine ");
+ pw.println(" if it can be shown in multi window.");
+ pw.println(" -1: The device ignores the activity min width/height when determining");
+ pw.println(" if it can be shown in multi window.");
+ pw.println(" 0: If this is a small screen, the device compares the activity min");
+ pw.println(" width/height with the min multi window modes dimensions");
+ pw.println(" the device supports to determine if the activity can be shown in");
+ pw.println(" multi window.");
+ pw.println(" 1: The device always compare the activity min width/height with the");
+ pw.println(" min multi window dimensions the device supports to determine if");
+ pw.println(" the activity can be shown in multi window.");
+ pw.println(" get-multi-window-config");
+ pw.println(" Prints values of the multi window config options.");
+ pw.println(" reset-multi-window-config");
+ pw.println(" Resets overrides to default values of the multi window config options.");
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index bac1ab1..26cfbdf 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -1318,6 +1319,11 @@
@Override
void resolveOverrideConfiguration(Configuration newParentConfig) {
+ final Configuration requestedOverrideConfig = getRequestedOverrideConfiguration();
+ if (requestedOverrideConfig.assetsSeq != ASSETS_SEQ_UNDEFINED
+ && newParentConfig.assetsSeq > requestedOverrideConfig.assetsSeq) {
+ requestedOverrideConfig.assetsSeq = ASSETS_SEQ_UNDEFINED;
+ }
super.resolveOverrideConfiguration(newParentConfig);
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
// Make sure that we don't accidentally override the activity type.
@@ -1396,6 +1402,28 @@
return mHasPendingConfigurationChange;
}
+ void updateAssetConfiguration(int assetSeq) {
+ // Update the process override configuration directly if the process configuration will
+ // not be override from its activities.
+ if (!mHasActivities || !mIsActivityConfigOverrideAllowed) {
+ Configuration overrideConfig = new Configuration(getRequestedOverrideConfiguration());
+ overrideConfig.assetsSeq = assetSeq;
+ onRequestedOverrideConfigurationChanged(overrideConfig);
+ return;
+ }
+
+ // Otherwise, we can just update the activity override configuration.
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ ActivityRecord r = mActivities.get(i);
+ Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
+ overrideConfig.assetsSeq = assetSeq;
+ r.onRequestedOverrideConfigurationChanged(overrideConfig);
+ if (r.mVisibleRequested) {
+ r.ensureActivityConfiguration(0, true);
+ }
+ }
+ }
+
/**
* This is called for sending {@link android.app.servertransaction.LaunchActivityItem}.
* The caller must call {@link #setLastReportedConfiguration} if the delivered configuration
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 074eeb9..9de5058 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7244,6 +7244,7 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkAllUsersAreAffiliatedWithDevice();
mInjector.binderWithCleanCallingIdentity(
() -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
}
@@ -15736,6 +15737,7 @@
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+ checkAllUsersAreAffiliatedWithDevice();
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
switch (mode) {
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index f3e7d67..36c0a67 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -269,7 +269,10 @@
template <class Func>
static auto makeCleanup(Func&& f) requires(!std::is_lvalue_reference_v<Func>) {
- auto deleter = [f = std::move(f)](auto) { f(); };
+ // ok to move a 'forwarding' reference here as lvalues are disabled anyway
+ auto deleter = [f = std::move(f)](auto) { // NOLINT
+ f();
+ };
// &f is a dangling pointer here, but we actually never use it as deleter moves it in.
return std::unique_ptr<Func, decltype(deleter)>(&f, std::move(deleter));
}
@@ -395,8 +398,18 @@
return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
}
+static uint64_t elapsedUsSinceMonoTs(uint64_t monoTsUs) {
+ timespec now;
+ if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
+ return 0;
+ }
+ uint64_t nowUs = now.tv_sec * 1000000LL + now.tv_nsec / 1000;
+ return nowUs - monoTsUs;
+}
+
void IncrementalService::onDump(int fd) {
dprintf(fd, "Incremental is %s\n", incfs::enabled() ? "ENABLED" : "DISABLED");
+ dprintf(fd, "IncFs features: 0x%x\n", int(mIncFs->features()));
dprintf(fd, "Incremental dir: %s\n", mIncrementalDir.c_str());
std::unique_lock l(mLock);
@@ -411,6 +424,8 @@
} else {
dprintf(fd, " mountId: %d\n", mnt.mountId);
dprintf(fd, " root: %s\n", mnt.root.c_str());
+ const auto metricsInstanceName = path::basename(ifs->root);
+ dprintf(fd, " metrics instance name: %s\n", path::c_str(metricsInstanceName).get());
dprintf(fd, " nextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
dprintf(fd, " flags: %d\n", int(mnt.flags));
if (mnt.startLoadingTs.time_since_epoch() == Clock::duration::zero()) {
@@ -440,6 +455,45 @@
dprintf(fd, " kind: %s\n", toString(bind.kind));
}
dprintf(fd, " }\n");
+
+ dprintf(fd, " incfsMetrics: {\n");
+ const auto incfsMetrics = mIncFs->getMetrics(metricsInstanceName);
+ if (incfsMetrics) {
+ dprintf(fd, " readsDelayedMin: %d\n", incfsMetrics.value().readsDelayedMin);
+ dprintf(fd, " readsDelayedMinUs: %lld\n",
+ (long long)incfsMetrics.value().readsDelayedMinUs);
+ dprintf(fd, " readsDelayedPending: %d\n",
+ incfsMetrics.value().readsDelayedPending);
+ dprintf(fd, " readsDelayedPendingUs: %lld\n",
+ (long long)incfsMetrics.value().readsDelayedPendingUs);
+ dprintf(fd, " readsFailedHashVerification: %d\n",
+ incfsMetrics.value().readsFailedHashVerification);
+ dprintf(fd, " readsFailedOther: %d\n", incfsMetrics.value().readsFailedOther);
+ dprintf(fd, " readsFailedTimedOut: %d\n",
+ incfsMetrics.value().readsFailedTimedOut);
+ } else {
+ dprintf(fd, " Metrics not available. Errno: %d\n", errno);
+ }
+ dprintf(fd, " }\n");
+
+ const auto lastReadError = mIncFs->getLastReadError(ifs->control);
+ const auto errorNo = errno;
+ dprintf(fd, " lastReadError: {\n");
+ if (lastReadError) {
+ if (lastReadError->timestampUs == 0) {
+ dprintf(fd, " No read errors.\n");
+ } else {
+ dprintf(fd, " fileId: %s\n",
+ IncFsWrapper::toString(lastReadError->id).c_str());
+ dprintf(fd, " time: %llu microseconds ago\n",
+ (unsigned long long)elapsedUsSinceMonoTs(lastReadError->timestampUs));
+ dprintf(fd, " blockIndex: %d\n", lastReadError->block);
+ dprintf(fd, " errno: %d\n", lastReadError->errorNo);
+ }
+ } else {
+ dprintf(fd, " Info not available. Errno: %d\n", errorNo);
+ }
+ dprintf(fd, " }\n");
}
dprintf(fd, " }\n");
}
@@ -578,7 +632,7 @@
if (!mkdirOrLog(path::join(backing, ".incomplete"), 0777)) {
return kInvalidStorageId;
}
- auto status = mVold->mountIncFs(backing, mountTarget, 0, &controlParcel);
+ auto status = mVold->mountIncFs(backing, mountTarget, 0, mountKey, &controlParcel);
if (!status.isOk()) {
LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
return kInvalidStorageId;
@@ -1586,9 +1640,10 @@
bool IncrementalService::mountExistingImage(std::string_view root) {
auto mountTarget = path::join(root, constants().mount);
const auto backing = path::join(root, constants().backing);
+ std::string mountKey(path::basename(path::dirname(mountTarget)));
IncrementalFileSystemControlParcel controlParcel;
- auto status = mVold->mountIncFs(backing, mountTarget, 0, &controlParcel);
+ auto status = mVold->mountIncFs(backing, mountTarget, 0, mountKey, &controlParcel);
if (!status.isOk()) {
LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
return false;
@@ -2562,7 +2617,9 @@
maxBindDelayMs)
.count();
const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
- const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+ // rand() is enough, not worth maintaining a full-blown <rand> object for delay jitter
+ const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - // NOLINT
+ bindDelayJitterRangeMs;
mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
return mPreviousBindDelay;
}
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 0755a22..68a28b2 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -43,8 +43,9 @@
~RealVoldService() = default;
binder::Status mountIncFs(
const std::string& backingPath, const std::string& targetDir, int32_t flags,
+ const std::string& sysfsName,
os::incremental::IncrementalFileSystemControlParcel* _aidl_return) const final {
- return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return);
+ return mInterface->mountIncFs(backingPath, targetDir, flags, sysfsName, _aidl_return);
}
binder::Status unmountIncFs(const std::string& dir) const final {
return mInterface->unmountIncFs(dir);
@@ -261,6 +262,12 @@
return cb(control, id);
});
}
+ std::optional<Metrics> getMetrics(std::string_view sysfsName) const final {
+ return incfs::getMetrics(sysfsName);
+ }
+ std::optional<LastReadError> getLastReadError(const Control& control) const final {
+ return incfs::getLastReadError(control);
+ }
};
static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 78e9589..c0ef7ba 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -51,6 +51,7 @@
virtual ~VoldServiceWrapper() = default;
virtual binder::Status mountIncFs(
const std::string& backingPath, const std::string& targetDir, int32_t flags,
+ const std::string& sysfsName,
os::incremental::IncrementalFileSystemControlParcel* result) const = 0;
virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
virtual binder::Status bindMount(const std::string& sourceDir,
@@ -79,6 +80,8 @@
using UniqueFd = incfs::UniqueFd;
using WaitResult = incfs::WaitResult;
using Features = incfs::Features;
+ using Metrics = incfs::Metrics;
+ using LastReadError = incfs::LastReadError;
using ExistingMountCallback = android::base::function_ref<
void(std::string_view root, std::string_view backingDir,
@@ -124,6 +127,8 @@
const = 0;
virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0;
virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0;
+ virtual std::optional<Metrics> getMetrics(std::string_view sysfsName) const = 0;
+ virtual std::optional<LastReadError> getLastReadError(const Control& control) const = 0;
};
class AppOpsManagerWrapper {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 68586a8..da7f0db 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -49,9 +49,9 @@
class MockVoldService : public VoldServiceWrapper {
public:
- MOCK_CONST_METHOD4(mountIncFs,
+ MOCK_CONST_METHOD5(mountIncFs,
binder::Status(const std::string& backingPath, const std::string& targetDir,
- int32_t flags,
+ int32_t flags, const std::string& sysfsName,
IncrementalFileSystemControlParcel* _aidl_return));
MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
MOCK_CONST_METHOD2(bindMount,
@@ -62,16 +62,16 @@
bool, bool));
void mountIncFsFails() {
- ON_CALL(*this, mountIncFs(_, _, _, _))
+ ON_CALL(*this, mountIncFs(_, _, _, _, _))
.WillByDefault(
Return(binder::Status::fromExceptionCode(1, String8("failed to mount"))));
}
void mountIncFsInvalidControlParcel() {
- ON_CALL(*this, mountIncFs(_, _, _, _))
+ ON_CALL(*this, mountIncFs(_, _, _, _, _))
.WillByDefault(Invoke(this, &MockVoldService::getInvalidControlParcel));
}
void mountIncFsSuccess() {
- ON_CALL(*this, mountIncFs(_, _, _, _))
+ ON_CALL(*this, mountIncFs(_, _, _, _, _))
.WillByDefault(Invoke(this, &MockVoldService::incFsSuccess));
}
void bindMountFails() {
@@ -93,12 +93,14 @@
}
binder::Status getInvalidControlParcel(const std::string& imagePath,
const std::string& targetDir, int32_t flags,
+ const std::string& sysfsName,
IncrementalFileSystemControlParcel* _aidl_return) {
_aidl_return = {};
return binder::Status::ok();
}
binder::Status incFsSuccess(const std::string& imagePath, const std::string& targetDir,
- int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) {
+ int32_t flags, const std::string& sysfsName,
+ IncrementalFileSystemControlParcel* _aidl_return) {
_aidl_return->pendingReads.reset(base::unique_fd(dup(STDIN_FILENO)));
_aidl_return->cmd.reset(base::unique_fd(dup(STDIN_FILENO)));
_aidl_return->log.reset(base::unique_fd(dup(STDIN_FILENO)));
@@ -414,6 +416,8 @@
const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb));
MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb));
+ MOCK_CONST_METHOD1(getMetrics, std::optional<Metrics>(std::string_view path));
+ MOCK_CONST_METHOD1(getLastReadError, std::optional<LastReadError>(const Control& control));
MockIncFs() {
ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return());
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 800f7ad..f92db86 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -52,7 +52,8 @@
libs: [
"unsupportedappusage",
"framework-wifi-util-lib",
- "framework-connectivity"
+ "framework-connectivity",
+ "modules-utils-build_system",
],
static_libs: [
// All the classes in netd_aidl_interface must be jarjar so they do not conflict with the
@@ -60,6 +61,7 @@
"netd_aidl_interface-V3-java",
"netlink-client",
"networkstack-client",
+ "modules-utils-build_system",
],
apex_available: [
"com.android.wifi",
@@ -80,7 +82,7 @@
],
visibility: [
"//frameworks/base/packages/Tethering",
- "//packages/modules/Connectivity/Tethering"
+ "//packages/modules/Connectivity/Tethering",
],
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/install/RequestThrottleTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/install/RequestThrottleTest.kt
new file mode 100644
index 0000000..2196ef7
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/install/RequestThrottleTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.pm.test.install
+
+import com.android.server.pm.utils.RequestThrottle
+import com.android.server.testutils.TestHandler
+import com.google.common.collect.Range
+import com.google.common.truth.LongSubject
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.CyclicBarrier
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+
+class RequestThrottleTest {
+
+ private val counter = AtomicInteger(0)
+
+ private val handler = TestHandler(null)
+
+ @Before
+ fun resetValues() {
+ handler.flush()
+ counter.set(0)
+ assertThat(counter.get()).isEqualTo(0)
+ }
+
+ @Test
+ fun simpleThrottle() {
+ val request = RequestThrottle(handler) {
+ counter.incrementAndGet()
+ true
+ }
+
+ fun sendRequests() {
+ request.schedule()
+ val thread = startThread { request.schedule() }
+ request.schedule()
+ thread.joinForTest()
+ }
+
+ sendRequests()
+ handler.flush()
+ assertThat(counter.get()).isEqualTo(1)
+
+ sendRequests()
+ handler.flush()
+ assertThat(counter.get()).isEqualTo(2)
+ }
+
+ @Test
+ fun exceptionInRequest() {
+ val shouldThrow = AtomicBoolean(true)
+ val request = RequestThrottle(handler) {
+ if (shouldThrow.get()) {
+ throw RuntimeException()
+ }
+ counter.incrementAndGet()
+ true
+ }
+
+ fun sendRequests() {
+ request.schedule()
+ val thread = startThread { request.schedule() }
+ request.schedule()
+ thread.joinForTest()
+ }
+
+ sendRequests()
+ try {
+ handler.flush()
+ } catch (ignored: Exception) {
+ }
+ assertThat(counter.get()).isEqualTo(0)
+
+ shouldThrow.set(false)
+
+ sendRequests()
+ handler.flush()
+ assertThat(counter.get()).isEqualTo(1)
+ }
+
+ @Test
+ fun scheduleWhileRunning() {
+ val latchForStartRequest = CountDownLatch(1)
+ val latchForEndRequest = CountDownLatch(1)
+ val request = RequestThrottle(handler) {
+ latchForStartRequest.countDown()
+ counter.incrementAndGet()
+ latchForEndRequest.awaitForTest()
+ true
+ }
+
+ // Schedule and block a request
+ request.schedule()
+ val handlerThread = startThread { handler.timeAdvance() }
+ latchForStartRequest.awaitForTest()
+
+ // Hit it with other requests
+ request.schedule()
+ (0..5).map { startThread { request.schedule() } }
+ .forEach { it.joinForTest() }
+
+ // Release everything
+ latchForEndRequest.countDown()
+ handlerThread.join()
+ handler.flush()
+
+ // Ensure another request was run after initial blocking request ends
+ assertThat(counter.get()).isEqualTo(2)
+ }
+
+ @Test
+ fun backoffRetry() {
+ val time = AtomicLong(0)
+ val handler = TestHandler(null) { time.get() }
+ val returnValue = AtomicBoolean(false)
+ val request = RequestThrottle(handler, 3, 1000, 2) {
+ counter.incrementAndGet()
+ returnValue.get()
+ }
+
+ request.schedule()
+
+ handler.timeAdvance()
+ handler.pendingMessages.apply {
+ assertThat(size).isEqualTo(1)
+ assertThat(single().sendTime).isAround(1000)
+ }
+
+ time.set(1000)
+ handler.timeAdvance()
+ handler.pendingMessages.apply {
+ assertThat(size).isEqualTo(1)
+ assertThat(single().sendTime).isAround(3000)
+ }
+
+ time.set(3000)
+ handler.timeAdvance()
+ handler.pendingMessages.apply {
+ assertThat(size).isEqualTo(1)
+ assertThat(single().sendTime).isAround(7000)
+ }
+
+ returnValue.set(true)
+ time.set(7000)
+ handler.timeAdvance()
+ assertThat(handler.pendingMessages).isEmpty()
+
+ // Ensure another request was run after initial blocking request ends
+ assertThat(counter.get()).isEqualTo(4)
+ }
+
+ @Test
+ fun forceWriteMultiple() {
+ val request = RequestThrottle(handler) {
+ counter.incrementAndGet()
+ true
+ }
+
+ request.runNow()
+ request.runNow()
+ request.runNow()
+
+ assertThat(counter.get()).isEqualTo(3)
+ }
+
+ @Test
+ fun forceWriteNowWithoutSync() {
+ // When forcing a write without synchronizing the request block, 2 instances will be run.
+ // There is no test for "with sync" because any logic to avoid multiple runs is left
+ // entirely up to the caller.
+
+ val barrierForEndRequest = CyclicBarrier(2)
+ val request = RequestThrottle(handler) {
+ counter.incrementAndGet()
+ barrierForEndRequest.awaitForTest()
+ true
+ }
+
+ // Schedule and block a request
+ request.schedule()
+ val thread = startThread { handler.timeAdvance() }
+
+ request.runNow()
+
+ thread.joinForTest()
+
+ assertThat(counter.get()).isEqualTo(2)
+ }
+
+ private fun CountDownLatch.awaitForTest() = assertThat(await(5, TimeUnit.SECONDS)).isTrue()
+ private fun CyclicBarrier.awaitForTest() = await(5, TimeUnit.SECONDS)
+ private fun Thread.joinForTest() = join(5000)
+
+ private fun startThread(block: () -> Unit) = Thread { block() }.apply { start() }
+
+ // Float math means time calculations are not exact, so use a loose range
+ private fun LongSubject.isAround(value: Long, threshold: Long = 10) =
+ isIn(Range.closed(value - threshold, value + threshold))
+}
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/OWNERS b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/OWNERS
new file mode 100644
index 0000000..c74393b
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
new file mode 100644
index 0000000..a4de08a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file2.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file2.xml
new file mode 100644
index 0000000..361075e
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file2.xml
@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+ <user id="10" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+ <user id="11" enabled="false">
+ </user>
+ <user id="12" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file3.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file3.xml
new file mode 100644
index 0000000..e8f9edf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file3.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file4.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file4.xml
new file mode 100644
index 0000000..d26c275
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file4.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+ <user id="10" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file5.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file5.xml
new file mode 100644
index 0000000..5c9d0cd
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file5.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+ <user id="10" enabled="false">
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 24b85f0..92e4ec9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -61,6 +61,8 @@
import android.location.LastLocationRequest;
import android.location.Location;
import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.LocationTagInfo;
+import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
@@ -90,6 +92,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -216,6 +219,21 @@
}
@Test
+ public void testAttributionTags() {
+ OnProviderLocationTagsChangeListener listener = mock(
+ OnProviderLocationTagsChangeListener.class);
+ mManager.setOnProviderLocationTagsChangeListener(listener);
+
+ mProvider.setExtraAttributionTags(Collections.singleton("extra"));
+
+ ArgumentCaptor<LocationTagInfo> captor = ArgumentCaptor.forClass(LocationTagInfo.class);
+ verify(listener, times(2)).onLocationTagsChanged(captor.capture());
+
+ assertThat(captor.getAllValues().get(0).getTags()).isEmpty();
+ assertThat(captor.getAllValues().get(1).getTags()).containsExactly("extra", "attribution");
+ }
+
+ @Test
public void testRemoveProvider() {
mManager.setRealProvider(null);
assertThat(mManager.hasProvider()).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/OWNERS b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/OWNERS
new file mode 100644
index 0000000..c74393b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
new file mode 100644
index 0000000..844687f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sensorprivacy;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Environment;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.LocalServices;
+import com.android.server.SensorPrivacyService;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+@RunWith(AndroidTestingRunner.class)
+public class SensorPrivacyServiceMockingTest {
+
+ private static final String PERSISTENCE_FILE_PATHS_TEMPLATE =
+ "SensorPrivacyServiceMockingTest/persisted_file%d.xml";
+ public static final String PERSISTENCE_FILE1 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 1);
+ public static final String PERSISTENCE_FILE2 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 2);
+ public static final String PERSISTENCE_FILE3 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 3);
+ public static final String PERSISTENCE_FILE4 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 4);
+ public static final String PERSISTENCE_FILE5 =
+ String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
+
+ private Context mContext;
+ @Mock
+ private AppOpsManager mMockedAppOpsManager;
+ @Mock
+ private UserManagerInternal mMockedUserManagerInternal;
+ @Mock
+ private ActivityManager mMockedActivityManager;
+ @Mock
+ private ActivityTaskManager mMockedActivityTaskManager;
+ @Mock
+ private TelephonyManager mMockedTelephonyManager;
+
+ @Test
+ public void testServiceInit() throws IOException {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(LocalServices.class)
+ .spyStatic(Environment.class)
+ .startMocking();
+
+ try {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ spyOn(mContext);
+
+ doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+ doReturn(mMockedUserManagerInternal)
+ .when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
+ doReturn(mMockedActivityTaskManager)
+ .when(mContext).getSystemService(ActivityTaskManager.class);
+ doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
+ TelephonyManager.class);
+
+ String dataDir = mContext.getApplicationInfo().dataDir;
+ doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+
+ File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
+ onDeviceFile.delete();
+
+ // Try all files with one known user
+ doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
+ doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+ .getUserInfo(0);
+ initServiceWithPersistenceFile(onDeviceFile, null);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
+
+ // Try all files with two known users
+ doReturn(new int[]{0, 10}).when(mMockedUserManagerInternal).getUserIds();
+ doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+ .getUserInfo(0);
+ doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+ .getUserInfo(10);
+ initServiceWithPersistenceFile(onDeviceFile, null);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
+ initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
+
+ } finally {
+ mockitoSession.finishMocking();
+ }
+ }
+
+ private void initServiceWithPersistenceFile(File onDeviceFile,
+ String persistenceFilePath) throws IOException {
+ if (persistenceFilePath != null) {
+ Files.copy(mContext.getAssets().open(persistenceFilePath),
+ onDeviceFile.toPath());
+ }
+ new SensorPrivacyService(mContext);
+ onDeviceFile.delete();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 638b1b4..8344049 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -30,6 +30,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import com.android.server.LocalServices;
@@ -57,8 +58,36 @@
private ProcessRecord mProcessRecord;
private static final long ZERO = 0L;
- private static final long USAGE_STATS_INTERACTION = 2 * 60 * 60 * 1000L;
- private static final long SERVICE_USAGE_INTERACTION = 30 * 60 * 1000;
+ private static final long USAGE_STATS_INTERACTION = 10 * 60 * 1000L;
+ private static final long SERVICE_USAGE_INTERACTION = 60 * 1000;
+
+ static class MyOomAdjuster extends OomAdjuster {
+
+ private final PlatformCompatCache mPlatformCompatCache;
+
+ MyOomAdjuster(ActivityManagerService service, ProcessList processList,
+ ActiveUids activeUids) {
+ super(service, processList, activeUids);
+ mPlatformCompatCache = new MyPlatformCompatCache(new long[]{});
+ }
+
+ static class MyPlatformCompatCache extends PlatformCompatCache {
+
+ MyPlatformCompatCache(long[] compatChanges) {
+ super(compatChanges);
+ }
+
+ @Override
+ boolean isChangeEnabled(long changeId, ApplicationInfo app, boolean defaultValue) {
+ return true;
+ }
+ }
+
+ @Override
+ protected OomAdjuster.PlatformCompatCache getPlatformCompatCache() {
+ return mPlatformCompatCache;
+ }
+ }
@BeforeClass
public static void setUpOnce() {
@@ -84,7 +113,7 @@
final AppProfiler profiler = mock(AppProfiler.class);
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
- sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null);
+ sService.mOomAdjuster = new MyOomAdjuster(sService, sService.mProcessList, null);
LocalServices.addService(UsageStatsManagerInternal.class,
mock(UsageStatsManagerInternal.class));
sService.mUsageStatsService = LocalServices.getService(UsageStatsManagerInternal.class);
@@ -119,8 +148,10 @@
// Ensure certain services and constants are defined properly
assertNotNull(sService.mUsageStatsService);
- assertEquals(USAGE_STATS_INTERACTION, sService.mConstants.USAGE_STATS_INTERACTION_INTERVAL);
- assertEquals(SERVICE_USAGE_INTERACTION, sService.mConstants.SERVICE_USAGE_INTERACTION_TIME);
+ assertEquals(USAGE_STATS_INTERACTION,
+ sService.mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S);
+ assertEquals(SERVICE_USAGE_INTERACTION,
+ sService.mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 1b42dfa..c54dffc 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -30,6 +30,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
@@ -7333,6 +7334,48 @@
assertThat(dpm.getPolicyExemptApps()).containsExactly("4", "8", "15", "16", "23", "42");
}
+ @Test
+ public void testSetGlobalPrivateDnsModeOpportunistic_asDeviceOwner() throws Exception {
+ setDeviceOwner();
+ // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+ // feature is disabled because there are non-affiliated secondary users.
+ getServices().removeUser(CALLER_USER_HANDLE);
+ clearInvocations(getServices().settings);
+
+ int result = dpm.setGlobalPrivateDnsModeOpportunistic(admin1);
+
+ assertThat(result).isEqualTo(PRIVATE_DNS_SET_NO_ERROR);
+ }
+
+ @Test
+ public void testSetGlobalPrivateDnsModeOpportunistic_hasUnaffiliatedUsers() throws Exception {
+ setDeviceOwner();
+ setAsProfileOwner(admin2);
+
+ assertThrows(SecurityException.class,
+ () -> dpm.setGlobalPrivateDnsModeOpportunistic(admin1));
+ }
+
+ @Test
+ public void testSetRecommendedGlobalProxy_asDeviceOwner() throws Exception {
+ setDeviceOwner();
+ // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+ // feature is disabled because there are non-affiliated secondary users.
+ getServices().removeUser(CALLER_USER_HANDLE);
+
+ dpm.setRecommendedGlobalProxy(admin1, null);
+
+ verify(getServices().connectivityManager).setGlobalProxy(null);
+ }
+
+ @Test
+ public void testSetRecommendedGlobalProxy_hasUnaffiliatedUsers() throws Exception {
+ setDeviceOwner();
+ setAsProfileOwner(admin2);
+
+ assertThrows(SecurityException.class, () -> dpm.setRecommendedGlobalProxy(admin1, null));
+ }
+
private void setUserUnlocked(int userHandle, boolean unlocked) {
when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
}
diff --git a/services/tests/servicestests/src/com/android/server/utils/OWNERS b/services/tests/servicestests/src/com/android/server/utils/OWNERS
new file mode 100644
index 0000000..1853220
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/OWNERS
@@ -0,0 +1,4 @@
+per-file WatchableTester.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file WatchableTester.java = shombert@google.com
+per-file WatcherTest.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file WatcherTest.java = shombert@google.com
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
index a05fea2..1126e1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
@@ -21,6 +21,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
import android.app.Notification;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -37,7 +39,11 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.ConcurrentModificationException;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -165,4 +171,54 @@
assertThat(expected).contains(sbn.getKey());
}
}
+
+ @Test
+ public void testRemoveChannelNotifications_concurrently() throws InterruptedException {
+ List<String> expected = new ArrayList<>();
+ // Add one extra notification to the beginning to test when 2 adjacent notifications will be
+ // removed in the same pass.
+ StatusBarNotification sbn0 = getNotification("pkg", 0, UserHandle.of(USER_CURRENT));
+ mArchive.record(sbn0, REASON_CANCEL);
+ for (int i = 0; i < SIZE; i++) {
+ StatusBarNotification sbn = getNotification("pkg", i, UserHandle.of(USER_CURRENT));
+ mArchive.record(sbn, REASON_CANCEL);
+ if (i >= SIZE - 2) {
+ // Remove everything < SIZE - 2
+ expected.add(sbn.getKey());
+ }
+ }
+
+ // Remove these in multiple threads to try to get them to happen at the same time
+ int numThreads = SIZE - 2;
+ AtomicBoolean error = new AtomicBoolean(false);
+ CountDownLatch startThreadsLatch = new CountDownLatch(1);
+ CountDownLatch threadsDone = new CountDownLatch(numThreads);
+ for (int i = 0; i < numThreads; i++) {
+ final int idx = i;
+ new Thread(() -> {
+ try {
+ startThreadsLatch.await(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ try {
+ mArchive.removeChannelNotifications("pkg", USER_CURRENT, "test" + idx);
+ } catch (ConcurrentModificationException e) {
+ error.compareAndSet(false, true);
+ }
+ }).start();
+ }
+
+ startThreadsLatch.countDown();
+ threadsDone.await(10, TimeUnit.SECONDS);
+ if (error.get()) {
+ fail("Concurrent modification exception");
+ }
+
+ List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(SIZE, true));
+ assertThat(actual).hasSize(expected.size());
+ for (StatusBarNotification sbn : actual) {
+ assertThat(expected).contains(sbn.getKey());
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 537bc2c..c33287c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -141,6 +141,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
@@ -267,6 +268,8 @@
RankingHandler mRankingHandler;
@Mock
ActivityManagerInternal mAmi;
+ @Mock
+ private Looper mMainLooper;
@Mock
IIntentSender pi1;
@@ -514,7 +517,9 @@
mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
mAmi, mToastRateLimiter);
- mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ // Return first true for RoleObserver main-thread check
+ when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
mService.setAudioManager(mAudioManager);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 4ce237e..27ae46c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.role.RoleManager.ROLE_BROWSER;
import static android.app.role.RoleManager.ROLE_DIALER;
import static android.app.role.RoleManager.ROLE_EMERGENCY;
import static android.content.pm.PackageManager.MATCH_ALL;
@@ -23,6 +24,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -31,6 +33,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.util.Arrays.asList;
+
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
@@ -44,7 +48,6 @@
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
@@ -80,7 +83,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -98,13 +100,13 @@
@Mock
private UserManager mUm;
@Mock
- private Executor mExecutor;
- @Mock
private RoleManager mRoleManager;
+ @Mock
+ private Looper mMainLooper;
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
- private List<UserInfo> mUsers;
+ private List<UserHandle> mUsers;
private static class TestableNotificationManagerService extends NotificationManagerService {
TestableNotificationManagerService(Context context,
@@ -133,13 +135,15 @@
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
mUsers = new ArrayList<>();
- mUsers.add(new UserInfo(0, "system", 0));
- mUsers.add(new UserInfo(10, "second", 0));
- when(mUm.getUsers()).thenReturn(mUsers);
+ mUsers.add(new UserHandle(0));
+ mUsers.add(new UserHandle(10));
+ when(mUm.getUserHandles(anyBoolean())).thenReturn(mUsers);
+
+ when(mMainLooper.isCurrentThread()).thenReturn(true);
mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
mNotificationInstanceIdSequence);
- mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
+ mRoleObserver = mService.new RoleObserver(mContext, mRoleManager, mPm, mMainLooper);
try {
mService.init(mService.new WorkerHandler(mock(Looper.class)),
@@ -174,7 +178,7 @@
}
@Test
- public void testInit() throws Exception {
+ public void testInit_forNonBlockableDefaultApps() throws Exception {
List<String> dialer0 = new ArrayList<>();
dialer0.add("dialer");
List<String> emer0 = new ArrayList<>();
@@ -191,29 +195,29 @@
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(0).getUserHandle())).
- thenReturn(dialer0);
+ mUsers.get(0)))
+ .thenReturn(dialer0);
when(mRoleManager.getRoleHoldersAsUser(
ROLE_EMERGENCY,
- mUsers.get(0).getUserHandle())).
- thenReturn(emer0);
+ mUsers.get(0)))
+ .thenReturn(emer0);
mRoleObserver.init();
// verify internal records of current state of the world
assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
- ROLE_DIALER, dialer0.get(0), mUsers.get(0).id));
+ ROLE_DIALER, dialer0.get(0), mUsers.get(0).getIdentifier()));
assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
- ROLE_DIALER, dialer0.get(0), mUsers.get(1).id));
+ ROLE_DIALER, dialer0.get(0), mUsers.get(1).getIdentifier()));
assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
- ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).id));
+ ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).getIdentifier()));
assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
- ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).id));
+ ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).getIdentifier()));
// make sure we're listening to updates
verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(
- eq(mExecutor), any(), eq(UserHandle.ALL));
+ any(), any(), eq(UserHandle.ALL));
// make sure we told pref helper about the state of the world
verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, dialer0Pair);
@@ -221,14 +225,31 @@
}
@Test
- public void testSwapDefault() throws Exception {
+ public void testInit_forTrampolines() throws Exception {
+ when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_BROWSER,
+ mUsers.get(0)))
+ .thenReturn(asList("com.browser"));
+
+ mRoleObserver.init();
+
+ assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+
+ // make sure we're listening to updates
+ verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(any(), any(),
+ eq(UserHandle.ALL));
+ }
+
+ @Test
+ public void testSwapDefault_forNonBlockableDefaultApps() throws Exception {
List<String> dialer0 = new ArrayList<>();
dialer0.add("dialer");
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(0).getUserHandle())).
- thenReturn(dialer0);
+ mUsers.get(0)))
+ .thenReturn(dialer0);
mRoleObserver.init();
@@ -241,8 +262,8 @@
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(0).getUserHandle())).
- thenReturn(newDefault);
+ mUsers.get(0)))
+ .thenReturn(newDefault);
mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));
@@ -251,15 +272,39 @@
}
@Test
- public void testSwapDefault_multipleOverlappingApps() throws Exception {
+ public void testSwapDefault_forTrampolines() throws Exception {
+ List<String> dialer0 = new ArrayList<>();
+ when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
+ when(mPm.getPackageUid("com.browser2", MATCH_ALL, 0)).thenReturn(31);
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_BROWSER,
+ mUsers.get(0)))
+ .thenReturn(asList("com.browser"));
+ mRoleObserver.init();
+ assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+ assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(31));
+ // Default changed
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_BROWSER,
+ mUsers.get(0)))
+ .thenReturn(asList("com.browser2"));
+ mRoleObserver.onRoleHoldersChanged(ROLE_BROWSER, UserHandle.of(0));
+
+ assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+ assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(31));
+ }
+
+ @Test
+ public void testSwapDefault_multipleOverlappingApps_forNonBlockableDefaultApps()
+ throws Exception {
List<String> dialer0 = new ArrayList<>();
dialer0.add("dialer");
dialer0.add("phone");
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(0).getUserHandle())).
- thenReturn(dialer0);
+ mUsers.get(0)))
+ .thenReturn(dialer0);
mRoleObserver.init();
@@ -273,8 +318,8 @@
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(0).getUserHandle())).
- thenReturn(newDefault);
+ mUsers.get(0)))
+ .thenReturn(newDefault);
ArraySet<String> expectedRemove = new ArraySet<>();
expectedRemove.add("dialer");
@@ -294,14 +339,14 @@
}
@Test
- public void testSwapDefault_newUser() throws Exception {
+ public void testSwapDefault_newUser_forNonBlockableDefaultApps() throws Exception {
List<String> dialer0 = new ArrayList<>();
dialer0.add("dialer");
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(0).getUserHandle())).
- thenReturn(dialer0);
+ mUsers.get(0)))
+ .thenReturn(dialer0);
mRoleObserver.init();
@@ -310,8 +355,8 @@
when(mRoleManager.getRoleHoldersAsUser(
ROLE_DIALER,
- mUsers.get(1).getUserHandle())).
- thenReturn(dialer10);
+ mUsers.get(1)))
+ .thenReturn(dialer10);
ArraySet<Pair<String, Integer>> expectedAddPair = new ArraySet<>();
expectedAddPair.add(new Pair("phone", 30));
@@ -329,4 +374,27 @@
assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 10));
assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
}
+
+ @Test
+ public void testSwapDefault_newUser_forTrampolines() throws Exception {
+ List<String> dialer0 = new ArrayList<>();
+ when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
+ when(mPm.getPackageUid("com.browser2", MATCH_ALL, 10)).thenReturn(1031);
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_BROWSER,
+ mUsers.get(0)))
+ .thenReturn(asList("com.browser"));
+ mRoleObserver.init();
+ assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+ assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(1031));
+ // New user
+ when(mRoleManager.getRoleHoldersAsUser(
+ ROLE_BROWSER,
+ mUsers.get(1)))
+ .thenReturn(asList("com.browser2"));
+ mRoleObserver.onRoleHoldersChanged(ROLE_BROWSER, UserHandle.of(10));
+
+ assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+ assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(1031));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index fdeb7a6..4bbea94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1723,6 +1723,15 @@
assertFalse(display.hasTopFixedRotationLaunchingApp());
assertFalse(activity.hasFixedRotationTransform());
+
+ // Simulate that the activity requests the same orientation as display.
+ activity.setOrientation(display.getConfiguration().orientation);
+ // Skip the real freezing.
+ activity.mVisibleRequested = false;
+ clearInvocations(activity);
+ activity.onCancelFixedRotationTransform(originalRotation);
+ // The implementation of cancellation must be executed.
+ verify(activity).startFreezingScreen(originalRotation);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 13ef998..9226c0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -62,7 +62,6 @@
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import android.app.ActivityManager;
import android.app.TaskInfo;
@@ -1323,20 +1322,22 @@
}
@Test
- public void testNotifyOrientationChangeCausedByConfigurationChange() {
+ public void testTaskOrientationOnDisplayWindowingModeChange() {
+ // Skip unnecessary operations to speed up the test.
+ mAtm.deferWindowLayout();
final Task task = getTestTask();
final ActivityRecord activity = task.getTopMostActivity();
final DisplayContent display = task.getDisplayContent();
- display.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mWm.setWindowingMode(display.mDisplayId, WINDOWING_MODE_FREEFORM);
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
- verify(display).onDescendantOrientationChanged(same(task));
- reset(display);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, display.getLastOrientation());
- display.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mWm.setWindowingMode(display.mDisplayId, WINDOWING_MODE_FULLSCREEN);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation());
- verify(display).onDescendantOrientationChanged(same(task));
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, display.getLastOrientation());
+ assertEquals(Configuration.ORIENTATION_LANDSCAPE, display.getConfiguration().orientation);
}
@Test
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d77ab2b..f0d43fa 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -2543,6 +2543,7 @@
} else if (mRttCall != null && parcelableCall.getParcelableRttCall() == null
&& parcelableCall.getIsRttCallChanged()) {
isRttChanged = true;
+ mRttCall.close();
mRttCall = null;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d9001bd..78da86c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15016,6 +15016,15 @@
"CAPABILITY_SLICING_CONFIG_SUPPORTED";
/**
+ * Indicates whether PHYSICAL_CHANNEL_CONFIG HAL1.6 is supported. See comments on
+ * respective methods for more information.
+ *
+ * @hide
+ */
+ public static final String CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED =
+ "CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED";
+
+ /**
* A list of the radio interface capability values with public valid constants.
*
* Here is a related list for the systemapi-only valid constants:
diff --git a/tests/HwAccelerationTest/res/layout/stretch_layout.xml b/tests/HwAccelerationTest/res/layout/stretch_layout.xml
index df5f297..81e0c01 100644
--- a/tests/HwAccelerationTest/res/layout/stretch_layout.xml
+++ b/tests/HwAccelerationTest/res/layout/stretch_layout.xml
@@ -16,7 +16,6 @@
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scroll_view"
- android:edgeEffectType="stretch"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
@@ -26,7 +25,6 @@
<HorizontalScrollView
android:id="@+id/horizontal_scroll_view"
- android:edgeEffectType="stretch"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
diff --git a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
index c01d32b..6ef1ecd 100644
--- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
+++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
@@ -35,7 +35,7 @@
fun setUp() {
mViewFrameInfo.reset()
mViewFrameInfo.setInputEvent(139)
- mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
+ mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED
mTimeStarted = SystemClock.uptimeNanos()
mViewFrameInfo.markDrawStart()
}
@@ -43,7 +43,7 @@
@Test
fun testPopulateFields() {
assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
- assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
+ assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED)
}
@Test
@@ -66,7 +66,7 @@
mViewFrameInfo.populateFrameInfo(frameInfo)
assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139)
assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
- FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
+ FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED)
assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)
}
}
\ No newline at end of file
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index 8b0ae5c..ea5a431 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -21,16 +21,15 @@
default_applicable_licenses: ["frameworks_base_license"],
}
-java_test_host {
+android_test {
name: "UpdatableSystemFontTest",
srcs: ["src/**/*.java"],
- libs: [
- "tradefed",
- "compatibility-tradefed",
- "compatibility-host-util",
- ],
+ libs: ["android.test.runner"],
static_libs: [
- "frameworks-base-hostutils",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "platform-test-annotations",
+ "truth-prebuilt",
],
test_suites: [
"general-tests",
@@ -47,4 +46,5 @@
":UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf",
":UpdatableSystemFontTestNotoColorEmojiVPlus2TtfFsvSig",
],
+ sdk_version: "test_current",
}
diff --git a/tests/UpdatableSystemFontTest/AndroidManifest.xml b/tests/UpdatableSystemFontTest/AndroidManifest.xml
new file mode 100644
index 0000000..531ee98
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.updatablesystemfont">
+
+ <application android:label="UpdatableSystemFontTest">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="UpdatableSystemFontTest"
+ android:targetPackage="com.android.updatablesystemfont">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index 4f11669..4f6487e 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -21,6 +21,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="UpdatableSystemFontTest.apk" />
<option name="test-file-name" value="EmojiRenderingTestApp.apk" />
</target_preparer>
@@ -37,7 +38,7 @@
<option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig" />
</target_preparer>
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="UpdatableSystemFontTest.jar" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.updatablesystemfont" />
</test>
</configuration>
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 74f6bca..79e23b8 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -17,26 +17,35 @@
package com.android.updatablesystemfont;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
import static java.util.concurrent.TimeUnit.SECONDS;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.RootPermissionTest;
+import android.security.FileIntegrityManager;
+import android.util.Log;
+import android.util.Pair;
-import com.android.fsverity.AddFsVerityCertRule;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.StreamUtil;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -44,9 +53,10 @@
* Tests if fonts can be updated by 'cmd font'.
*/
@RootPermissionTest
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
+@RunWith(AndroidJUnit4.class)
+public class UpdatableSystemFontTest {
+ private static final String TAG = "UpdatableSystemFontTest";
private static final String SYSTEM_FONTS_DIR = "/system/fonts/";
private static final String DATA_FONTS_DIR = "/data/fonts/files/";
@@ -84,58 +94,65 @@
T get() throws Exception;
}
- @Rule
- public final AddFsVerityCertRule mAddFsverityCertRule =
- new AddFsVerityCertRule(this, CERT_PATH);
+ private String mKeyId;
@Before
public void setUp() throws Exception {
- expectRemoteCommandToSucceed("cmd font clear");
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ // Run tests only if updatable system font is enabled.
+ FileIntegrityManager fim = context.getSystemService(FileIntegrityManager.class);
+ assumeTrue(fim != null);
+ assumeTrue(fim.isApkVeritySupported());
+ mKeyId = insertCert(CERT_PATH);
+ expectCommandToSucceed("cmd font clear");
}
@After
public void tearDown() throws Exception {
- expectRemoteCommandToSucceed("cmd font clear");
+ expectCommandToSucceed("cmd font clear");
+ if (mKeyId != null) {
+ expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
+ }
}
@Test
public void updateFont() throws Exception {
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith(DATA_FONTS_DIR);
// The updated font should be readable and unmodifiable.
- expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
- expectRemoteCommandToFail("echo -n '' >> " + fontPath);
+ expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null");
+ expectCommandToFail("dd status=none if=" + CERT_PATH + " of=" + fontPath);
}
@Test
public void updateFont_twice() throws Exception {
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
assertThat(fontPath2).isNotEqualTo(fontPath);
// The new file should be readable.
- expectRemoteCommandToSucceed("cat " + fontPath2 + " > /dev/null");
+ expectCommandToSucceed("dd status=none if=" + fontPath2 + " of=/dev/null");
// The old file should be still readable.
- expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+ expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null");
}
@Test
public void updateFont_allowSameVersion() throws Exception {
// Update original font to the same version
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
// Update updated font to the same version
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith(DATA_FONTS_DIR);
@@ -147,21 +164,21 @@
@Test
public void updateFont_invalidCert() throws Exception {
- expectRemoteCommandToFail(String.format("cmd font update %s %s",
+ expectCommandToFail(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
}
@Test
public void updateFont_downgradeFromSystem() throws Exception {
- expectRemoteCommandToFail(String.format("cmd font update %s %s",
+ expectCommandToFail(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG));
}
@Test
public void updateFont_downgradeFromData() throws Exception {
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
- expectRemoteCommandToFail(String.format("cmd font update %s %s",
+ expectCommandToFail(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
}
@@ -178,7 +195,7 @@
public void launchApp_afterUpdateFont() throws Exception {
String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR);
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR);
@@ -191,57 +208,99 @@
@Test
public void reboot() throws Exception {
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ expectCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPath).startsWith(DATA_FONTS_DIR);
// Emulate reboot by 'cmd font restart'.
- expectRemoteCommandToSucceed("cmd font restart");
+ expectCommandToSucceed("cmd font restart");
String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
assertThat(fontPathAfterReboot).isEqualTo(fontPath);
}
- private String getFontPath(String fontFileName) throws Exception {
+ private static String insertCert(String certPath) throws Exception {
+ Pair<String, String> result;
+ try (InputStream is = new FileInputStream(certPath)) {
+ result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
+ }
+ // Assert that there are no errors.
+ assertThat(result.second).isEmpty();
+ String keyId = result.first.trim();
+ assertThat(keyId).matches("^\\d+$");
+ return keyId;
+ }
+
+ private static String getFontPath(String fontFileName) throws Exception {
// TODO: add a dedicated command for testing.
- String lines = expectRemoteCommandToSucceed("cmd font dump");
+ String lines = expectCommandToSucceed("cmd font dump");
for (String line : lines.split("\n")) {
Matcher m = PATTERN_FONT.matcher(line);
if (m.find() && m.group(1).endsWith(fontFileName)) {
return m.group(1);
}
}
- CLog.e("Font not found: " + fontFileName);
- return null;
+ throw new AssertionError("Font not found: " + fontFileName);
}
- private void startActivity(String appId, String activityId) throws Exception {
+ private static void startActivity(String appId, String activityId) throws Exception {
// Make sure that the app is installed and enabled.
waitUntil(ACTIVITY_TIMEOUT_MILLIS, () -> {
- String packageInfo = expectRemoteCommandToSucceed(
- "pm list packages -e " + EMOJI_RENDERING_TEST_APP_ID);
+ String packageInfo = expectCommandToSucceed("pm list packages -e " + appId);
return !packageInfo.isEmpty();
});
- expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
- expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
+ expectCommandToSucceed("am force-stop " + appId);
+ expectCommandToSucceed("am start-activity -n " + activityId);
}
- private String expectRemoteCommandToSucceed(String cmd) throws Exception {
- CommandResult result = getDevice().executeShellV2Command(cmd);
- assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
- .that(result.getStatus())
- .isEqualTo(CommandStatus.SUCCESS);
- return result.getStdout();
+ private static String expectCommandToSucceed(String cmd) throws IOException {
+ Pair<String, String> result = runShellCommand(cmd, null);
+ // UiAutomation.runShellCommand() does not return exit code.
+ // Assume that the command fails if stderr is not empty.
+ assertThat(result.second.trim()).isEmpty();
+ return result.first;
}
- private void expectRemoteCommandToFail(String cmd) throws Exception {
- CommandResult result = getDevice().executeShellV2Command(cmd);
- assertWithMessage("Unexpected success from `" + cmd + "`: " + result.getStderr())
- .that(result.getStatus())
- .isNotEqualTo(CommandStatus.SUCCESS);
+ private static void expectCommandToFail(String cmd) throws IOException {
+ Pair<String, String> result = runShellCommand(cmd, null);
+ // UiAutomation.runShellCommand() does not return exit code.
+ // Assume that the command fails if stderr is not empty.
+ assertThat(result.second.trim()).isNotEmpty();
}
- private void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
+ /** Runs a command and returns (stdout, stderr). */
+ private static Pair<String, String> runShellCommand(String cmd, @Nullable InputStream input)
+ throws IOException {
+ Log.i(TAG, "runShellCommand: " + cmd);
+ UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ ParcelFileDescriptor[] rwe = automation.executeShellCommandRwe(cmd);
+ // executeShellCommandRwe returns [stdout, stdin, stderr].
+ try (ParcelFileDescriptor outFd = rwe[0];
+ ParcelFileDescriptor inFd = rwe[1];
+ ParcelFileDescriptor errFd = rwe[2]) {
+ if (input != null) {
+ try (OutputStream os = new FileOutputStream(inFd.getFileDescriptor())) {
+ StreamUtil.copyStreams(input, os);
+ }
+ }
+ // We have to close stdin before reading stdout and stderr.
+ // It's safe to close ParcelFileDescriptor multiple times.
+ inFd.close();
+ String stdout;
+ try (InputStream is = new FileInputStream(outFd.getFileDescriptor())) {
+ stdout = StreamUtil.readInputStream(is);
+ }
+ Log.i(TAG, "stdout = " + stdout);
+ String stderr;
+ try (InputStream is = new FileInputStream(errFd.getFileDescriptor())) {
+ stderr = StreamUtil.readInputStream(is);
+ }
+ Log.i(TAG, "stderr = " + stderr);
+ return new Pair<>(stdout, stderr);
+ }
+ }
+
+ private static void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
long untilMillis = System.currentTimeMillis() + timeoutMillis;
do {
try {
@@ -256,25 +315,16 @@
throw new AssertionError("Timed out");
}
- private boolean isFileOpenedBy(String path, String appId) throws DeviceNotAvailableException {
+ private static boolean isFileOpenedBy(String path, String appId) throws Exception {
String pid = pidOf(appId);
if (pid.isEmpty()) {
return false;
}
- CommandResult result = getDevice().executeShellV2Command(
- String.format("lsof -t -p %s '%s'", pid, path));
- if (result.getStatus() != CommandStatus.SUCCESS) {
- return false;
- }
- // The file is open if the output of lsof is non-empty.
- return !result.getStdout().trim().isEmpty();
+ String cmd = String.format("lsof -t -p %s %s", pid, path);
+ return !expectCommandToSucceed(cmd).trim().isEmpty();
}
- private String pidOf(String appId) throws DeviceNotAvailableException {
- CommandResult result = getDevice().executeShellV2Command("pidof " + appId);
- if (result.getStatus() != CommandStatus.SUCCESS) {
- return "";
- }
- return result.getStdout().trim();
+ private static String pidOf(String appId) throws Exception {
+ return expectCommandToSucceed("pidof " + appId).trim();
}
}
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 21386b8..f2c3b86 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -969,6 +969,8 @@
densities.add(dens);
}
+ std::vector<ResXMLParser::ResXMLPosition> tagsToSkip;
+
size_t len;
ResXMLTree::event_code_t code;
int depth = 0;
@@ -1091,6 +1093,42 @@
Vector<FeatureGroup> featureGroups;
KeyedVector<String8, ImpliedFeature> impliedFeatures;
+ {
+ int curDepth = 0;
+ ResXMLParser::ResXMLPosition initialPos;
+ tree.getPosition(&initialPos);
+
+ // Find all of the "uses-sdk" tags within the "manifest" tag.
+ std::vector<ResXMLParser::ResXMLPosition> usesSdkTagPositions;
+ ResXMLParser::ResXMLPosition curPos;
+ while ((code = tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ curDepth--;
+ continue;
+ }
+ if (code == ResXMLTree::START_TAG) {
+ curDepth++;
+ }
+ const char16_t* ctag16 = tree.getElementName(&len);
+ if (ctag16 == NULL || String8(ctag16) != "uses-sdk" || curDepth != 2) {
+ continue;
+ }
+
+ tree.getPosition(&curPos);
+ usesSdkTagPositions.emplace_back(curPos);
+ }
+
+ // Skip all "uses-sdk" tags besides the very last tag. The android runtime only uses
+ // the attribute values from the last defined tag.
+ for (size_t i = 0; i < usesSdkTagPositions.size() - 1; i++) {
+ tagsToSkip.emplace_back(usesSdkTagPositions[i]);
+ }
+
+ // Reset the position before parsing.
+ tree.setPosition(initialPos);
+ }
+
while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
@@ -1202,8 +1240,25 @@
if (code != ResXMLTree::START_TAG) {
continue;
}
+
depth++;
+ // If this tag should be skipped, skip to the end of this tag.
+ ResXMLParser::ResXMLPosition curPos;
+ tree.getPosition(&curPos);
+ if (std::find(tagsToSkip.begin(), tagsToSkip.end(), curPos) != tagsToSkip.end()) {
+ const int breakDepth = depth - 1;
+ while ((code = tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG && --depth == breakDepth) {
+ break;
+ } else if (code == ResXMLTree::START_TAG) {
+ depth++;
+ }
+ }
+ continue;
+ }
+
const char16_t* ctag16 = tree.getElementName(&len);
if (ctag16 == NULL) {
SourcePos(manifestFile, tree.getLineNumber()).error(