diff options
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; } |