summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java14
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java46
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java17
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java6
-rw-r--r--apex/appsearch/synced_jetpack_changeid.txt2
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java6
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java11
-rw-r--r--apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java8
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/PowerAllowlistInternal.java40
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java100
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java171
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/NotificationChannel.java33
-rw-r--r--core/java/android/app/usage/StorageStats.java14
-rw-r--r--core/java/android/view/SurfaceView.java2
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/content/OWNERS1
-rw-r--r--core/tests/coretests/src/android/content/pm/OWNERS2
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp5
-rw-r--r--libs/hwui/hwui/ImageDecoder.h1
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp7
-rw-r--r--native/graphics/jni/imagedecoder.cpp2
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java90
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/CacheOomRanker.java308
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java19
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java18
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java33
-rw-r--r--services/core/java/com/android/server/policy/WindowOrientationListener.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java10
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java5
-rw-r--r--services/tests/mockingservicestests/AndroidManifest.xml2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java432
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java246
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java12
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java90
-rw-r--r--services/usage/java/com/android/server/usage/StorageStatsService.java1
49 files changed, 1744 insertions, 121 deletions
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 4931cc026466..62324b2d93f7 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -52,6 +52,9 @@ public final class SearchResult {
/** @hide */
public static final String PACKAGE_NAME_FIELD = "packageName";
+ /** @hide */
+ public static final String DATABASE_NAME_FIELD = "databaseName";
+
@NonNull private final Bundle mBundle;
/** Cache of the inflated document. Comes from inflating mDocumentBundle at first use. */
@@ -119,6 +122,17 @@ public final class SearchResult {
}
/**
+ * Contains the database name that stored the {@link GenericDocument}.
+ *
+ * @return Database name that stored the document
+ * @hide
+ */
+ @NonNull
+ public String getDatabaseName() {
+ return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD));
+ }
+
+ /**
* This class represents a match objects for any Snippets that might be present in {@link
* SearchResults} from query. Using this class user can get the full text, exact matches and
* Snippets of document content for a given match.
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index b75492629a2a..674f199f0022 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -45,6 +45,7 @@ import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.GetAllNamespacesResultProto;
import com.google.android.icing.proto.GetOptimizeInfoResultProto;
import com.google.android.icing.proto.GetResultProto;
+import com.google.android.icing.proto.GetResultSpecProto;
import com.google.android.icing.proto.GetSchemaResultProto;
import com.google.android.icing.proto.IcingSearchEngineOptions;
import com.google.android.icing.proto.InitializeResultProto;
@@ -62,6 +63,7 @@ import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.SetSchemaResultProto;
import com.google.android.icing.proto.StatusProto;
+import com.google.android.icing.proto.TypePropertyMask;
import java.io.File;
import java.util.ArrayList;
@@ -432,7 +434,9 @@ public final class AppSearchImpl {
try {
getResultProto =
mIcingSearchEngineLocked.get(
- createPrefix(packageName, databaseName) + namespace, uri);
+ createPrefix(packageName, databaseName) + namespace,
+ uri,
+ GetResultSpecProto.getDefaultInstance());
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -691,8 +695,6 @@ public final class AppSearchImpl {
* <p>If the app crashes before a call to PersistToDisk(), Icing would trigger a costly recovery
* process in next initialization. After that, Icing would still be able to recover all written
* data.
- *
- * @throws AppSearchException
*/
public void persistToDisk() throws AppSearchException {
PersistToDiskResultProto persistToDiskResultProto =
@@ -986,13 +988,12 @@ public final class AppSearchImpl {
return false;
}
- List<ResultSpecProto.TypePropertyMask> prefixedTypePropertyMasks = new ArrayList<>();
+ List<TypePropertyMask> prefixedTypePropertyMasks = new ArrayList<>();
// Rewrite filters to include a database prefix.
for (String prefix : existingPrefixes) {
Set<String> existingSchemaTypes = mSchemaMapLocked.get(prefix);
// Qualify the given schema types
- for (ResultSpecProto.TypePropertyMask typePropertyMask :
- resultSpecBuilder.getTypePropertyMasksList()) {
+ for (TypePropertyMask typePropertyMask : resultSpecBuilder.getTypePropertyMasksList()) {
String qualifiedType = prefix + typePropertyMask.getSchemaType();
if (existingSchemaTypes.contains(qualifiedType)) {
prefixedTypePropertyMasks.add(
@@ -1060,12 +1061,36 @@ public final class AppSearchImpl {
int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
if (delimiterIndex == -1) {
// This should never happen if we construct our prefixes properly
- Log.wtf(TAG, "Malformed prefix doesn't contain package name: " + prefix);
+ Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
return "";
}
return prefix.substring(0, delimiterIndex);
}
+ /**
+ * Returns the database name that's contained within the {@code prefix}.
+ *
+ * @param prefix Prefix string that contains the database name inside of it. The database name
+ * must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
+ * @return Valid database name.
+ */
+ @NonNull
+ private static String getDatabaseName(@NonNull String prefix) {
+ int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
+ int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER);
+ if (packageDelimiterIndex == -1) {
+ // This should never happen if we construct our prefixes properly
+ Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
+ return "";
+ }
+ if (databaseDelimiterIndex == -1) {
+ // This should never happen if we construct our prefixes properly
+ Log.wtf(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
+ return "";
+ }
+ return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
+ }
+
@NonNull
private static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
// The prefix is made up of the package, then the database. So we only need to find the
@@ -1178,6 +1203,9 @@ public final class AppSearchImpl {
// Parallel array of package names for each document search result.
List<String> packageNames = new ArrayList<>(searchResultProto.getResultsCount());
+ // Parallel array of database names for each document search result.
+ List<String> databaseNames = new ArrayList<>(searchResultProto.getResultsCount());
+
SearchResultProto.Builder resultsBuilder = searchResultProto.toBuilder();
for (int i = 0; i < searchResultProto.getResultsCount(); i++) {
SearchResultProto.ResultProto.Builder resultBuilder =
@@ -1185,10 +1213,12 @@ public final class AppSearchImpl {
DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
String prefix = removePrefixesFromDocument(documentBuilder);
packageNames.add(getPackageName(prefix));
+ databaseNames.add(getDatabaseName(prefix));
resultBuilder.setDocument(documentBuilder);
resultsBuilder.setResults(i, resultBuilder);
}
- return SearchResultToProtoConverter.toSearchResultPage(resultsBuilder, packageNames);
+ return SearchResultToProtoConverter.toSearchResultPage(
+ resultsBuilder, packageNames, databaseNames);
}
@GuardedBy("mReadWriteLock")
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
index ccd567d1c945..f422ebc2db7b 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java
@@ -46,11 +46,16 @@ public class SearchResultToProtoConverter {
* @param proto The {@link SearchResultProto} containing results.
* @param packageNames A parallel array of package names. The package name at index 'i' of this
* list should be the package that indexed the document at index 'i' of proto.getResults(i).
+ * @param databaseNames A parallel array of database names. The database name at index 'i' of
+ * this list shold be the database that indexed the document at index 'i' of
+ * proto.getResults(i).
* @return {@link SearchResultPage} of results.
*/
@NonNull
public static SearchResultPage toSearchResultPage(
- @NonNull SearchResultProtoOrBuilder proto, @NonNull List<String> packageNames) {
+ @NonNull SearchResultProtoOrBuilder proto,
+ @NonNull List<String> packageNames,
+ @NonNull List<String> databaseNames) {
Preconditions.checkArgument(
proto.getResultsCount() == packageNames.size(),
"Size of " + "results does not match the number of package names.");
@@ -58,7 +63,9 @@ public class SearchResultToProtoConverter {
bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
for (int i = 0; i < proto.getResultsCount(); i++) {
- resultBundles.add(toSearchResultBundle(proto.getResults(i), packageNames.get(i)));
+ resultBundles.add(
+ toSearchResultBundle(
+ proto.getResults(i), packageNames.get(i), databaseNames.get(i)));
}
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
return new SearchResultPage(bundle);
@@ -69,16 +76,20 @@ public class SearchResultToProtoConverter {
*
* @param proto The proto to be converted.
* @param packageName The package name associated with the document in {@code proto}.
+ * @param databaseName The database name associated with the document in {@code proto}.
* @return A {@link SearchResult} bundle.
*/
@NonNull
private static Bundle toSearchResultBundle(
- @NonNull SearchResultProto.ResultProtoOrBuilder proto, @NonNull String packageName) {
+ @NonNull SearchResultProto.ResultProtoOrBuilder proto,
+ @NonNull String packageName,
+ @NonNull String databaseName) {
Bundle bundle = new Bundle();
GenericDocument document =
GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
bundle.putBundle(SearchResult.DOCUMENT_FIELD, document.getBundle());
bundle.putString(SearchResult.PACKAGE_NAME_FIELD, packageName);
+ bundle.putString(SearchResult.DATABASE_NAME_FIELD, databaseName);
ArrayList<Bundle> matchList = new ArrayList<>();
if (proto.hasSnippet()) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index 073a7f624d89..0d7d3e12aa0e 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -25,6 +25,7 @@ import com.google.android.icing.proto.ResultSpecProto;
import com.google.android.icing.proto.ScoringSpecProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.TermMatchType;
+import com.google.android.icing.proto.TypePropertyMask;
import java.util.List;
import java.util.Map;
@@ -71,7 +72,7 @@ public final class SearchSpecToProtoConverter {
Map<String, List<String>> projectionTypePropertyPaths = spec.getProjections();
for (Map.Entry<String, List<String>> e : projectionTypePropertyPaths.entrySet()) {
builder.addTypePropertyMasks(
- ResultSpecProto.TypePropertyMask.newBuilder()
+ TypePropertyMask.newBuilder()
.setSchemaType(e.getKey())
.addAllPaths(e.getValue()));
}
@@ -107,8 +108,7 @@ public final class SearchSpecToProtoConverter {
case SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP:
return ScoringSpecProto.RankingStrategy.Code.CREATION_TIMESTAMP;
case SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE:
- return ScoringSpecProto.RankingStrategy.Code
- .RELEVANCE_SCORE_NONFUNCTIONAL_PLACEHOLDER;
+ return ScoringSpecProto.RankingStrategy.Code.RELEVANCE_SCORE;
default:
throw new IllegalArgumentException(
"Invalid result ranking strategy: " + rankingStrategyCode);
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index f8e5a8aba17b..9be30495eb90 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I6745091e5cb97d69ce2e5f85d3d15c073e7e3ef7
+Icd58005ad659b6b3d03f683f8954939175324685
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 6859747286a8..39ca6871ba09 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -62,6 +62,7 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
@NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
mGlobalSearchSession = Preconditions.checkNotNull(session);
mExecutor = Preconditions.checkNotNull(executor);
+
}
@NonNull
@@ -72,4 +73,9 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim {
mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor);
return new SearchResultsShimImpl(searchResults, mExecutor);
}
+
+ @Override
+ public void close() {
+ mGlobalSearchSession.close();
+ }
}
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index e439c5ab3947..3e819687dae0 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -21,6 +21,7 @@ import android.annotation.SuppressLint;
import com.google.common.util.concurrent.ListenableFuture;
+import java.io.Closeable;
import java.util.Set;
/**
@@ -29,7 +30,7 @@ import java.util.Set;
*
* <p>All implementations of this interface must be thread safe.
*/
-public interface AppSearchSessionShim {
+public interface AppSearchSessionShim extends Closeable {
/**
* Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
@@ -207,11 +208,9 @@ public interface AppSearchSessionShim {
@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/**
- * Closes the SearchSessionImpl to persists all update/delete requests to the disk.
- *
- * @hide
+ * Closes the {@link AppSearchSessionShim} to persist all schema and document updates,
+ * additions, and deletes to disk.
*/
-
- // TODO(b/175637134) when unhide it, extends Closeable and remove this method.
+ @Override
void close();
}
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
index 2d09247dfbc9..cd867a47d407 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
@@ -18,12 +18,14 @@ package android.app.appsearch;
import android.annotation.NonNull;
+import java.io.Closeable;
+
/**
* This class provides global access to the centralized AppSearch index maintained by the system.
*
* <p>Apps can retrieve indexed documents through the query API.
*/
-public interface GlobalSearchSessionShim {
+public interface GlobalSearchSessionShim extends Closeable {
/**
* Searches across all documents in the storage based on a given query string.
*
@@ -65,4 +67,8 @@ public interface GlobalSearchSessionShim {
*/
@NonNull
SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+
+ /** Closes the {@link GlobalSearchSessionShim}. */
+ @Override
+ void close();
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/PowerAllowlistInternal.java b/apex/jobscheduler/framework/java/com/android/server/PowerAllowlistInternal.java
new file mode 100644
index 000000000000..b5d1838b2277
--- /dev/null
+++ b/apex/jobscheduler/framework/java/com/android/server/PowerAllowlistInternal.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+
+public interface PowerAllowlistInternal {
+ /**
+ * Listener to be notified when the temporary allowlist changes.
+ */
+ interface TempAllowlistChangeListener {
+ void onAppAdded(int uid);
+ void onAppRemoved(int uid);
+ }
+
+ /**
+ * Registers a listener that will be notified when the temp allowlist changes.
+ */
+ void registerTempAllowlistChangeListener(@NonNull TempAllowlistChangeListener listener);
+
+ /**
+ * Unregisters a registered stationary listener from being notified when the temp allowlist
+ * changes.
+ */
+ void unregisterTempAllowlistChangeListener(@NonNull TempAllowlistChangeListener listener);
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 99892527721a..4b98c32db608 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -558,6 +559,9 @@ public class DeviceIdleController extends SystemService
private final ArraySet<DeviceIdleInternal.StationaryListener> mStationaryListeners =
new ArraySet<>();
+ private final ArraySet<PowerAllowlistInternal.TempAllowlistChangeListener>
+ mTempAllowlistChangeListeners = new ArraySet<>();
+
private static final int EVENT_NULL = 0;
private static final int EVENT_NORMAL = 1;
private static final int EVENT_LIGHT_IDLE = 2;
@@ -741,6 +745,20 @@ public class DeviceIdleController extends SystemService
}
}
+ private void registerTempAllowlistChangeListener(
+ @NonNull PowerAllowlistInternal.TempAllowlistChangeListener listener) {
+ synchronized (this) {
+ mTempAllowlistChangeListeners.add(listener);
+ }
+ }
+
+ private void unregisterTempAllowlistChangeListener(
+ @NonNull PowerAllowlistInternal.TempAllowlistChangeListener listener) {
+ synchronized (this) {
+ mTempAllowlistChangeListeners.remove(listener);
+ }
+ }
+
@VisibleForTesting
final class MotionListener extends TriggerEventListener
implements SensorEventListener {
@@ -1508,12 +1526,13 @@ public class DeviceIdleController extends SystemService
@VisibleForTesting
static final int MSG_REPORT_STATIONARY_STATUS = 7;
private static final int MSG_FINISH_IDLE_OP = 8;
- private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
+ private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS = 9;
private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
@VisibleForTesting
static final int MSG_UPDATE_PRE_IDLE_TIMEOUT_FACTOR = 11;
@VisibleForTesting
static final int MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR = 12;
+ private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 13;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -1606,14 +1625,31 @@ public class DeviceIdleController extends SystemService
} break;
case MSG_TEMP_APP_WHITELIST_TIMEOUT: {
// TODO: What is keeping the device awake at this point? Does it need to be?
- int appId = msg.arg1;
- checkTempAppWhitelistTimeout(appId);
+ int uid = msg.arg1;
+ checkTempAppWhitelistTimeout(uid);
} break;
case MSG_FINISH_IDLE_OP: {
// mActiveIdleWakeLock is held at this point
decActiveIdleOps();
} break;
case MSG_REPORT_TEMP_APP_WHITELIST_CHANGED: {
+ final int uid = msg.arg1;
+ final boolean added = (msg.arg2 == 1);
+ PowerAllowlistInternal.TempAllowlistChangeListener[] listeners;
+ synchronized (DeviceIdleController.this) {
+ listeners = mTempAllowlistChangeListeners.toArray(
+ new PowerAllowlistInternal.TempAllowlistChangeListener[
+ mTempAllowlistChangeListeners.size()]);
+ }
+ for (PowerAllowlistInternal.TempAllowlistChangeListener listener : listeners) {
+ if (added) {
+ listener.onAppAdded(uid);
+ } else {
+ listener.onAppRemoved(uid);
+ }
+ }
+ } break;
+ case MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS: {
final int appId = msg.arg1;
final boolean added = (msg.arg2 == 1);
mNetworkPolicyManagerInternal.onTempPowerSaveWhitelistChange(appId, added);
@@ -1960,6 +1996,21 @@ public class DeviceIdleController extends SystemService
}
}
+ private class LocalPowerAllowlistService implements PowerAllowlistInternal {
+
+ @Override
+ public void registerTempAllowlistChangeListener(
+ @NonNull TempAllowlistChangeListener listener) {
+ DeviceIdleController.this.registerTempAllowlistChangeListener(listener);
+ }
+
+ @Override
+ public void unregisterTempAllowlistChangeListener(
+ @NonNull TempAllowlistChangeListener listener) {
+ DeviceIdleController.this.unregisterTempAllowlistChangeListener(listener);
+ }
+ }
+
static class Injector {
private final Context mContext;
private ConnectivityManager mConnectivityManager;
@@ -2164,6 +2215,7 @@ public class DeviceIdleController extends SystemService
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
mLocalService = new LocalService();
publishLocalService(DeviceIdleInternal.class, mLocalService);
+ publishLocalService(PowerAllowlistInternal.class, new LocalPowerAllowlistService());
}
@Override
@@ -2708,15 +2760,18 @@ public class DeviceIdleController extends SystemService
reason, uid);
} catch (RemoteException e) {
}
- postTempActiveTimeoutMessage(appId, duration);
+ postTempActiveTimeoutMessage(uid, duration);
updateTempWhitelistAppIdsLocked(appId, true);
if (sync) {
informWhitelistChanged = true;
} else {
- mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED, appId, 1)
+ // NPMS needs to update its state synchronously in certain situations so we
+ // can't have it use the TempAllowlistChangeListener path right now.
+ // TODO: see if there's a way to simplify/consolidate
+ mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS, appId, 1)
.sendToTarget();
}
- reportTempWhitelistChangedLocked();
+ reportTempWhitelistChangedLocked(uid, true);
}
}
if (informWhitelistChanged) {
@@ -2737,7 +2792,8 @@ public class DeviceIdleController extends SystemService
}
}
- private void removePowerSaveTempWhitelistAppDirectInternal(int appId) {
+ private void removePowerSaveTempWhitelistAppDirectInternal(int uid) {
+ final int appId = UserHandle.getAppId(uid);
synchronized (this) {
final int idx = mTempWhitelistAppIdEndTimes.indexOfKey(appId);
if (idx < 0) {
@@ -2746,22 +2802,23 @@ public class DeviceIdleController extends SystemService
}
final String reason = mTempWhitelistAppIdEndTimes.valueAt(idx).second;
mTempWhitelistAppIdEndTimes.removeAt(idx);
- onAppRemovedFromTempWhitelistLocked(appId, reason);
+ onAppRemovedFromTempWhitelistLocked(uid, reason);
}
}
- private void postTempActiveTimeoutMessage(int appId, long delay) {
+ private void postTempActiveTimeoutMessage(int uid, long delay) {
if (DEBUG) {
- Slog.d(TAG, "postTempActiveTimeoutMessage: appId=" + appId + ", delay=" + delay);
+ Slog.d(TAG, "postTempActiveTimeoutMessage: uid=" + uid + ", delay=" + delay);
}
mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_TEMP_APP_WHITELIST_TIMEOUT, appId, 0), delay);
+ mHandler.obtainMessage(MSG_TEMP_APP_WHITELIST_TIMEOUT, uid, 0), delay);
}
- void checkTempAppWhitelistTimeout(int appId) {
+ void checkTempAppWhitelistTimeout(int uid) {
final long timeNow = SystemClock.elapsedRealtime();
+ final int appId = UserHandle.getAppId(uid);
if (DEBUG) {
- Slog.d(TAG, "checkTempAppWhitelistTimeout: appId=" + appId + ", timeNow=" + timeNow);
+ Slog.d(TAG, "checkTempAppWhitelistTimeout: uid=" + uid + ", timeNow=" + timeNow);
}
synchronized (this) {
Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.get(appId);
@@ -2771,26 +2828,27 @@ public class DeviceIdleController extends SystemService
}
if (timeNow >= entry.first.value) {
mTempWhitelistAppIdEndTimes.delete(appId);
- onAppRemovedFromTempWhitelistLocked(appId, entry.second);
+ onAppRemovedFromTempWhitelistLocked(uid, entry.second);
} else {
// Need more time
if (DEBUG) {
Slog.d(TAG, "Time to remove AppId " + appId + ": " + entry.first.value);
}
- postTempActiveTimeoutMessage(appId, entry.first.value - timeNow);
+ postTempActiveTimeoutMessage(uid, entry.first.value - timeNow);
}
}
}
@GuardedBy("this")
- private void onAppRemovedFromTempWhitelistLocked(int appId, String reason) {
+ private void onAppRemovedFromTempWhitelistLocked(int uid, String reason) {
+ final int appId = UserHandle.getAppId(uid);
if (DEBUG) {
- Slog.d(TAG, "Removing appId " + appId + " from temp whitelist");
+ Slog.d(TAG, "Removing uid " + uid + " from temp whitelist");
}
updateTempWhitelistAppIdsLocked(appId, false);
- mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED, appId, 0)
+ mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS, appId, 0)
.sendToTarget();
- reportTempWhitelistChangedLocked();
+ reportTempWhitelistChangedLocked(uid, false);
try {
mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_TEMP_WHITELIST_FINISH,
reason, appId);
@@ -3843,7 +3901,9 @@ public class DeviceIdleController extends SystemService
getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
}
- private void reportTempWhitelistChangedLocked() {
+ private void reportTempWhitelistChangedLocked(final int uid, final boolean added) {
+ mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED, uid, added ? 1 : 0)
+ .sendToTarget();
Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index a02f8de5e292..736ee18b5444 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -60,6 +60,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
@@ -68,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
+import com.android.server.PowerAllowlistInternal;
import com.android.server.job.ConstantsProto;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobServiceContext;
@@ -343,6 +345,15 @@ public final class QuotaController extends StateController {
*/
private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
+ /** Current set of UIDs on the temp allowlist. */
+ private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();
+
+ /**
+ * Mapping of app IDs to the when their temp allowlist grace period ends (in the elapsed
+ * realtime timebase).
+ */
+ private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
+
private final ActivityManagerInternal mActivityManagerInternal;
private final AlarmManager mAlarmManager;
private final ChargingTracker mChargeTracker;
@@ -538,6 +549,9 @@ public final class QuotaController extends StateController {
*/
private long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS;
+ private long mEJTempAllowlistGracePeriodMs =
+ QcConstants.DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS;
+
/** The package verifier app. */
@Nullable
private String mPackageVerifier;
@@ -562,6 +576,9 @@ public final class QuotaController extends StateController {
* userId will the first arg.
*/
private static final int MSG_PROCESS_USAGE_EVENT = 5;
+ /** A UID's free quota grace period has ended. */
+ @VisibleForTesting
+ static final int MSG_END_GRACE_PERIOD = 6;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -586,6 +603,9 @@ public final class QuotaController extends StateController {
UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class);
usmi.registerListener(new UsageEventTracker());
+ PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class);
+ pai.registerTempAllowlistChangeListener(new TempAllowlistTracker());
+
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE,
@@ -693,6 +713,8 @@ public final class QuotaController extends StateController {
clearAppStatsLocked(UserHandle.getUserId(uid), packageName);
mForegroundUids.delete(uid);
mUidToPackageCache.remove(uid);
+ mTempAllowlistCache.delete(uid);
+ mTempAllowlistGraceCache.delete(uid);
}
@Override
@@ -1988,10 +2010,15 @@ public final class QuotaController extends StateController {
}
private boolean shouldTrackLocked() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
- mPkg.userId, sElapsedRealtimeClock.millis());
+ mPkg.userId, nowElapsed);
+ final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(mUid);
+ final boolean hasTempAllowlistExemption = !mRegularJobTimer
+ && (mTempAllowlistCache.get(mUid)
+ || nowElapsed < tempAllowlistGracePeriodEndElapsed);
return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging())
- && !mForegroundUids.get(mUid);
+ && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption;
}
void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
@@ -2265,6 +2292,38 @@ public final class QuotaController extends StateController {
}
}
+ final class TempAllowlistTracker implements PowerAllowlistInternal.TempAllowlistChangeListener {
+
+ @Override
+ public void onAppAdded(int uid) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ mTempAllowlistCache.put(uid, true);
+ final ArraySet<String> packages = getPackagesForUid(uid);
+ if (packages != null) {
+ final int userId = UserHandle.getUserId(uid);
+ for (int i = packages.size() - 1; i >= 0; --i) {
+ Timer t = mEJPkgTimers.get(userId, packages.valueAt(i));
+ if (t != null) {
+ t.onStateChangedLocked(nowElapsed, true);
+ }
+ }
+ if (maybeUpdateConstraintForUidLocked(uid)) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ }
+
+ @Override
+ public void onAppRemoved(int uid) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long endElapsed = nowElapsed + mEJTempAllowlistGracePeriodMs;
+ mTempAllowlistCache.delete(uid);
+ mTempAllowlistGraceCache.put(uid, endElapsed);
+ Message msg = mHandler.obtainMessage(MSG_END_GRACE_PERIOD, uid, 0);
+ mHandler.sendMessageDelayed(msg, mEJTempAllowlistGracePeriodMs);
+ }
+ }
+
private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
public boolean test(TimingSession ts) {
@@ -2291,6 +2350,26 @@ public final class QuotaController extends StateController {
// getRemainingEJExecutionTimeLocked().
}
+ @Nullable
+ private ArraySet<String> getPackagesForUid(final int uid) {
+ ArraySet<String> packages = mUidToPackageCache.get(uid);
+ if (packages == null) {
+ try {
+ String[] pkgs = AppGlobals.getPackageManager()
+ .getPackagesForUid(uid);
+ if (pkgs != null) {
+ for (String pkg : pkgs) {
+ mUidToPackageCache.add(uid, pkg);
+ }
+ packages = mUidToPackageCache.get(uid);
+ }
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ }
+ }
+ return packages;
+ }
+
private class QcHandler extends Handler {
private boolean mIsProcessing;
@@ -2396,21 +2475,7 @@ public final class QuotaController extends StateController {
// Update Timers first.
if (mPkgTimers.indexOfKey(userId) >= 0
|| mEJPkgTimers.indexOfKey(userId) >= 0) {
- ArraySet<String> packages = mUidToPackageCache.get(uid);
- if (packages == null) {
- try {
- String[] pkgs = AppGlobals.getPackageManager()
- .getPackagesForUid(uid);
- if (pkgs != null) {
- for (String pkg : pkgs) {
- mUidToPackageCache.add(uid, pkg);
- }
- packages = mUidToPackageCache.get(uid);
- }
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Failed to get package list", e);
- }
- }
+ final ArraySet<String> packages = getPackagesForUid(uid);
if (packages != null) {
for (int i = packages.size() - 1; i >= 0; --i) {
Timer t = mEJPkgTimers.get(userId, packages.valueAt(i));
@@ -2466,6 +2531,37 @@ public final class QuotaController extends StateController {
break;
}
+ case MSG_END_GRACE_PERIOD: {
+ final int uid = msg.arg1;
+ synchronized (mLock) {
+ if (mTempAllowlistCache.get(uid)) {
+ // App added back to the temp allowlist during the grace period.
+ if (DEBUG) {
+ Slog.d(TAG, uid + " is still allowed");
+ }
+ break;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, uid + " is now out of grace period");
+ }
+ final ArraySet<String> packages = getPackagesForUid(uid);
+ if (packages != null) {
+ final int userId = UserHandle.getUserId(uid);
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ for (int i = packages.size() - 1; i >= 0; --i) {
+ Timer t = mEJPkgTimers.get(userId, packages.valueAt(i));
+ if (t != null) {
+ t.onStateChangedLocked(nowElapsed, false);
+ }
+ }
+ if (maybeUpdateConstraintForUidLocked(uid)) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ }
+
+ break;
+ }
}
}
@@ -2784,6 +2880,9 @@ public final class QuotaController extends StateController {
@VisibleForTesting
static final String KEY_EJ_REWARD_NOTIFICATION_SEEN_MS =
QC_CONSTANT_PREFIX + "ej_reward_notification_seen_ms";
+ @VisibleForTesting
+ static final String KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS =
+ QC_CONSTANT_PREFIX + "ej_temp_allowlist_grace_period_ms";
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
10 * 60 * 1000L; // 10 minutes
@@ -2836,6 +2935,7 @@ public final class QuotaController extends StateController {
private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0;
+ private static final long DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS = 3 * MINUTE_IN_MILLIS;
/** How much time each app will have to run jobs within their standby bucket window. */
public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -3063,6 +3163,12 @@ public final class QuotaController extends StateController {
*/
public long EJ_REWARD_NOTIFICATION_SEEN_MS = DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS;
+ /**
+ * How much additional grace period to add to the end of an app's temp allowlist
+ * duration.
+ */
+ public long EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS = DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS;
+
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
switch (key) {
@@ -3245,22 +3351,25 @@ public final class QuotaController extends StateController {
properties.getLong(key, DEFAULT_EJ_REWARD_INTERACTION_MS);
// Limit interaction reward to be in the range [5 seconds, 15 minutes] per
// event.
- long newInteractionReward = Math.min(15 * MINUTE_IN_MILLIS,
+ mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS,
Math.max(5 * SECOND_IN_MILLIS, EJ_REWARD_INTERACTION_MS));
- if (mEJRewardInteractionMs != newInteractionReward) {
- mEJRewardInteractionMs = newInteractionReward;
- }
break;
case KEY_EJ_REWARD_NOTIFICATION_SEEN_MS:
// We don't need to re-evaluate execution stats or constraint status for this.
EJ_REWARD_NOTIFICATION_SEEN_MS =
properties.getLong(key, DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS);
// Limit notification seen reward to be in the range [0, 5] minutes per event.
- long newNotiSeenReward = Math.min(5 * MINUTE_IN_MILLIS,
+ mEJRewardNotificationSeenMs = Math.min(5 * MINUTE_IN_MILLIS,
Math.max(0, EJ_REWARD_NOTIFICATION_SEEN_MS));
- if (mEJRewardNotificationSeenMs != newNotiSeenReward) {
- mEJRewardNotificationSeenMs = newNotiSeenReward;
- }
+ break;
+ case KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS:
+ // We don't need to re-evaluate execution stats or constraint status for this.
+ EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS =
+ properties.getLong(key, DEFAULT_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS);
+ // Limit grace period to be in the range [0 minutes, 1 hour].
+ mEJTempAllowlistGracePeriodMs = Math.min(HOUR_IN_MILLIS,
+ Math.max(0, EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS));
+ break;
}
}
@@ -3522,6 +3631,8 @@ public final class QuotaController extends StateController {
pw.print(KEY_EJ_REWARD_TOP_APP_MS, EJ_REWARD_TOP_APP_MS).println();
pw.print(KEY_EJ_REWARD_INTERACTION_MS, EJ_REWARD_INTERACTION_MS).println();
pw.print(KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, EJ_REWARD_NOTIFICATION_SEEN_MS).println();
+ pw.print(KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS,
+ EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS).println();
pw.decreaseIndent();
}
@@ -3668,6 +3779,10 @@ public final class QuotaController extends StateController {
return mEJRewardTopAppMs;
}
+ @VisibleForTesting
+ long getEJTempAllowlistGracePeriodMs() {
+ return mEJTempAllowlistGracePeriodMs;
+ }
@VisibleForTesting
@Nullable
@@ -3757,6 +3872,12 @@ public final class QuotaController extends StateController {
pw.decreaseIndent();
pw.println();
+ pw.print("Cached temp allowlist: ");
+ pw.println(mTempAllowlistCache.toString());
+ pw.print("Cached temp allowlist grace period: ");
+ pw.println(mTempAllowlistGraceCache.toString());
+
+ pw.println();
mTrackedJobs.forEach((jobs) -> {
for (int j = 0; j < jobs.size(); j++) {
final JobStatus js = jobs.valueAt(j);
diff --git a/core/api/current.txt b/core/api/current.txt
index 1951d99b718d..113591e5e97b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8163,6 +8163,7 @@ package android.app.usage {
method public long getAppBytes();
method public long getCacheBytes();
method public long getDataBytes();
+ method public long getExternalCacheBytes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e885fd38c2ff..9e83136b978d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -254,6 +254,7 @@ package android.app {
method public boolean isImportanceLockedByOEM();
method public void lockFields(int);
method public void setDeleted(boolean);
+ method public void setDeletedTimeMs(long);
method public void setDemoted(boolean);
method public void setFgServiceShown(boolean);
method public void setImportanceLockedByCriticalDeviceFunction(boolean);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index b1a8f9b0ba33..323af8211d76 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -32,6 +32,7 @@ import android.os.Parcelable;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
@@ -111,6 +112,7 @@ public final class NotificationChannel implements Parcelable {
private static final String ATT_CONVERSATION_ID = "conv_id";
private static final String ATT_IMP_CONVERSATION = "imp_conv";
private static final String ATT_DEMOTE = "dem";
+ private static final String ATT_DELETED_TIME_MS = "del_time";
private static final String DELIMITER = ",";
/**
@@ -183,6 +185,7 @@ public final class NotificationChannel implements Parcelable {
NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_DELETED = false;
private static final boolean DEFAULT_SHOW_BADGE = true;
+ private static final long DEFAULT_DELETION_TIME_MS = -1;
@UnsupportedAppUsage
private String mId;
@@ -214,6 +217,7 @@ public final class NotificationChannel implements Parcelable {
private String mConversationId = null;
private boolean mDemoted = false;
private boolean mImportantConvo = false;
+ private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
/**
* Creates a notification channel.
@@ -282,6 +286,7 @@ public final class NotificationChannel implements Parcelable {
mConversationId = in.readString();
mDemoted = in.readBoolean();
mImportantConvo = in.readBoolean();
+ mDeletedTime = in.readLong();
}
@Override
@@ -341,6 +346,7 @@ public final class NotificationChannel implements Parcelable {
dest.writeString(mConversationId);
dest.writeBoolean(mDemoted);
dest.writeBoolean(mImportantConvo);
+ dest.writeLong(mDeletedTime);
}
/**
@@ -378,6 +384,14 @@ public final class NotificationChannel implements Parcelable {
* @hide
*/
@TestApi
+ public void setDeletedTimeMs(long time) {
+ mDeletedTime = time;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
public void setImportantConversation(boolean importantConvo) {
mImportantConvo = importantConvo;
}
@@ -766,6 +780,13 @@ public final class NotificationChannel implements Parcelable {
/**
* @hide
*/
+ public long getDeletedTimeMs() {
+ return mDeletedTime;
+ }
+
+ /**
+ * @hide
+ */
@SystemApi
public int getUserLockedFields() {
return mUserLockedFields;
@@ -906,6 +927,8 @@ public final class NotificationChannel implements Parcelable {
enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
setDeleted(safeBool(parser, ATT_DELETED, false));
+ setDeletedTimeMs(XmlUtils.readLongAttribute(
+ parser, ATT_DELETED_TIME_MS, DEFAULT_DELETION_TIME_MS));
setGroup(parser.getAttributeValue(null, ATT_GROUP));
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
@@ -1024,6 +1047,9 @@ public final class NotificationChannel implements Parcelable {
if (isDeleted()) {
out.attributeBoolean(null, ATT_DELETED, isDeleted());
}
+ if (getDeletedTimeMs() >= 0) {
+ out.attributeLong(null, ATT_DELETED_TIME_MS, getDeletedTimeMs());
+ }
if (getGroup() != null) {
out.attribute(null, ATT_GROUP, getGroup());
}
@@ -1091,6 +1117,7 @@ public final class NotificationChannel implements Parcelable {
record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
record.put(ATT_DELETED, Boolean.toString(isDeleted()));
+ record.put(ATT_DELETED_TIME_MS, Long.toString(getDeletedTimeMs()));
record.put(ATT_GROUP, getGroup());
record.put(ATT_BLOCKABLE_SYSTEM, isBlockable());
record.put(ATT_ALLOW_BUBBLE, getAllowBubbles());
@@ -1182,6 +1209,7 @@ public final class NotificationChannel implements Parcelable {
&& mVibrationEnabled == that.mVibrationEnabled
&& mShowBadge == that.mShowBadge
&& isDeleted() == that.isDeleted()
+ && getDeletedTimeMs() == that.getDeletedTimeMs()
&& isBlockable() == that.isBlockable()
&& mAllowBubbles == that.mAllowBubbles
&& Objects.equals(getId(), that.getId())
@@ -1205,8 +1233,8 @@ public final class NotificationChannel implements Parcelable {
int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd,
getLockscreenVisibility(), getSound(), mLights, getLightColor(),
getUserLockedFields(),
- isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
- getAudioAttributes(), isBlockable(), mAllowBubbles,
+ isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
+ getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
mParentId, mConversationId, mDemoted, mImportantConvo);
result = 31 * result + Arrays.hashCode(mVibration);
@@ -1247,6 +1275,7 @@ public final class NotificationChannel implements Parcelable {
+ ", mVibrationEnabled=" + mVibrationEnabled
+ ", mShowBadge=" + mShowBadge
+ ", mDeleted=" + mDeleted
+ + ", mDeletedTimeMs=" + mDeletedTime
+ ", mGroup='" + mGroup + '\''
+ ", mAudioAttributes=" + mAudioAttributes
+ ", mBlockableSystem=" + mBlockableSystem
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 4757557d0fd5..8d25d7bfb11b 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -32,6 +32,7 @@ public final class StorageStats implements Parcelable {
/** {@hide} */ public long codeBytes;
/** {@hide} */ public long dataBytes;
/** {@hide} */ public long cacheBytes;
+ /** {@hide} */ public long externalCacheBytes;
/**
* Return the size of app. This includes {@code APK} files, optimized
@@ -77,6 +78,17 @@ public final class StorageStats implements Parcelable {
return cacheBytes;
}
+ /**
+ * Return the size of all cached data in the primary external/shared storage.
+ * This includes files stored under
+ * {@link Context#getExternalCacheDir()}.
+ * <p>
+ * Cached data is isolated for each user on a multiuser device.
+ */
+ public @BytesLong long getExternalCacheBytes() {
+ return externalCacheBytes;
+ }
+
/** {@hide} */
public StorageStats() {
}
@@ -86,6 +98,7 @@ public final class StorageStats implements Parcelable {
this.codeBytes = in.readLong();
this.dataBytes = in.readLong();
this.cacheBytes = in.readLong();
+ this.externalCacheBytes = in.readLong();
}
@Override
@@ -98,6 +111,7 @@ public final class StorageStats implements Parcelable {
dest.writeLong(codeBytes);
dest.writeLong(dataBytes);
dest.writeLong(cacheBytes);
+ dest.writeLong(externalCacheBytes);
}
public static final @android.annotation.NonNull Creator<StorageStats> CREATOR = new Creator<StorageStats>() {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 26e3bb27a788..7ac57b5e0421 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -232,7 +232,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private final Matrix mTmpMatrix = new Matrix();
SurfaceControlViewHost.SurfacePackage mSurfacePackage;
- private final boolean mUseBlastSync = false;
+ private final boolean mUseBlastSync = true;
/**
* Returns {@code true} if buffers should be submitted via blast
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f859e54cb53d..272e130920f3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4617,4 +4617,9 @@
If omitted, image editing will not be offered via Chooser.
This name is in the ComponentName flattened format (package/class) [DO NOT TRANSLATE] -->
<string name="config_systemImageEditor" translatable="false"></string>
+
+ <!-- Whether to force WindowOrientationListener to keep listening to its sensor, even when
+ dreaming. This allows the AoD to rotate on devices without a wake device_orientation
+ sensor. Note that this flag should only be enabled for development/testing use. -->
+ <bool name="config_forceOrientationListenerEnabledWhileDreaming">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1d7561247589..0c86905efbce 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4148,4 +4148,6 @@
<java-symbol type="bool" name="config_attachNavBarToAppDuringTransition" />
<java-symbol type="bool" name="config_enableBackSound" />
+
+ <java-symbol type="bool" name="config_forceOrientationListenerEnabledWhileDreaming" />
</resources>
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index 696aa11618ff..912db1e835dc 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -1,4 +1,3 @@
per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
-per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
diff --git a/core/tests/coretests/src/android/content/pm/OWNERS b/core/tests/coretests/src/android/content/pm/OWNERS
new file mode 100644
index 000000000000..711f5f012b8b
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/OWNERS
@@ -0,0 +1,2 @@
+per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 764bc4c7ff0a..f055c6e0fa44 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -76,6 +76,11 @@ static bool requires_matrix_scaling(bool swapWidthHeight, const SkISize& decodeS
|| (!swapWidthHeight && decodeSize != targetSize);
}
+SkISize ImageDecoder::getSampledDimensions(int sampleSize) const {
+ auto size = mCodec->getSampledDimensions(sampleSize);
+ return swapWidthHeight() ? swapped(size) : size;
+}
+
bool ImageDecoder::setTargetSize(int width, int height) {
if (width <= 0 || height <= 0) {
return false;
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index 1b309bcc7bf0..cbfffd5e9291 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -38,6 +38,7 @@ public:
sk_sp<SkPngChunkReader> peeker = nullptr);
~ImageDecoder();
+ SkISize getSampledDimensions(int sampleSize) const;
bool setTargetSize(int width, int height);
bool setCropRect(const SkIRect*);
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index 96e912fd9f26..ad7741b61e9f 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -465,7 +465,7 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jint sampleSize) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
+ SkISize size = decoder->getSampledDimensions(sampleSize);
return env->NewObject(gSize_class, gSize_constructorMethodID, size.width(), size.height());
}
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 9e91dff9e28e..80c014fb99a7 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -304,10 +304,13 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
sk_sp<SkImage> image = bitmap.makeImage();
+ // HWUI always draws 9-patches with linear filtering, regardless of the Paint.
+ const SkFilterMode filter = SkFilterMode::kLinear;
+
applyLooper(get_looper(paint), filterBitmap(paint), [&](SkScalar x, SkScalar y,
const SkPaint* p) {
- mRecorder.drawImageLattice(image, lattice, dst.makeOffset(x, y), Paint_to_filter(p),
- p, bitmap.palette());
+ mRecorder.drawImageLattice(image, lattice, dst.makeOffset(x, y), filter, p,
+ bitmap.palette());
});
if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) {
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index eab5f4143968..385e455e3e1f 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -353,7 +353,7 @@ int AImageDecoder_computeSampledSize(const AImageDecoder* decoder, int sampleSiz
return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
}
- SkISize size = toDecoder(decoder)->mCodec->getSampledDimensions(sampleSize);
+ SkISize size = toDecoder(decoder)->getSampledDimensions(sampleSize);
*width = size.width();
*height = size.height();
return ANDROID_IMAGE_DECODER_SUCCESS;
diff --git a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml b/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml
index 3d6ca7acda87..0e308d2f5bfb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml
@@ -1,18 +1,3 @@
-<!--
- 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.
--->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -20,9 +5,8 @@
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10c0.34,0 0.68,-0.02 1.01,-0.05V20h-1v-0.04c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96H13v-2H9.66c-0.09,-0.66 -0.16,-1.32 -0.16,-2s0.07,-1.35 0.16,-2H21.8C20.87,5.44 16.83,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56C16.43,5.07 17.96,6.35 18.92,8zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82C10.52,6.57 11.17,5.24 12,4.04zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2s0.06,1.34 0.14,2H4.26zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56C7.57,18.93 6.04,17.66 5.08,16zM8.03,8H5.08c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8z"
- android:fillAlpha="0.3"/>
+ android:pathData="M20.17,14.84l-3.26,-0.65c-0.33,-0.07 -0.67,0.04 -0.9,0.27l-2.62,2.62c-2.75,-1.49 -5.01,-3.75 -6.5,-6.5l2.62,-2.62c0.24,-0.24 0.34,-0.58 0.27,-0.9L9.13,3.8C9.04,3.34 8.63,3 8.15,3H4C3.44,3 2.97,3.47 3,4.03c0.17,2.91 1.04,5.63 2.43,8.01c1.57,2.69 3.81,4.93 6.5,6.5c2.38,1.39 5.1,2.26 8.01,2.43c0.56,0.03 1.03,-0.44 1.03,-1v-4.15C20.97,15.34 20.64,14.93 20.17,14.84z"/>
<path
android:fillColor="#FF000000"
- android:pathData="M22,19.3v-0.9l-3.37,-2.25v-2.47C18.63,13.3 18.35,13 18,13s-0.63,0.3 -0.63,0.68v2.47L14,18.4v0.9l3.37,-1.12v2.48l-0.84,0.68V22L18,21.55L19.47,22v-0.67l-0.84,-0.68v-2.48L22,19.3z"/>
+ android:pathData="M22,3.41L20.59,2L18.5,4.09L16.41,2L15,3.41l2.09,2.09L15,7.59L16.41,9l2.09,-2.08L20.59,9L22,7.59L19.92,5.5L22,3.41z"/>
</vector> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 8223d9717208..a03fc136da61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
@@ -1785,6 +1786,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
doLongClickCallback(x, y, menuItem);
}
+ /**
+ * Perform a smart action which triggers a longpress (expose guts).
+ * Based on the semanticAction passed, may update the state of the guts view.
+ * @param semanticAction associated with this smart action click
+ */
+ public void doSmartActionClick(int x, int y, int semanticAction) {
+ createMenu();
+ NotificationMenuRowPlugin provider = getProvider();
+ MenuItem menuItem = null;
+ if (provider != null) {
+ menuItem = provider.getLongpressMenuItem(mContext);
+ }
+ if (SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == semanticAction
+ && menuItem.getGutsView() instanceof NotificationConversationInfo) {
+ NotificationConversationInfo info =
+ (NotificationConversationInfo) menuItem.getGutsView();
+ info.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
+ }
+ doLongClickCallback(x, y, menuItem);
+ }
+
private void doLongClickCallback(int x, int y, MenuItem menuItem) {
if (mLongPressListener != null && menuItem != null) {
mLongPressListener.onLongPress(this, x, y, menuItem);
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 1a2550b81878..adeba9078c52 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
@@ -206,6 +206,7 @@ public class NotificationConversationInfo extends LinearLayout implements
}
public void bindNotification(
+ @Action int selectedAction,
ShortcutManager shortcutManager,
PackageManager pm,
INotificationManager iNotificationManager,
@@ -224,7 +225,8 @@ public class NotificationConversationInfo extends LinearLayout implements
@Background Handler bgHandler,
OnConversationSettingsClickListener onConversationSettingsClickListener,
Optional<BubblesManager> bubblesManagerOptional) {
- mSelectedAction = -1;
+ mPressedApply = false;
+ mSelectedAction = selectedAction;
mINotificationManager = iNotificationManager;
mOnUserInteractionCallback = onUserInteractionCallback;
mPackageName = pkg;
@@ -297,7 +299,8 @@ public class NotificationConversationInfo extends LinearLayout implements
settingsButton.setOnClickListener(getSettingsOnClickListener());
settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
- updateToggleActions(getSelectedAction(), false);
+ updateToggleActions(mSelectedAction == -1 ? getPriority() : mSelectedAction,
+ false);
}
private void bindHeader() {
@@ -406,7 +409,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Override
public void onFinishedClosing() {
- // TODO: do we need to do anything here?
+ mSelectedAction = -1;
}
@Override
@@ -487,7 +490,7 @@ public class NotificationConversationInfo extends LinearLayout implements
throw new IllegalArgumentException("Unrecognized behavior: " + mSelectedAction);
}
- boolean isAChange = getSelectedAction() != selectedAction;
+ boolean isAChange = getPriority() != selectedAction;
TextView done = findViewById(R.id.done);
done.setText(isAChange
? R.string.inline_ok_button
@@ -498,6 +501,10 @@ public class NotificationConversationInfo extends LinearLayout implements
}
int getSelectedAction() {
+ return mSelectedAction;
+ }
+
+ private int getPriority() {
if (mNotificationChannel.getImportance() <= IMPORTANCE_LOW
&& mNotificationChannel.getImportance() > IMPORTANCE_UNSPECIFIED) {
return ACTION_MUTE;
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 2fd17a587612..6a873b678a93 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
@@ -474,6 +474,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
R.dimen.notification_guts_conversation_icon_size));
notificationInfoView.bindNotification(
+ notificationInfoView.getSelectedAction(),
mShortcutManager,
pmUser,
mNotificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 24b820669beb..2f66508dafee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -467,7 +467,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
// 1. The first valid voice state has been received
// 2. The voice state has been changed and either the last or current state is
// ServiceState.STATE_IN_SERVICE
- if (lastVoiceState != currentVoiceState
+ if (mProviderModel
+ && lastVoiceState != currentVoiceState
&& (lastVoiceState == -1
|| (lastVoiceState == ServiceState.STATE_IN_SERVICE
|| currentVoiceState == ServiceState.STATE_IN_SERVICE))) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
index 6a3a69c0419e..ea803253ea0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import android.app.Notification
+import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
import android.app.PendingIntent
import android.app.RemoteInput
import android.content.Context
@@ -310,11 +311,19 @@ interface SmartActionInflater {
actionIndex: Int,
action: Notification.Action
) =
+ if (smartActions.fromAssistant
+ && SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction) {
+ entry.row.doSmartActionClick(entry.row.x.toInt() / 2,
+ entry.row.y.toInt() / 2, SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY)
+ smartReplyController
+ .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ } else {
activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
smartReplyController
- .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
headsUpManager.removeNotification(entry.key, true /* releaseImmediately */)
}
+ }
}
interface SmartReplyInflater {
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 291b223d72bb..5c37656d2cf1 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
@@ -241,6 +241,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_SetsShortcutIcon() {
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -265,6 +266,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_SetsTextApplicationName() {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -289,6 +291,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_SetsTextChannelName() {
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mLauncherApps,
mMockPackageManager,
@@ -316,6 +319,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setGroup(group.getId());
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -341,6 +345,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_GroupNameHiddenIfNoGroup() {
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -365,6 +370,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_noDelegate() {
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -400,6 +406,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
.setShortcutInfo(mShortcutInfo)
.build();
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -425,6 +432,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_SetsOnClickListenerForSettings() {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -454,6 +462,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -478,6 +487,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -506,6 +516,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportance(IMPORTANCE_LOW);
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -534,6 +545,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -565,6 +577,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -595,6 +608,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -639,6 +653,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportance(IMPORTANCE_LOW);
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -682,6 +697,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -726,6 +742,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -763,6 +780,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportance(9);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -799,6 +817,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -832,11 +851,75 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
}
@Test
+ public void testDefaultSelectedWhenChannelIsDefault() throws Exception {
+ // GIVEN channel importance indicates "Default" priority
+ mConversationChannel.setImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportantConversation(false);
+
+ // WHEN we indicate no selected action
+ mNotificationInfo.bindNotification(
+ -1, // no action selected by default
+ mShortcutManager,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ mBuilderProvider,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager));
+
+ // THEN the selected action is -1, so the selected option is "Default" priority
+ assertEquals(mNotificationInfo.getSelectedAction(), -1);
+ assertTrue(mNotificationInfo.findViewById(R.id.default_behavior).isSelected());
+ }
+
+ @Test
+ public void testFavoriteSelectedWhenChannelIsDefault() throws Exception {
+ // GIVEN channel importance indicates "Default" priority
+ mConversationChannel.setImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportantConversation(false);
+
+ // WHEN we indicate the selected action should be "Favorite"
+ mNotificationInfo.bindNotification(
+ NotificationConversationInfo.ACTION_FAVORITE, // "Favorite" selected by default
+ mShortcutManager,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ null,
+ mIconFactory,
+ mContext,
+ mBuilderProvider,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager));
+
+ // THEN the selected action is "Favorite", so the selected option is "priority" priority
+ assertEquals(mNotificationInfo.getSelectedAction(),
+ NotificationConversationInfo.ACTION_FAVORITE);
+ assertTrue(mNotificationInfo.findViewById(R.id.priority).isSelected());
+ }
+
+ @Test
public void testDefault_andSave() throws Exception {
mConversationChannel.setAllowBubbles(true);
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -873,6 +956,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -909,6 +993,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -944,6 +1029,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -978,6 +1064,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_createsNewChannel() throws Exception {
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -1003,6 +1090,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
mNotificationChannel.setConversationId("", CONVERSATION_ID);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -1038,6 +1126,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
// GIVEN the user is changing conversation settings
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
@@ -1078,6 +1167,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
when(b.build()).thenReturn(controller);
mNotificationInfo.bindNotification(
+ -1,
mShortcutManager,
mMockPackageManager,
mMockINotificationManager,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b587f1b2599d..16874cfb5819 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8302,6 +8302,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized(this) {
mConstants.dump(pw);
mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
+ mOomAdjuster.dumpCacheOomRankerSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -8722,6 +8723,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
mConstants.dump(pw);
mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
+ mOomAdjuster.dumpCacheOomRankerSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java
new file mode 100644
index 000000000000..26cfd62d40ad
--- /dev/null
+++ b/services/core/java/com/android/server/am/CacheOomRanker.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.concurrent.Executor;
+
+/**
+ * Class to re-rank a number of the least recently used processes before they
+ * are assigned oom adjust scores.
+ */
+public class CacheOomRanker {
+ @VisibleForTesting
+ static final String KEY_USE_OOM_RE_RANKING = "use_oom_re_ranking";
+ private static final boolean DEFAULT_USE_OOM_RE_RANKING = false;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank";
+ @VisibleForTesting static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight";
+ @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight";
+ @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight";
+ @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
+
+ private static final Comparator<RankedProcessRecord> SCORED_PROCESS_RECORD_COMPARATOR =
+ new ScoreComparator();
+ private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR =
+ new CacheUseComparator();
+ private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR =
+ new LastRssComparator();
+ private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR =
+ new LastActivityTimeComparator();
+
+ private final Object mPhenotypeFlagLock = new Object();
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING;
+ // Weight to apply to the LRU ordering.
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
+ // Weight to apply to the ordering by number of times the process has been added to the cache.
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
+ // Weight to apply to the ordering by RSS used by the processes.
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
+
+ // Positions to replace in the lru list.
+ @GuardedBy("mPhenotypeFlagLock")
+ private int[] mLruPositions;
+ // Processes to re-rank
+ @GuardedBy("mPhenotypeFlagLock")
+ private RankedProcessRecord[] mScoredProcessRecords;
+
+ private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ synchronized (mPhenotypeFlagLock) {
+ for (String name : properties.getKeyset()) {
+ if (KEY_USE_OOM_RE_RANKING.equals(name)) {
+ updateUseOomReranking();
+ } else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) {
+ updateNumberToReRank();
+ } else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) {
+ updateLruWeight();
+ } else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) {
+ updateUsesWeight();
+ } else if (KEY_OOM_RE_RANKING_RSS_WEIGHT.equals(name)) {
+ updateRssWeight();
+ }
+ }
+ }
+ }
+ };
+
+ /** Load settings from device config and register a listener for changes. */
+ public void init(Executor executor) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ executor, mOnFlagsChangedListener);
+ synchronized (mPhenotypeFlagLock) {
+ updateUseOomReranking();
+ updateNumberToReRank();
+ updateLruWeight();
+ updateUsesWeight();
+ updateRssWeight();
+ }
+ }
+
+ /**
+ * Returns whether oom re-ranking is enabled.
+ */
+ public boolean useOomReranking() {
+ synchronized (mPhenotypeFlagLock) {
+ return mUseOomReRanking;
+ }
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUseOomReranking() {
+ mUseOomReRanking = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_USE_OOM_RE_RANKING, DEFAULT_USE_OOM_RE_RANKING);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateNumberToReRank() {
+ int previousNumberToReRank = getNumberToReRank();
+ int numberToReRank = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK, DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK);
+ if (previousNumberToReRank != numberToReRank) {
+ mScoredProcessRecords = new RankedProcessRecord[numberToReRank];
+ for (int i = 0; i < mScoredProcessRecords.length; ++i) {
+ mScoredProcessRecords[i] = new RankedProcessRecord();
+ }
+ mLruPositions = new int[numberToReRank];
+ }
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ int getNumberToReRank() {
+ return mScoredProcessRecords == null ? 0 : mScoredProcessRecords.length;
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateLruWeight() {
+ mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUsesWeight() {
+ mUsesWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_USES_WEIGHT, DEFAULT_OOM_RE_RANKING_USES_WEIGHT);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateRssWeight() {
+ mRssWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_RSS_WEIGHT, DEFAULT_OOM_RE_RANKING_RSS_WEIGHT);
+ }
+
+ /**
+ * Re-rank the cached processes in the lru list with a weighted ordering
+ * of lru, rss size and number of times the process has been put in the cache.
+ */
+ public void reRankLruCachedApps(ProcessList processList) {
+ float lruWeight;
+ float usesWeight;
+ float rssWeight;
+ int[] lruPositions;
+ RankedProcessRecord[] scoredProcessRecords;
+
+ ArrayList<ProcessRecord> lruList = processList.mLruProcesses;
+
+ synchronized (mPhenotypeFlagLock) {
+ lruWeight = mLruWeight;
+ usesWeight = mUsesWeight;
+ rssWeight = mRssWeight;
+ lruPositions = mLruPositions;
+ scoredProcessRecords = mScoredProcessRecords;
+ }
+
+ // Don't re-rank if the class hasn't been initialized with defaults.
+ if (lruPositions == null || scoredProcessRecords == null) {
+ return;
+ }
+
+ // Collect the least recently used processes to re-rank, only rank cached
+ // processes further down the list than mLruProcessServiceStart.
+ int cachedProcessPos = 0;
+ for (int i = 0; i < processList.mLruProcessServiceStart
+ && cachedProcessPos < scoredProcessRecords.length; ++i) {
+ ProcessRecord app = lruList.get(i);
+ // Processes that will be assigned a cached oom adj score.
+ if (!app.killedByAm && app.thread != null && app.curAdj
+ >= ProcessList.UNKNOWN_ADJ) {
+ scoredProcessRecords[cachedProcessPos].proc = app;
+ scoredProcessRecords[cachedProcessPos].score = 0.0f;
+ lruPositions[cachedProcessPos] = i;
+ ++cachedProcessPos;
+ }
+ }
+
+ // TODO maybe ensure a certain number above this in the cache before re-ranking.
+ if (cachedProcessPos < scoredProcessRecords.length) {
+ // Ignore we don't have enough processes to worry about re-ranking.
+ return;
+ }
+
+ // Add scores for each of the weighted features we want to rank based on.
+ if (lruWeight > 0.0f) {
+ // This doesn't use the LRU list ordering as after the first re-ranking
+ // that will no longer be lru.
+ Arrays.sort(scoredProcessRecords, LAST_ACTIVITY_TIME_COMPARATOR);
+ addToScore(scoredProcessRecords, lruWeight);
+ }
+ if (rssWeight > 0.0f) {
+ Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR);
+ addToScore(scoredProcessRecords, rssWeight);
+ }
+ if (usesWeight > 0.0f) {
+ Arrays.sort(scoredProcessRecords, CACHE_USE_COMPARATOR);
+ addToScore(scoredProcessRecords, usesWeight);
+ }
+
+ // Re-rank by the new combined score.
+ Arrays.sort(scoredProcessRecords, SCORED_PROCESS_RECORD_COMPARATOR);
+
+ if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
+ boolean printedHeader = false;
+ for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ if (scoredProcessRecords[i].proc.pid != lruList.get(lruPositions[i]).pid) {
+ if (!printedHeader) {
+ Slog.i(OomAdjuster.TAG, "reRankLruCachedApps");
+ printedHeader = true;
+ }
+ Slog.i(OomAdjuster.TAG, " newPos=" + lruPositions[i] + " "
+ + scoredProcessRecords[i].proc);
+ }
+ }
+ }
+
+ for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ lruList.set(lruPositions[i], scoredProcessRecords[i].proc);
+ scoredProcessRecords[i].proc = null;
+ }
+ }
+
+ private static void addToScore(RankedProcessRecord[] scores, float weight) {
+ for (int i = 1; i < scores.length; ++i) {
+ scores[i].score += i * weight;
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println("CacheOomRanker settings");
+ synchronized (mPhenotypeFlagLock) {
+ pw.println(" " + KEY_USE_OOM_RE_RANKING + "=" + mUseOomReRanking);
+ pw.println(" " + KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK + "=" + getNumberToReRank());
+ pw.println(" " + KEY_OOM_RE_RANKING_LRU_WEIGHT + "=" + mLruWeight);
+ pw.println(" " + KEY_OOM_RE_RANKING_USES_WEIGHT + "=" + mUsesWeight);
+ pw.println(" " + KEY_OOM_RE_RANKING_RSS_WEIGHT + "=" + mRssWeight);
+ }
+ }
+
+ private static class ScoreComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ return Float.compare(o1.score, o2.score);
+ }
+ }
+
+ private static class LastActivityTimeComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ return Long.compare(o1.proc.lastActivityTime, o2.proc.lastActivityTime);
+ }
+ }
+
+ private static class CacheUseComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ return Long.compare(o1.proc.getCacheOomRankerUseCount(),
+ o2.proc.getCacheOomRankerUseCount());
+ }
+ }
+
+ private static class LastRssComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ // High RSS first to match least recently used.
+ return Long.compare(o2.proc.mLastRss, o1.proc.mLastRss);
+ }
+ }
+
+ private static class RankedProcessRecord {
+ public ProcessRecord proc;
+ public float score;
+ }
+}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index e5d57df742aa..d69bf2d75064 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@ import static com.android.server.am.AppProfiler.TAG_PSS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import android.app.ActivityManager;
+import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
import android.app.usage.UsageEvents;
import android.compat.annotation.ChangeId;
@@ -119,7 +120,7 @@ import java.util.Arrays;
* All of the code required to compute proc states and oom_adj values.
*/
public final class OomAdjuster {
- private static final String TAG = "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";
static final String OOM_ADJ_REASON_ACTIVITY = OOM_ADJ_REASON_METHOD + "_activityChange";
@@ -169,6 +170,12 @@ public final class OomAdjuster {
*/
CachedAppOptimizer mCachedAppOptimizer;
+ /**
+ * Re-rank apps getting a cache oom adjustment from lru to weighted order
+ * based on weighted scores for LRU, PSS and cache use count.
+ */
+ CacheOomRanker mCacheOomRanker;
+
ActivityManagerConstants mConstants;
final long[] mTmpLong = new long[3];
@@ -331,6 +338,7 @@ public final class OomAdjuster {
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
mCachedAppOptimizer = new CachedAppOptimizer(mService);
+ mCacheOomRanker = new CacheOomRanker();
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
final int pid = msg.arg1;
@@ -361,6 +369,7 @@ public final class OomAdjuster {
void initSettings() {
mCachedAppOptimizer.init();
+ mCacheOomRanker.init(ActivityThread.currentApplication().getMainExecutor());
if (mService.mConstants.KEEP_WARMING_SERVICES.size() > 0) {
final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
@@ -769,6 +778,9 @@ public final class OomAdjuster {
}
}
+ if (mCacheOomRanker.useOomReranking()) {
+ mCacheOomRanker.reRankLruCachedApps(mProcessList);
+ }
assignCachedAdjIfNecessary(mProcessList.mLruProcesses);
if (computeClients) { // There won't be cycles if we didn't compute clients above.
@@ -2775,6 +2787,11 @@ public final class OomAdjuster {
}
@GuardedBy("mService")
+ void dumpCacheOomRankerSettings(PrintWriter pw) {
+ mCacheOomRanker.dump(pw);
+ }
+
+ @GuardedBy("mService")
void updateAppFreezeStateLocked(ProcessRecord app) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index d4d01652e338..520a28bfd889 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -360,6 +360,13 @@ class ProcessRecord implements WindowProcessListener {
int mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ // Approximates the usage count of the app, used for cache re-ranking by CacheOomRanker.
+ //
+ // Counts the number of times the process is re-added to the cache (i.e. setCached(false);
+ // setCached(true)). This over counts, as setCached is sometimes reset while remaining in the
+ // cache. However, this happens uniformly across processes, so ranking is not affected.
+ private int mCacheOomRankerUseCount;
+
boolean mReachable; // Whether or not this process is reachable from given process
long mKillTime; // The timestamp in uptime when this process was killed.
@@ -828,7 +835,12 @@ class ProcessRecord implements WindowProcessListener {
}
void setCached(boolean cached) {
- mCached = cached;
+ if (mCached != cached) {
+ mCached = cached;
+ if (cached) {
+ ++mCacheOomRankerUseCount;
+ }
+ }
}
@Override
@@ -836,6 +848,10 @@ class ProcessRecord implements WindowProcessListener {
return mCached;
}
+ int getCacheOomRankerUseCount() {
+ return mCacheOomRankerUseCount;
+ }
+
boolean hasActivities() {
return mWindowProcessController.hasActivities();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cbd973aeaf6a..61c8b178b4c0 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -50,6 +50,7 @@ import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
import android.service.notification.RankingHelperProto;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
@@ -101,6 +102,7 @@ public class PreferencesHelper implements RankingConfig {
private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
private static final int NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT = 1000;
+ private static final int NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS = 30;
@VisibleForTesting
static final String TAG_RANKING = "ranking";
@@ -324,12 +326,8 @@ public class PreferencesHelper implements RankingConfig {
channel.setImportanceLockedByOEM(true);
}
}
- boolean isInvalidShortcutChannel =
- channel.getConversationId() != null &&
- channel.getConversationId().contains(
- PLACEHOLDER_CONVERSATION_ID);
- if (mAllowInvalidShortcuts || (!mAllowInvalidShortcuts
- && !isInvalidShortcutChannel)) {
+
+ if (isShortcutOk(channel) && isDeletionOk(channel)) {
r.channels.put(id, channel);
}
}
@@ -369,6 +367,26 @@ public class PreferencesHelper implements RankingConfig {
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
+ private boolean isShortcutOk(NotificationChannel channel) {
+ boolean isInvalidShortcutChannel =
+ channel.getConversationId() != null &&
+ channel.getConversationId().contains(
+ PLACEHOLDER_CONVERSATION_ID);
+ return mAllowInvalidShortcuts || (!mAllowInvalidShortcuts && !isInvalidShortcutChannel);
+ }
+
+ private boolean isDeletionOk(NotificationChannel nc) {
+ if (!nc.isDeleted()) {
+ return true;
+ }
+ long boundary = System.currentTimeMillis() - (
+ DateUtils.DAY_IN_MILLIS * NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS);
+ if (nc.getDeletedTimeMs() <= boundary) {
+ return false;
+ }
+ return true;
+ }
+
private PackagePreferences getPackagePreferencesLocked(String pkg, int uid) {
final String key = packagePreferencesKey(pkg, uid);
return mPackagePreferences.get(key);
@@ -828,6 +846,7 @@ public class PreferencesHelper implements RankingConfig {
if (existing.isDeleted()) {
// The existing channel was deleted - undelete it.
existing.setDeleted(false);
+ existing.setDeletedTimeMs(-1);
needsPolicyFileChange = true;
wasUndeleted = true;
@@ -1119,6 +1138,7 @@ public class PreferencesHelper implements RankingConfig {
private void deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid) {
if (!channel.isDeleted()) {
channel.setDeleted(true);
+ channel.setDeletedTimeMs(System.currentTimeMillis());
LogMaker lm = getChannelLog(channel, pkg);
lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
MetricsLogger.action(lm);
@@ -1479,6 +1499,7 @@ public class PreferencesHelper implements RankingConfig {
if (nc.getConversationId() != null
&& conversationIds.contains(nc.getConversationId())) {
nc.setDeleted(true);
+ nc.setDeletedTimeMs(System.currentTimeMillis());
LogMaker lm = getChannelLog(nc, pkg);
lm.setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index 0157706866c7..17e81daa80cc 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -62,6 +62,7 @@ public abstract class WindowOrientationListener {
private Sensor mSensor;
private OrientationJudge mOrientationJudge;
private int mCurrentRotation = -1;
+ private final Context mContext;
private final Object mLock = new Object();
@@ -88,6 +89,7 @@ public abstract class WindowOrientationListener {
* This constructor is private since no one uses it.
*/
private WindowOrientationListener(Context context, Handler handler, int rate) {
+ mContext = context;
mHandler = handler;
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
@@ -284,6 +286,19 @@ public abstract class WindowOrientationListener {
}
}
+ /**
+ * Returns whether this WindowOrientationListener can remain enabled while the device is dozing.
+ * If this returns true, it implies that the underlying sensor can still run while the AP is
+ * asleep, and that the underlying sensor will wake the AP on an event.
+ */
+ public boolean shouldStayEnabledWhileDreaming() {
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_forceOrientationListenerEnabledWhileDreaming)) {
+ return true;
+ }
+ return mSensor.getType() == Sensor.TYPE_DEVICE_ORIENTATION && mSensor.isWakeUpSensor();
+ }
+
abstract class OrientationJudge implements SensorEventListener {
// Number of nanoseconds per millisecond.
protected static final long NANOS_PER_MS = 1000000;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c4aaf7c8a935..df5d3ea3c150 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -955,10 +955,16 @@ public class DisplayRotation {
keyguardDrawComplete, windowManagerDrawComplete);
boolean disable = true;
+
+ // If the orientation listener uses a wake sensor, keep the orientation listener on if the
+ // screen is on (regardless of wake state). This allows the AoD to rotate.
+ //
// Note: We postpone the rotating of the screen until the keyguard as well as the
// window manager have reported a draw complete or the keyguard is going away in dismiss
// mode.
- if (screenOnEarly && awake && ((keyguardDrawComplete && windowManagerDrawComplete))) {
+ if (screenOnEarly
+ && (awake || mOrientationListener.shouldStayEnabledWhileDreaming())
+ && ((keyguardDrawComplete && windowManagerDrawComplete))) {
if (needSensorRunning()) {
disable = false;
// Enable listener if not already enabled.
@@ -974,7 +980,7 @@ public class DisplayRotation {
}
}
// Check if sensors need to be disabled.
- if (disable && mOrientationListener.mEnabled) {
+ if (disable) {
mOrientationListener.disable();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2975800f4cf1..7df90166420c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8650,11 +8650,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (isCallerProfileOwnerOrDelegate && isProfileOwnerOfOrganizationOwnedDevice(userId)) {
return true;
}
- //TODO(b/130844684): Temporarily allow profile owner on non-organization-owned devices
- //to read device identifiers.
- if (isCallerProfileOwnerOrDelegate) {
- return true;
- }
return false;
}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index fbde1d2cf1cf..e4b650cad055 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -21,11 +21,13 @@
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
+ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<!-- needed by MasterClearReceiverTest to display a system dialog -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index f4c6918c0e96..e99113d1296f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -351,6 +351,7 @@ public class DeviceIdleControllerTest {
// them after each test, otherwise, subsequent tests will fail.
LocalServices.removeServiceForTest(AppStateTracker.class);
LocalServices.removeServiceForTest(DeviceIdleInternal.class);
+ LocalServices.removeServiceForTest(PowerAllowlistInternal.class);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
new file mode 100644
index 000000000000..37103965de3b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.provider.DeviceConfig;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.testables.TestableDeviceConfig;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link CachedAppOptimizer}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CacheOomRankerTest
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class CacheOomRankerTest {
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ private Handler mHandler;
+ private ActivityManagerService mAms;
+
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+
+ @Rule
+ public final TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private int mNextPid = 10000;
+ private int mNextUid = 30000;
+ private int mNextPackageUid = 40000;
+ private int mNextPackageName = 1;
+
+ private TestExecutor mExecutor = new TestExecutor();
+ private CacheOomRanker mCacheOomRanker = new CacheOomRanker();
+
+ @Before
+ public void setUp() {
+ HandlerThread handlerThread = new HandlerThread("");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ /* allowIo */
+ ServiceThread thread = new ServiceThread("TestServiceThread",
+ Process.THREAD_PRIORITY_DEFAULT,
+ true /* allowIo */);
+ thread.start();
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mAms = new ActivityManagerService(
+ new TestInjector(context), mServiceThreadRule.getThread());
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(context);
+ mAms.mActivityTaskManager.initialize(null, null, context.getMainLooper());
+ mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+ mAms.mPackageManagerInt = mPackageManagerInt;
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ mCacheOomRanker.init(mExecutor);
+ }
+
+ @Test
+ public void init_listensForConfigChanges() throws InterruptedException {
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_USE_OOM_RE_RANKING,
+ Boolean.TRUE.toString(), true);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.useOomReranking()).isTrue();
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_USE_OOM_RE_RANKING, Boolean.FALSE.toString(), false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.useOomReranking()).isFalse();
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK,
+ Integer.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.getNumberToReRank())
+ .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK + 2);
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
+ Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.mLruWeight)
+ .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f);
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT,
+ Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.mRssWeight)
+ .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_RSS_WEIGHT - 0.1f);
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
+ Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.mUsesWeight)
+ .isEqualTo(CacheOomRanker.DEFAULT_OOM_RE_RANKING_USES_WEIGHT + 0.2f);
+ }
+
+ @Test
+ public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 5,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */1.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ processList.add(lastUsed40MinutesAgo);
+ ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ processList.add(lastUsed42MinutesAgo);
+ ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ processList.add(lastUsed60MinutesAgo);
+ ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ processList.add(lastUsed15MinutesAgo);
+ ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(17).toMillis(), 1024L, 20);
+ processList.add(lastUsed17MinutesAgo);
+ // Only re-ranking 5 entries so this should stay in most recent position.
+ ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 1024L, 20);
+ processList.add(lastUsed30MinutesAgo);
+
+ mCacheOomRanker.reRankLruCachedApps(list);
+
+ // First 5 ordered by least recently used first, then last processes position unchanged.
+ assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
+ lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo,
+ lastUsed30MinutesAgo);
+ }
+
+ @Test
+ public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 15 * 1024L, 20);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20);
+ processList.add(rss16k);
+
+ mCacheOomRanker.reRankLruCachedApps(list);
+
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k);
+ }
+
+ @Test
+ public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 4,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ list.mLruProcessServiceStart = 1;
+ ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ processList.add(used20);
+ // Only re-ranking 6 entries so last two should stay in most recent position.
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ processList.add(used200);
+
+ mCacheOomRanker.reRankLruCachedApps(list);
+
+ // First 4 ordered by uses, then last processes position unchanged.
+ assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
+ used200);
+ }
+
+ @Test
+ public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 4,
+ /* usesWeight= */ 0.5f,
+ /* pssWeight= */ 0.2f,
+ /* lruWeight= */ 0.3f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ processList.add(unknownAdj1);
+ ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ processList.add(unknownAdj2);
+ ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ processList.add(unknownAdj3);
+ ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
+ Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ processList.add(foregroundAdj);
+ ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
+ Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ processList.add(serviceAdj);
+ ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
+ Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ processList.add(systemAdj);
+
+ // 6 Processes but only 3 in eligible for cache so no re-ranking.
+ mCacheOomRanker.reRankLruCachedApps(list);
+
+ // All positions unchanged.
+ assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3,
+ foregroundAdj, serviceAdj, systemAdj);
+ }
+
+ @Test
+ public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 4,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ list.mLruProcessServiceStart = 4;
+ ArrayList<ProcessRecord> processList = list.mLruProcesses;
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ processList.add(used200);
+
+ mCacheOomRanker.reRankLruCachedApps(list);
+
+ // All positions unchanged.
+ assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
+ used200);
+ }
+
+ private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight)
+ throws InterruptedException {
+ mExecutor.init(4);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK,
+ Integer.toString(numberToReRank),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
+ Float.toString(lruWeight),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_RSS_WEIGHT,
+ Float.toString(pssWeight),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
+ Float.toString(useWeight),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank);
+ assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight);
+ assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight);
+ assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight);
+ }
+
+ private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss,
+ int returnedToCacheCount) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = "a.package.name" + mNextPackageName++;
+ ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
+ app.pid = mNextPid++;
+ app.info.uid = mNextPackageUid++;
+ // Exact value does not mater, it can be any state for which compaction is allowed.
+ app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ app.setAdj = setAdj;
+ app.lastActivityTime = lastActivityTime;
+ app.mLastRss = lastRss;
+ app.setCached(false);
+ for (int i = 0; i < returnedToCacheCount; ++i) {
+ app.setCached(false);
+ app.setCached(true);
+ }
+ return app;
+ }
+
+ private class TestExecutor implements Executor {
+ private CountDownLatch mLatch;
+
+ private void init(int count) {
+ mLatch = new CountDownLatch(count);
+ }
+
+ private void init() {
+ init(1);
+ }
+
+ private void waitForLatch() throws InterruptedException {
+ mLatch.await(5, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ mLatch.countDown();
+ }
+ }
+
+ private class TestInjector extends ActivityManagerService.Injector {
+ private TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index dcbf8c02edf9..4effa4d445bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -59,6 +59,7 @@ import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
+import com.android.server.PowerAllowlistInternal;
import com.android.server.SystemServiceManager;
import com.android.server.job.controllers.JobStatus;
import com.android.server.usage.AppStandbyInternal;
@@ -134,6 +135,8 @@ public class JobSchedulerServiceTest {
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getResources()).thenReturn(mock(Resources.class));
// Called in QuotaController constructor.
+ doReturn(mock(PowerAllowlistInternal.class))
+ .when(() -> LocalServices.getService(PowerAllowlistInternal.class));
IActivityManager activityManager = ActivityManager.getService();
spyOn(activityManager);
try {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 1a65894f85b1..c4c9173536ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -80,6 +80,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
+import com.android.server.PowerAllowlistInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.JobServiceContext;
@@ -123,6 +124,7 @@ public class QuotaControllerTest {
private QuotaController mQuotaController;
private QuotaController.QcConstants mQcConstants;
private int mSourceUid;
+ private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
private IUidObserver mUidObserver;
DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
@@ -138,6 +140,8 @@ public class QuotaControllerTest {
@Mock
private PackageManagerInternal mPackageManagerInternal;
@Mock
+ private PowerAllowlistInternal mPowerAllowlistInternal;
+ @Mock
private UsageStatsManagerInternal mUsageStatsManager;
private JobStore mJobStore;
@@ -173,6 +177,8 @@ public class QuotaControllerTest {
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
doReturn(mUsageStatsManager)
.when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
+ doReturn(mPowerAllowlistInternal)
+ .when(() -> LocalServices.getService(PowerAllowlistInternal.class));
// Used in JobStatus.
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -211,11 +217,19 @@ public class QuotaControllerTest {
ArgumentCaptor.forClass(BroadcastReceiver.class);
ArgumentCaptor<IUidObserver> uidObserverCaptor =
ArgumentCaptor.forClass(IUidObserver.class);
+ ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
+ ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class);
mQuotaController = new QuotaController(mJobSchedulerService,
mock(BackgroundJobsController.class), mock(ConnectivityController.class));
- verify(mContext).registerReceiver(receiverCaptor.capture(), any());
+ verify(mContext).registerReceiver(receiverCaptor.capture(),
+ ArgumentMatchers.argThat(filter ->
+ filter.hasAction(BatteryManager.ACTION_CHARGING)
+ && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
mChargingReceiver = receiverCaptor.getValue();
+ verify(mPowerAllowlistInternal)
+ .registerTempAllowlistChangeListener(taChangeCaptor.capture());
+ mTempAllowlistListener = taChangeCaptor.getValue();
try {
verify(activityManager).registerUidObserver(
uidObserverCaptor.capture(),
@@ -2385,6 +2399,8 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 86 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 85 * SECOND_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS,
+ 84 * SECOND_IN_MILLIS);
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
@@ -2423,6 +2439,7 @@ public class QuotaControllerTest {
assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
assertEquals(86 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
+ assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJTempAllowlistGracePeriodMs());
}
@Test
@@ -2462,6 +2479,7 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1);
+ setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, -1);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(0, mQuotaController.getInQuotaBufferMs());
@@ -2497,6 +2515,7 @@ public class QuotaControllerTest {
assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
assertEquals(5 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs());
+ assertEquals(0, mQuotaController.getEJTempAllowlistGracePeriodMs());
// Invalid configurations.
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
@@ -2530,6 +2549,7 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, 25 * HOUR_IN_MILLIS);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
@@ -2555,6 +2575,7 @@ public class QuotaControllerTest {
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
+ assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJTempAllowlistGracePeriodMs());
}
/** Tests that TimingSessions aren't saved when the device is charging. */
@@ -3145,6 +3166,119 @@ public class QuotaControllerTest {
}
/**
+ * Tests that Timers properly track regular sessions when an app is added and removed from the
+ * temp allowlist.
+ */
+ @Test
+ public void testTimerTracking_TempAllowlisting() {
+ // None of these should be affected purely by the temp allowlist changing.
+ setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ JobStatus job1 = createJobStatus("testTimerTracking_TempAllowlisting", 1);
+ JobStatus job2 = createJobStatus("testTimerTracking_TempAllowlisting", 2);
+ JobStatus job3 = createJobStatus("testTimerTracking_TempAllowlisting", 3);
+ JobStatus job4 = createJobStatus("testTimerTracking_TempAllowlisting", 4);
+ JobStatus job5 = createJobStatus("testTimerTracking_TempAllowlisting", 5);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job1, null);
+ }
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job1);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Job starts after app is added to temp allowlist and stops before removal.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job2, null);
+ mQuotaController.prepareForExecutionLocked(job2);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts after app is added to temp allowlist and stops after removal,
+ // before grace period ends.
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job3, null);
+ mQuotaController.prepareForExecutionLocked(job3);
+ }
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
+ advanceElapsedClock(elapsedGracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job3, null, false);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + elapsedGracePeriodMs, 1));
+ assertEquals(expected,
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ elapsedGracePeriodMs += SECOND_IN_MILLIS;
+
+ // Job starts during grace period and ends after grace period ends
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job4, null);
+ mQuotaController.prepareForExecutionLocked(job4);
+ }
+ final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(remainingGracePeriod);
+ // Wait for handler to update Timer
+ // Can't directly evaluate the message because for some reason, the captured message returns
+ // the wrong 'what' even though the correct message goes to the handler and the correct
+ // path executes.
+ verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, remainingGracePeriod + 10 * SECOND_IN_MILLIS, 1));
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
+ }
+ assertEquals(expected,
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts and runs completely after temp allowlist grace period.
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job5, null);
+ mQuotaController.prepareForExecutionLocked(job5);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ /**
* Tests that TOP jobs aren't stopped when an app runs out of quota.
*/
@Test
@@ -4583,6 +4717,116 @@ public class QuotaControllerTest {
}
/**
+ * Tests that Timers properly track sessions when an app is added and removed from the temp
+ * allowlist.
+ */
+ @Test
+ public void testEJTimerTracking_TempAllowlisting() {
+ setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ JobStatus job1 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 1);
+ JobStatus job2 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 2);
+ JobStatus job3 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 3);
+ JobStatus job4 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 4);
+ JobStatus job5 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 5);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job1, null);
+ }
+ assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job1);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Job starts after app is added to temp allowlist and stops before removal.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job2, null);
+ mQuotaController.prepareForExecutionLocked(job2);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+ }
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts after app is added to temp allowlist and stops after removal,
+ // before grace period ends.
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job3, null);
+ mQuotaController.prepareForExecutionLocked(job3);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
+ advanceElapsedClock(elapsedGracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job3, null, false);
+ }
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ elapsedGracePeriodMs += SECOND_IN_MILLIS;
+
+ // Job starts during grace period and ends after grace period ends
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job4, null);
+ mQuotaController.prepareForExecutionLocked(job4);
+ }
+ final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
+ start = JobSchedulerService.sElapsedRealtimeClock.millis() + remainingGracePeriod;
+ advanceElapsedClock(remainingGracePeriod);
+ // Wait for handler to update Timer
+ // Can't directly evaluate the message because for some reason, the captured message returns
+ // the wrong 'what' even though the correct message goes to the handler and the correct
+ // path executes.
+ verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
+ }
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts and runs completely after temp allowlist grace period.
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job5, null);
+ mQuotaController.prepareForExecutionLocked(job5);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ /**
* Tests that expedited jobs aren't stopped when an app runs out of quota.
*/
@Test
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 009cb0b363e4..6366155a4a39 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -1230,14 +1230,14 @@ public class AppSearchImplTest {
@Test
public void testRewriteSearchResultProto() throws Exception {
- final String database =
+ final String prefix =
"com.package.foo"
+ AppSearchImpl.PACKAGE_DELIMITER
+ "databaseName"
+ AppSearchImpl.DATABASE_DELIMITER;
final String uri = "uri";
- final String namespace = database + "namespace";
- final String schemaType = database + "schema";
+ final String namespace = prefix + "namespace";
+ final String schemaType = prefix + "schema";
// Building the SearchResult received from query.
DocumentProto documentProto =
@@ -1257,6 +1257,7 @@ public class AppSearchImplTest {
AppSearchImpl.rewriteSearchResultProto(searchResultProto);
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getPackageName()).isEqualTo("com.package.foo");
+ assertThat(result.getDatabaseName()).isEqualTo("databaseName");
assertThat(result.getDocument())
.isEqualTo(
GenericDocumentToProtoConverter.toGenericDocument(
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index a3f0f6bf280e..97daea3011ea 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -86,7 +86,9 @@ public class SnippetTest {
// Making ResultReader and getting Snippet values.
SearchResultPage searchResultPage =
SearchResultToProtoConverter.toSearchResultPage(
- searchResultProto, Collections.singletonList("packageName"));
+ searchResultProto,
+ Collections.singletonList("packageName"),
+ Collections.singletonList("databaseName"));
for (SearchResult result : searchResultPage.getResults()) {
SearchResult.MatchInfo match = result.getMatches().get(0);
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
@@ -135,7 +137,9 @@ public class SnippetTest {
SearchResultPage searchResultPage =
SearchResultToProtoConverter.toSearchResultPage(
- searchResultProto, Collections.singletonList("packageName"));
+ searchResultProto,
+ Collections.singletonList("packageName"),
+ Collections.singletonList("databaseName"));
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getMatches()).isEmpty();
}
@@ -201,7 +205,9 @@ public class SnippetTest {
// Making ResultReader and getting Snippet values.
SearchResultPage searchResultPage =
SearchResultToProtoConverter.toSearchResultPage(
- searchResultProto, Collections.singletonList("packageName"));
+ searchResultProto,
+ Collections.singletonList("packageName"),
+ Collections.singletonList("databaseName"));
for (SearchResult result : searchResultPage.getResults()) {
SearchResult.MatchInfo match1 = result.getMatches().get(0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 4d2a4784b5d9..8c744c94249b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -95,6 +95,7 @@ import android.provider.Settings.Secure;
import android.service.notification.ConversationChannelWrapper;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
@@ -3293,6 +3294,95 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void testDeleted_noTime() throws Exception {
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ final String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+ + "<channel id=\"id\" name=\"hi\" importance=\"3\" deleted=\"true\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
+ }
+
+ @Test
+ public void testDeleted_recentTime() throws Exception {
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ mHelper.createNotificationChannel(
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
+ mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
+ NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
+ assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
+ assertTrue(nc1.isDeleted());
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG_P, UID_P, false,
+ UserHandle.USER_SYSTEM, "id", NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+ null);
+ parser.nextTag();
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+ mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+
+ NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
+ assertTrue(DateUtils.isToday(nc.getDeletedTimeMs()));
+ assertTrue(nc.isDeleted());
+ }
+
+ @Test
+ public void testUnDelete_time() throws Exception {
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ mHelper.createNotificationChannel(
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
+ mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
+ NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
+ assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
+ assertTrue(nc1.isDeleted());
+
+ mHelper.createNotificationChannel(
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
+ nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
+ assertEquals(-1, nc1.getDeletedTimeMs());
+ assertFalse(nc1.isDeleted());
+ }
+
+ @Test
+ public void testDeleted_longTime() throws Exception {
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
+ mAppOpsManager, mStatsEventBuilderFactory);
+
+ long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30);
+
+ final String xml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+ + "<channel id=\"id\" name=\"hi\" importance=\"3\" deleted=\"true\" del_time=\""
+ + time + "\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ NotificationChannel nc = mHelper.getNotificationChannel(PKG_O, UID_O, "id", true);
+ assertNull(nc);
+ }
+
+ @Test
public void testGetConversations_all() {
String convoId = "convo";
NotificationChannel messages =
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 52b0afb56b3c..a0a390947822 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -514,6 +514,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
res.codeBytes = stats.codeSize + stats.externalCodeSize;
res.dataBytes = stats.dataSize + stats.externalDataSize;
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+ res.externalCacheBytes = stats.externalCacheSize;
return res;
}