summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/statsd/framework/Android.bp2
-rw-r--r--apex/statsd/framework/java/android/os/StatsDimensionsValue.java (renamed from core/java/android/os/StatsDimensionsValue.java)3
-rwxr-xr-xapi/system-current.txt3
-rw-r--r--api/test-current.txt1
-rw-r--r--cmds/statsd/Android.bp2
-rw-r--r--cmds/statsd/src/atoms.proto2
-rw-r--r--core/java/android/app/ActivityManagerInternal.java2
-rw-r--r--core/java/android/content/pm/PermissionInfo.java15
-rw-r--r--core/java/android/os/incremental/IncrementalFileStorages.java155
-rw-r--r--core/java/android/util/RotationUtils.java72
-rw-r--r--core/java/android/widget/Magnifier.java60
-rw-r--r--core/proto/android/server/connectivity/data_stall_event.proto6
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/values-mcc334/config.xml23
-rw-r--r--core/res/res/values-mcc732/config.xml23
-rw-r--r--core/res/res/values-mcc740/config.xml23
-rw-r--r--core/res/res/values/attrs_manifest.xml3
-rw-r--r--core/res/res/values/config.xml14
-rw-r--r--core/res/res/values/dimens.xml6
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java230
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java290
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java2
-rw-r--r--services/core/java/com/android/server/VibratorService.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java13
-rw-r--r--services/core/java/com/android/server/incremental/IncrementalManagerService.java12
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java47
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java41
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java15
-rw-r--r--services/core/java/com/android/server/pm/permission/BasePermission.java4
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java19
-rw-r--r--services/core/java/com/android/server/power/AttentionDetector.java5
-rw-r--r--services/core/java/com/android/server/power/Notifier.java36
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java19
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java85
-rw-r--r--services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java23
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java59
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java10
-rw-r--r--services/core/java/com/android/server/wm/utils/WmDisplayCutout.java87
-rw-r--r--services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp5
-rw-r--r--services/incremental/BinderIncrementalService.cpp18
-rw-r--r--services/incremental/IncrementalService.cpp131
-rw-r--r--services/incremental/IncrementalService.h9
-rw-r--r--services/incremental/include/incremental_service.h1
-rw-r--r--services/incremental/test/IncrementalServiceTest.cpp55
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java184
-rw-r--r--tools/stats_log_api_gen/Android.bp1
-rw-r--r--wifi/java/android/net/wifi/IScoreChangeCallback.aidl4
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java12
67 files changed, 1588 insertions, 587 deletions
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 231c91026bb6..43e762d04342 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -30,6 +30,7 @@ java_library {
],
permitted_packages: [
"android.app",
+ "android.os",
"android.util",
],
libs: [
@@ -43,6 +44,7 @@ java_library {
"//frameworks/base/apex/statsd:__subpackages__",
//TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
"//frameworks/base",
+ "//frameworks/opt/net/wifi/service",
],
apex_available: [
"com.android.os.statsd",
diff --git a/core/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
index da13ea13d5fa..886130fc5f14 100644
--- a/core/java/android/os/StatsDimensionsValue.java
+++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java
@@ -264,7 +264,8 @@ public final class StatsDimensionsValue implements Parcelable {
/**
* Parcelable Creator for StatsDimensionsValue.
*/
- public static final @android.annotation.NonNull Parcelable.Creator<StatsDimensionsValue> CREATOR = new
+ public static final @android.annotation.NonNull
+ Parcelable.Creator<StatsDimensionsValue> CREATOR = new
Parcelable.Creator<StatsDimensionsValue>() {
public StatsDimensionsValue createFromParcel(Parcel in) {
return new StatsDimensionsValue(in);
diff --git a/api/system-current.txt b/api/system-current.txt
index 8630b36f1595..89811ada123a 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2304,6 +2304,7 @@ package android.content.pm {
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
+ field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
@@ -7775,7 +7776,7 @@ package android.net.wifi {
}
public static interface WifiManager.ScoreChangeCallback {
- method public void onStatusChange(int, boolean);
+ method public void onScoreChange(int, @NonNull android.net.NetworkScore);
method public void onTriggerUpdateOfWifiUsabilityStats(int);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index c8d746b44a15..cee560b00abe 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -933,6 +933,7 @@ package android.content.pm {
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
+ field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 2237bf2b2acb..ebed4ee2b82d 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -110,7 +110,7 @@ cc_defaults {
],
cflags: [
- // "-DNEW_ENCODING_SCHEME",
+ "-DNEW_ENCODING_SCHEME",
],
local_include_dirs: [
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index cc05dbb0639c..4c0c16f6ac84 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -378,7 +378,7 @@ message Atom {
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
UserspaceRebootReported userspace_reboot_reported = 243;
- NotificationReported notification_reported = 244;
+ NotificationReported notification_reported = 244 [(module) = "framework"];
NotificationPanelReported notification_panel_reported = 245;
NotificationChannelModified notification_panel_modified = 246;
IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"];
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 4e47594b6196..c60f7bd29ce8 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -278,7 +278,7 @@ public abstract class ActivityManagerInternal {
String resolvedType, boolean fgRequired, String callingPackage, @UserIdInt int userId,
boolean allowBackgroundActivityStarts) throws TransactionTooLargeException;
- public abstract void disconnectActivityFromServices(Object connectionHolder, Object conns);
+ public abstract void disconnectActivityFromServices(Object connectionHolder);
public abstract void cleanUpServices(@UserIdInt int userId, ComponentName component,
Intent baseIntent);
public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, @UserIdInt int userId);
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index a0f089b2df41..3aa1a6d3f8ff 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -259,6 +259,17 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
@TestApi
public static final int PROTECTION_FLAG_COMPANION = 0x800000;
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>retailDemo</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_RETAIL_DEMO = 0x1000000;
+
/** @hide */
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
PROTECTION_FLAG_PRIVILEGED,
@@ -282,6 +293,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
PROTECTION_FLAG_APP_PREDICTOR,
PROTECTION_FLAG_TELEPHONY,
PROTECTION_FLAG_COMPANION,
+ PROTECTION_FLAG_RETAIL_DEMO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -528,6 +540,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
if ((level & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0) {
protLevel += "|telephony";
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0) {
+ protLevel += "|retailDemo";
+ }
return protLevel;
}
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 4a668791aeb9..987a53e337a0 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -17,24 +17,24 @@
package android.os.incremental;
/**
- * Set up files and directories used in an installation session.
- * Currently only used by Incremental Installation.
- * For Incremental installation, the expected outcome of this function is:
- * 0) All the files are in defaultStorage
- * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the
- * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage.
- * 2) All lib files are in the sub directories as their names suggest, and in the same parent
- * directory as the APK files. The files are linked from mApkStorage to defaultStorage.
- * 3) OBB files are in another directory that is different from APK files and lib files, bound
- * to mObbStorage. The files are linked from mObbStorage to defaultStorage.
+ * Set up files and directories used in an installation session. Currently only used by Incremental
+ * Installation. For Incremental installation, the expected outcome of this function is: 0) All the
+ * files are in defaultStorage 1) All APK files are in the same directory, bound to mApkStorage, and
+ * bound to the InstallerSession's stage dir. The files are linked from mApkStorage to
+ * defaultStorage. 2) All lib files are in the sub directories as their names suggest, and in the
+ * same parent directory as the APK files. The files are linked from mApkStorage to defaultStorage.
+ * 3) OBB files are in another directory that is different from APK files and lib files, bound to
+ * mObbStorage. The files are linked from mObbStorage to defaultStorage.
*
* @throws IllegalStateException the session is not an Incremental installation session.
*/
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
+import android.text.TextUtils;
import android.util.Slog;
import java.io.File;
@@ -42,6 +42,8 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.List;
+import java.util.Objects;
import java.util.Random;
/**
@@ -50,6 +52,10 @@ import java.util.Random;
*/
public final class IncrementalFileStorages {
private static final String TAG = "IncrementalFileStorages";
+
+ private static final String TMP_DIR_ROOT = "/data/incremental/tmp";
+ private static final Random TMP_DIR_RANDOM = new Random();
+
private @Nullable IncrementalStorage mDefaultStorage;
private @Nullable String mDefaultDir;
private @NonNull IncrementalManager mIncrementalManager;
@@ -61,41 +67,77 @@ public final class IncrementalFileStorages {
* TODO(b/133435829): code clean up
*
* @throws IllegalStateException the session is not an Incremental installation session.
+ * @throws IOException if fails to setup files or directories.
*/
- public IncrementalFileStorages(@NonNull String packageName,
+ public static IncrementalFileStorages initialize(Context context,
@NonNull File stageDir,
+ @NonNull DataLoaderParams dataLoaderParams,
+ List<InstallationFile> addedFiles) throws IOException {
+ // TODO(b/136132412): sanity check if session should not be incremental
+ IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
+ Context.INCREMENTAL_SERVICE);
+ if (incrementalManager == null) {
+ // TODO(b/146080380): add incremental-specific error code
+ throw new IOException("Failed to obtain incrementalManager.");
+ }
+
+ IncrementalFileStorages result = null;
+ try {
+ result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams);
+ for (InstallationFile file : addedFiles) {
+ if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
+ try {
+ result.addApkFile(file);
+ } catch (IOException e) {
+ // TODO(b/146080380): add incremental-specific error code
+ throw new IOException(
+ "Failed to add and configure Incremental File: " + file.getName(),
+ e);
+ }
+ } else {
+ throw new IOException("Unknown file type: " + file.getFileType());
+ }
+ }
+
+ if (!result.mDefaultStorage.startLoading()) {
+ // TODO(b/146080380): add incremental-specific error code
+ throw new IOException("Failed to start loading data for Incremental installation.");
+ }
+
+ return result;
+ } catch (IOException e) {
+ if (result != null) {
+ result.cleanUp();
+ }
+ throw e;
+ }
+ }
+
+ private IncrementalFileStorages(@NonNull File stageDir,
@NonNull IncrementalManager incrementalManager,
- @NonNull DataLoaderParams dataLoaderParams) {
+ @NonNull DataLoaderParams dataLoaderParams) throws IOException {
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
final String incrementalPath = dataLoaderParams.getArguments();
- mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
mDefaultDir = incrementalPath;
- return;
- }
- mDefaultDir = getTempDir();
- if (mDefaultDir == null) {
- return;
+ if (TextUtils.isEmpty(mDefaultDir)) {
+ throw new IOException("Failed to create storage: incrementalPath is empty");
+ }
+ mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
+ } else {
+ mDefaultDir = getTempDir();
+ if (mDefaultDir == null) {
+ throw new IOException("Failed to create storage: tempDir is empty");
+ }
+ mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
+ dataLoaderParams,
+ IncrementalManager.CREATE_MODE_CREATE
+ | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
}
- mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
- dataLoaderParams,
- IncrementalManager.CREATE_MODE_CREATE
- | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
- }
- /**
- * Adds a file into the installation session. Makes sure it will be placed inside
- * a proper storage instance, based on its file type.
- */
- public void addFile(@NonNull InstallationFile file) throws IOException {
if (mDefaultStorage == null) {
- throw new IOException("Cannot add file because default storage does not exist");
- }
- if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
- addApkFile(file);
- } else {
- throw new IOException("Unknown file type: " + file.getFileType());
+ throw new IOException("Failed to create storage");
}
}
@@ -108,26 +150,6 @@ public final class IncrementalFileStorages {
mDefaultStorage.makeFile(apkName, apk.getSize(), null,
apk.getMetadata(), 0, null, null, null);
}
- if (targetFile.exists()) {
- Slog.i(TAG, "!!! created: " + targetFile.getAbsolutePath());
- }
- }
-
- /**
- * Starts loading data for default storage.
- * TODO(b/136132412): update the implementation with latest API design.
- */
- public boolean startLoading() {
- if (mDefaultStorage == null) {
- return false;
- }
- return mDefaultStorage.startLoading();
- }
-
- /**
- * Sets up obb storage directory and create bindings.
- */
- public void finishSetUp() {
}
/**
@@ -135,22 +157,21 @@ public final class IncrementalFileStorages {
* TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
*/
public void cleanUp() {
- if (mDefaultStorage != null && mDefaultDir != null) {
- try {
- mDefaultStorage.unBind(mDefaultDir);
- mDefaultStorage.unBind(mStageDir.getAbsolutePath());
- } catch (IOException ignored) {
- }
- mDefaultDir = null;
- mDefaultStorage = null;
+ Objects.requireNonNull(mDefaultStorage);
+
+ try {
+ mDefaultStorage.unBind(mDefaultDir);
+ mDefaultStorage.unBind(mStageDir.getAbsolutePath());
+ } catch (IOException ignored) {
}
+
+ mDefaultDir = null;
+ mDefaultStorage = null;
}
- private String getTempDir() {
- final String tmpDirRoot = "/data/incremental/tmp";
- final Random random = new Random();
- final Path tmpDir =
- Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1)));
+ private static String getTempDir() {
+ final Path tmpDir = Paths.get(TMP_DIR_ROOT,
+ String.valueOf(TMP_DIR_RANDOM.nextInt(Integer.MAX_VALUE - 1)));
try {
Files.createDirectories(tmpDir);
} catch (Exception ex) {
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
new file mode 100644
index 000000000000..a44ed59c14d4
--- /dev/null
+++ b/core/java/android/util/RotationUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.graphics.Insets;
+import android.view.Surface.Rotation;
+
+/**
+ * A class containing utility methods related to rotation.
+ *
+ * @hide
+ */
+public class RotationUtils {
+
+ /**
+ * Rotates an Insets according to the given rotation.
+ */
+ public static Insets rotateInsets(Insets insets, @Rotation int rotation) {
+ if (insets == null || insets == Insets.NONE) {
+ return insets;
+ }
+ Insets rotated;
+ switch (rotation) {
+ case ROTATION_0:
+ rotated = insets;
+ break;
+ case ROTATION_90:
+ rotated = Insets.of(
+ insets.top,
+ insets.right,
+ insets.bottom,
+ insets.left);
+ break;
+ case ROTATION_180:
+ rotated = Insets.of(
+ insets.right,
+ insets.bottom,
+ insets.left,
+ insets.top);
+ break;
+ case ROTATION_270:
+ rotated = Insets.of(
+ insets.bottom,
+ insets.left,
+ insets.top,
+ insets.right);
+ break;
+ default:
+ throw new IllegalArgumentException("unknown rotation: " + rotation);
+ }
+ return rotated;
+ }
+}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 57b63a7a9f0d..50da6ce19f44 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -854,6 +854,8 @@ public final class Magnifier {
// The z of the magnifier surface, defining its z order in the list of
// siblings having the same parent surface (usually the main app surface).
private static final int SURFACE_Z = 5;
+ // The width of the ramp region in pixels on the left & right sides of the fish-eye effect.
+ private static final int FISHEYE_RAMP_WIDTH = 30;
// Display associated to the view the magnifier is attached to.
private final Display mDisplay;
@@ -906,7 +908,8 @@ public final class Magnifier {
// Whether is in the new magnifier style.
private boolean mIsFishEyeStyle;
// The mesh matrix for the fish-eye effect.
- private float[] mMesh;
+ private float[] mMeshLeft;
+ private float[] mMeshRight;
private int mMeshWidth;
private int mMeshHeight;
@@ -986,29 +989,29 @@ public final class Magnifier {
}
private void createMeshMatrixForFishEyeEffect() {
- mMeshWidth = mZoom < 1.5f ? 5 : 4;
+ mMeshWidth = 1;
mMeshHeight = 6;
final float w = mContentWidth;
final float h = mContentHeight;
- final float dx = (w - mZoom * w * (mMeshWidth - 2) / mMeshWidth) / 2;
- mMesh = new float[2 * (mMeshWidth + 1) * (mMeshHeight + 1)];
+ final float h0 = h / mZoom;
+ final float dh = h - h0;
+ final float ramp = FISHEYE_RAMP_WIDTH;
+ mMeshLeft = new float[2 * (mMeshWidth + 1) * (mMeshHeight + 1)];
+ mMeshRight = new float[2 * (mMeshWidth + 1) * (mMeshHeight + 1)];
for (int i = 0; i < 2 * (mMeshWidth + 1) * (mMeshHeight + 1); i += 2) {
// Calculates X value.
final int colIndex = i % (2 * (mMeshWidth + 1)) / 2;
- if (colIndex == 0) {
- mMesh[i] = 0;
- } else if (colIndex == mMeshWidth) {
- mMesh[i] = w;
- } else {
- mMesh[i] = (colIndex - 1) * (w - 2 * dx) / (mMeshWidth - 2) + dx;
- }
+ mMeshLeft[i] = (float) colIndex * ramp / mMeshWidth;
+ mMeshRight[i] = w - ramp + colIndex * ramp / mMeshWidth;
+
// Calculates Y value.
final int rowIndex = i / 2 / (mMeshWidth + 1);
- final float y0 = colIndex == 0 || colIndex == mMeshWidth
- ? (h - h / mZoom) / 2 : 0;
- final float dy = colIndex == 0 || colIndex == mMeshWidth
- ? h / mZoom / mMeshHeight : h / mMeshHeight;
- mMesh[i + 1] = y0 + rowIndex * dy;
+ final float hl = h0 + dh * colIndex / mMeshWidth;
+ final float yl = (h - hl) / 2;
+ mMeshLeft[i + 1] = yl + hl * rowIndex / mMeshHeight;
+ final float hr = h - dh * colIndex / mMeshWidth;
+ final float yr = (h - hr) / 2;
+ mMeshRight[i + 1] = yr + hr * rowIndex / mMeshHeight;
}
}
@@ -1166,14 +1169,31 @@ public final class Magnifier {
final RecordingCanvas canvas =
mBitmapRenderNode.beginRecording(mContentWidth, mContentHeight);
try {
+ final int w = mBitmap.getWidth();
+ final int h = mBitmap.getHeight();
+ final Paint paint = new Paint();
+ paint.setFilterBitmap(true);
if (mIsFishEyeStyle) {
+ final int ramp = FISHEYE_RAMP_WIDTH;
+ final int margin =
+ (int)((mContentWidth - (mContentWidth - 2 * ramp) / mZoom) / 2);
+
+ // Draws the middle part.
+ final Rect srcRect = new Rect(margin, 0, w - margin, h);
+ final Rect dstRect = new Rect(
+ ramp, 0, mContentWidth - ramp, mContentHeight);
+ canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
+
+ // Draws the left/right parts with mesh matrixes.
+ canvas.drawBitmapMesh(
+ Bitmap.createBitmap(mBitmap, 0, 0, margin, h),
+ mMeshWidth, mMeshHeight, mMeshLeft, 0, null, 0, paint);
canvas.drawBitmapMesh(
- mBitmap, mMeshWidth, mMeshHeight, mMesh, 0, null, 0, null);
+ Bitmap.createBitmap(mBitmap, w - margin, 0, margin, h),
+ mMeshWidth, mMeshHeight, mMeshRight, 0, null, 0, paint);
} else {
- final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ final Rect srcRect = new Rect(0, 0, w, h);
final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
- final Paint paint = new Paint();
- paint.setFilterBitmap(true);
canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
}
} finally {
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
index a82326f67d74..23fcf6ebc2cc 100644
--- a/core/proto/android/server/connectivity/data_stall_event.proto
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -34,7 +34,7 @@ enum ApBand {
AP_BAND_5GHZ = 2;
}
-// Refer to definition in ServiceState.java.
+// Refer to definition in TelephonyManager.java.
enum RadioTech {
RADIO_TECHNOLOGY_UNKNOWN = 0;
RADIO_TECHNOLOGY_GPRS = 1;
@@ -49,8 +49,8 @@ enum RadioTech {
RADIO_TECHNOLOGY_HSUPA = 10;
RADIO_TECHNOLOGY_HSPA = 11;
RADIO_TECHNOLOGY_EVDO_B = 12;
- RADIO_TECHNOLOGY_EHRPD = 13;
- RADIO_TECHNOLOGY_LTE = 14;
+ RADIO_TECHNOLOGY_LTE = 13;
+ RADIO_TECHNOLOGY_EHRPD = 14;
RADIO_TECHNOLOGY_HSPAP = 15;
RADIO_TECHNOLOGY_GSM = 16;
RADIO_TECHNOLOGY_TD_SCDMA = 17;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6df0161c3ca6..0e7214a4f3d3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4018,9 +4018,9 @@
statistics
<p>Declaring the permission implies intention to use the API and the user of the
device can grant permission through the Settings application.
- <p>Protection level: signature|privileged|development|appop -->
+ <p>Protection level: signature|privileged|development|appop|retailDemo -->
<permission android:name="android.permission.PACKAGE_USAGE_STATS"
- android:protectionLevel="signature|privileged|development|appop" />
+ android:protectionLevel="signature|privileged|development|appop|retailDemo" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
diff --git a/core/res/res/values-mcc334/config.xml b/core/res/res/values-mcc334/config.xml
new file mode 100644
index 000000000000..e99c9a028178
--- /dev/null
+++ b/core/res/res/values-mcc334/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc732/config.xml b/core/res/res/values-mcc732/config.xml
new file mode 100644
index 000000000000..e99c9a028178
--- /dev/null
+++ b/core/res/res/values-mcc732/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc740/config.xml b/core/res/res/values-mcc740/config.xml
new file mode 100644
index 000000000000..e99c9a028178
--- /dev/null
+++ b/core/res/res/values-mcc740/config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 059bc443a609..b22e1867f257 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,6 +301,9 @@
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
+ <!-- Additional flag from base permission type: this permission will be granted to the
+ retail demo app, as defined by the OEM. -->
+ <flag name="retailDemo" value="0x1000000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 52b92d2660da..612fc928dae0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3590,7 +3590,6 @@
-->
<string name="config_defaultWellbeingPackage" translatable="false"></string>
-
<!-- The package name for the system telephony apps.
This package must be trusted, as it will be granted with permissions with special telephony
protection level. Note, framework by default support multiple telephony apps, each package
@@ -3658,6 +3657,19 @@
-->
<string name="config_defaultContentSuggestionsService" translatable="false"></string>
+ <!-- The package name for the default retail demo app.
+ This package must be trusted, as it has the permissions to query the usage stats on the
+ device.
+ Example: "com.google.android.retaildemo"
+ -->
+ <string name="config_retailDemoPackage" translatable="false"></string>
+
+ <!-- The package signature hash for the default retail demo app.
+ This package must be trusted, as it has the permissions to query the usage stats on the
+ device.
+ -->
+ <string name="config_retailDemoPackageSignature" translatable="false"></string>
+
<!-- Whether the device uses the default focus highlight when focus state isn't specified. -->
<bool name="config_useDefaultFocusHighlight">true</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 0b5082cdaf94..22abedcac2a7 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -33,9 +33,11 @@
<dimen name="toast_y_offset">24dp</dimen>
<!-- Height of the status bar -->
<dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
- <!-- Height of the status bar in portrait -->
+ <!-- Height of the status bar in portrait. The height should be
+ Max((status bar content height + waterfall top size), top cutout size) -->
<dimen name="status_bar_height_portrait">24dp</dimen>
- <!-- Height of the status bar in landscape -->
+ <!-- Height of the status bar in landscape. The height should be
+ Max((status bar content height + waterfall top size), top cutout size) -->
<dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen>
<!-- Height of area above QQS where battery/time go -->
<dimen name="quick_qs_offset_height">48dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 21d1d3cf9c89..d676150a4ba3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3396,6 +3396,8 @@
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultSystemCaptionsService" />
<java-symbol type="string" name="config_defaultSystemCaptionsManagerService" />
+ <java-symbol type="string" name="config_retailDemoPackage" />
+ <java-symbol type="string" name="config_retailDemoPackageSignature" />
<java-symbol type="string" name="notification_channel_foreground_service" />
<java-symbol type="string" name="foreground_service_app_in_background" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 692af584cd61..a676fa2b2a83 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -400,5 +400,6 @@ applications that come with the platform
</privapp-permissions>
<privapp-permissions package="com.android.settings">
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
+ <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
</privapp-permissions>
</permissions>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index a784e04ee6a0..ddb7341b7366 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -45,6 +46,7 @@ public class A2dpProfile implements LocalBluetoothProfile {
private boolean mIsProfileReady;
private final CachedBluetoothDeviceManager mDeviceManager;
+ private final BluetoothAdapter mBluetoothAdapter;
static final ParcelUuid[] SINK_UUIDS = {
BluetoothUuid.A2DP_SINK,
@@ -99,7 +101,8 @@ public class A2dpProfile implements LocalBluetoothProfile {
mContext = context;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new A2dpServiceListener(),
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mBluetoothAdapter.getProfileProxy(context, new A2dpServiceListener(),
BluetoothProfile.A2DP);
}
@@ -173,8 +176,10 @@ public class A2dpProfile implements LocalBluetoothProfile {
}
public boolean setActiveDevice(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.setActiveDevice(device);
+ if (mBluetoothAdapter == null) {
+ return false;
+ }
+ return mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_AUDIO);
}
public BluetoothDevice getActiveDevice() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index d65b5da22056..218d0b2dc2c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -45,6 +46,7 @@ public class HeadsetProfile implements LocalBluetoothProfile {
private final CachedBluetoothDeviceManager mDeviceManager;
private final LocalBluetoothProfileManager mProfileManager;
+ private final BluetoothAdapter mBluetoothAdapter;
static final ParcelUuid[] UUIDS = {
BluetoothUuid.HSP,
@@ -99,7 +101,8 @@ public class HeadsetProfile implements LocalBluetoothProfile {
LocalBluetoothProfileManager profileManager) {
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new HeadsetServiceListener(),
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mBluetoothAdapter.getProfileProxy(context, new HeadsetServiceListener(),
BluetoothProfile.HEADSET);
}
@@ -134,10 +137,10 @@ public class HeadsetProfile implements LocalBluetoothProfile {
}
public boolean setActiveDevice(BluetoothDevice device) {
- if (mService == null) {
+ if (mBluetoothAdapter == null) {
return false;
}
- return mService.setActiveDevice(device);
+ return mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_PHONE_CALL);
}
public BluetoothDevice getActiveDevice() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 9f1af669c708..b82fb37a770f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
@@ -45,6 +46,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
static final String NAME = "HearingAid";
private final LocalBluetoothProfileManager mProfileManager;
+ private final BluetoothAdapter mBluetoothAdapter;
// Order of this profile in device profiles list
private static final int ORDINAL = 1;
@@ -97,7 +99,8 @@ public class HearingAidProfile implements LocalBluetoothProfile {
mContext = context;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mBluetoothAdapter.getProfileProxy(context,
new HearingAidServiceListener(), BluetoothProfile.HEARING_AID);
}
@@ -171,8 +174,10 @@ public class HearingAidProfile implements LocalBluetoothProfile {
}
public boolean setActiveDevice(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.setActiveDevice(device);
+ if (mBluetoothAdapter == null) {
+ return false;
+ }
+ return mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_ALL);
}
public List<BluetoothDevice> getActiveDevices() {
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index eab970626bf1..924d16dd27d7 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -89,7 +89,7 @@ public class ForegroundServiceNotificationListener {
}
@Override
- public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+ public void onEntryRemoved(NotificationEntry entry, int reason) {
removeNotification(entry.getSbn());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index eaa9d78c08f4..7fe229c26f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.collection;
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
+
import java.util.Arrays;
import java.util.List;
@@ -132,8 +135,20 @@ public class ListDumper {
.append(" ");
}
+ if (notifEntry.mCancellationReason != REASON_NOT_CANCELED) {
+ rksb.append("cancellationReason=")
+ .append(notifEntry.mCancellationReason)
+ .append(" ");
+ }
+
if (notifEntry.hasInflationError()) {
- rksb.append("hasInflationError ");
+ rksb.append("(!)hasInflationError ");
+ }
+
+ if (notifEntry.getDismissState() != NOT_DISMISSED) {
+ rksb.append("dismissState=")
+ .append(notifEntry.getDismissState())
+ .append(" ");
}
String rkString = rksb.toString();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 1b6170326bac..3b2fe9441c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -35,9 +35,14 @@ import static android.service.notification.NotificationListenerService.REASON_TI
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
+
+import static java.util.Objects.requireNonNull;
+
import android.annotation.IntDef;
import android.annotation.MainThread;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking;
@@ -45,6 +50,8 @@ import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import androidx.annotation.NonNull;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
@@ -172,15 +179,65 @@ public class NotifCollection implements Dumpable {
/**
* Dismiss a notification on behalf of the user.
*/
- void dismissNotification(
- NotificationEntry entry,
- @CancellationReason int reason,
- @NonNull DismissedByUserStats stats) {
+ void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
Assert.isMainThread();
- Objects.requireNonNull(stats);
+ requireNonNull(stats);
checkForReentrantCall();
- removeNotification(entry.getKey(), null, reason, stats);
+ if (entry != mNotificationSet.get(entry.getKey())) {
+ throw new IllegalStateException("Invalid entry: " + entry.getKey());
+ }
+
+ if (entry.getDismissState() == DISMISSED) {
+ return;
+ }
+
+ // Optimistically mark the notification as dismissed -- we'll wait for the signal from
+ // system server before removing it from our notification set.
+ entry.setDismissState(DISMISSED);
+ mLogger.logNotifDismissed(entry.getKey());
+
+ List<NotificationEntry> canceledEntries = new ArrayList<>();
+
+ if (isCanceled(entry)) {
+ canceledEntries.add(entry);
+ } else {
+ // Ask system server to remove it for us
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getTag(),
+ entry.getSbn().getId(),
+ entry.getSbn().getUser().getIdentifier(),
+ entry.getSbn().getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+
+ // Also mark any children as dismissed as system server will auto-dismiss them as well
+ if (entry.getSbn().getNotification().isGroupSummary()) {
+ for (NotificationEntry otherEntry : mNotificationSet.values()) {
+ if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey())
+ && otherEntry.getDismissState() != DISMISSED) {
+ otherEntry.setDismissState(PARENT_DISMISSED);
+ if (isCanceled(otherEntry)) {
+ canceledEntries.add(otherEntry);
+ }
+ }
+ }
+ }
+ }
+
+ // Immediately remove any dismissed notifs that have already been canceled by system server
+ // (probably due to being lifetime-extended up until this point).
+ for (NotificationEntry canceledEntry : canceledEntries) {
+ tryRemoveNotification(canceledEntry);
+ }
+
+ rebuildList();
}
private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -208,7 +265,15 @@ public class NotifCollection implements Dumpable {
Assert.isMainThread();
mLogger.logNotifRemoved(sbn.getKey(), reason);
- removeNotification(sbn.getKey(), rankingMap, reason, null);
+
+ final NotificationEntry entry = mNotificationSet.get(sbn.getKey());
+ if (entry == null) {
+ throw new IllegalStateException("No notification to remove with key " + sbn.getKey());
+ }
+ entry.mCancellationReason = reason;
+ applyRanking(rankingMap);
+ tryRemoveNotification(entry);
+ rebuildList();
}
private void onNotificationRankingUpdate(RankingMap rankingMap) {
@@ -242,9 +307,12 @@ public class NotifCollection implements Dumpable {
// Update to an existing entry
mLogger.logNotifUpdated(sbn.getKey());
- // Notification is updated so it is essentially re-added and thus alive again. Don't
+ cancelLocalDismissal(entry);
+
+ // Notification is updated so it is essentially re-added and thus alive again. Don't
// need to keep its lifetime extended.
cancelLifetimeExtension(entry);
+ entry.mCancellationReason = REASON_NOT_CANCELED;
entry.setSbn(sbn);
if (rankingMap != null) {
@@ -255,59 +323,42 @@ public class NotifCollection implements Dumpable {
}
}
- private void removeNotification(
- String key,
- @Nullable RankingMap rankingMap,
- @CancellationReason int reason,
- @Nullable DismissedByUserStats dismissedByUserStats) {
+ /**
+ * Tries to remove a notification from the notification set. This removal may be blocked by
+ * lifetime extenders. Does not trigger a rebuild of the list; caller must do that manually.
+ *
+ * @return True if the notification was removed, false otherwise.
+ */
+ private boolean tryRemoveNotification(NotificationEntry entry) {
+ if (mNotificationSet.get(entry.getKey()) != entry) {
+ throw new IllegalStateException("No notification to remove with key " + entry.getKey());
+ }
- NotificationEntry entry = mNotificationSet.get(key);
- if (entry == null) {
- throw new IllegalStateException("No notification to remove with key " + key);
+ if (!isCanceled(entry)) {
+ throw new IllegalStateException("Cannot remove notification " + entry.getKey()
+ + ": has not been marked for removal");
}
- entry.mLifetimeExtenders.clear();
- mAmDispatchingToOtherCode = true;
- for (NotifLifetimeExtender extender : mLifetimeExtenders) {
- if (extender.shouldExtendLifetime(entry, reason)) {
- entry.mLifetimeExtenders.add(extender);
- }
+ if (isDismissedByUser(entry)) {
+ // User-dismissed notifications cannot be lifetime-extended
+ cancelLifetimeExtension(entry);
+ } else {
+ updateLifetimeExtension(entry);
}
- mAmDispatchingToOtherCode = false;
if (!isLifetimeExtended(entry)) {
mNotificationSet.remove(entry.getKey());
-
- if (dismissedByUserStats != null) {
- try {
- mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getTag(),
- entry.getSbn().getId(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
- dismissedByUserStats.dismissalSurface,
- dismissedByUserStats.dismissalSentiment,
- dismissedByUserStats.notificationVisibility);
- } catch (RemoteException e) {
- // system process is dead if we're here.
- }
- }
-
- if (rankingMap != null) {
- applyRanking(rankingMap);
- }
-
- dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */);
+ dispatchOnEntryRemoved(entry, entry.mCancellationReason);
dispatchOnEntryCleanUp(entry);
+ return true;
+ } else {
+ return false;
}
-
- rebuildList();
}
private void applyRanking(@NonNull RankingMap rankingMap) {
for (NotificationEntry entry : mNotificationSet.values()) {
- if (!isLifetimeExtended(entry)) {
+ if (!isCanceled(entry)) {
// TODO: (b/148791039) We should crash if we are ever handed a ranking with
// incomplete entries. Right now, there's a race condition in NotificationListener
@@ -355,9 +406,9 @@ public class NotifCollection implements Dumpable {
}
if (!isLifetimeExtended(entry)) {
- // TODO: This doesn't need to be undefined -- we can set either EXTENDER_EXPIRED or
- // save the original reason
- removeNotification(entry.getKey(), null, REASON_UNKNOWN, null);
+ if (tryRemoveNotification(entry)) {
+ rebuildList();
+ }
}
}
@@ -374,6 +425,31 @@ public class NotifCollection implements Dumpable {
return entry.mLifetimeExtenders.size() > 0;
}
+ private void updateLifetimeExtension(NotificationEntry entry) {
+ entry.mLifetimeExtenders.clear();
+ mAmDispatchingToOtherCode = true;
+ for (NotifLifetimeExtender extender : mLifetimeExtenders) {
+ if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) {
+ entry.mLifetimeExtenders.add(extender);
+ }
+ }
+ mAmDispatchingToOtherCode = false;
+ }
+
+ private void cancelLocalDismissal(NotificationEntry entry) {
+ if (isDismissedByUser(entry)) {
+ entry.setDismissState(NOT_DISMISSED);
+ if (entry.getSbn().getNotification().isGroupSummary()) {
+ for (NotificationEntry otherEntry : mNotificationSet.values()) {
+ if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey())
+ && otherEntry.getDismissState() == PARENT_DISMISSED) {
+ otherEntry.setDismissState(NOT_DISMISSED);
+ }
+ }
+ }
+ }
+ }
+
private void checkForReentrantCall() {
if (mAmDispatchingToOtherCode) {
throw new IllegalStateException("Reentrant call detected");
@@ -389,6 +465,19 @@ public class NotifCollection implements Dumpable {
return ranking;
}
+ /**
+ * True if the notification has been canceled by system server. Usually, such notifications are
+ * immediately removed from the collection, but can sometimes stick around due to lifetime
+ * extenders.
+ */
+ private static boolean isCanceled(NotificationEntry entry) {
+ return entry.mCancellationReason != REASON_NOT_CANCELED;
+ }
+
+ private static boolean isDismissedByUser(NotificationEntry entry) {
+ return entry.getDismissState() != NOT_DISMISSED;
+ }
+
private void dispatchOnEntryInit(NotificationEntry entry) {
mAmDispatchingToOtherCode = true;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -421,13 +510,10 @@ public class NotifCollection implements Dumpable {
mAmDispatchingToOtherCode = false;
}
- private void dispatchOnEntryRemoved(
- NotificationEntry entry,
- @CancellationReason int reason,
- boolean removedByUser) {
+ private void dispatchOnEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
mAmDispatchingToOtherCode = true;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryRemoved(entry, reason, removedByUser);
+ listener.onEntryRemoved(entry, reason);
}
mAmDispatchingToOtherCode = false;
}
@@ -439,6 +525,20 @@ public class NotifCollection implements Dumpable {
}
mAmDispatchingToOtherCode = false;
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
+ final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+ pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+ if (entries.size() == 0) {
+ pw.println("\t\t None");
+ }
+ pw.println(
+ ListDumper.dumpList(
+ entries,
+ true,
+ "\t\t"));
+ }
private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() {
@Override
@@ -494,20 +594,6 @@ public class NotifCollection implements Dumpable {
@Retention(RetentionPolicy.SOURCE)
public @interface CancellationReason {}
+ public static final int REASON_NOT_CANCELED = -1;
public static final int REASON_UNKNOWN = 0;
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
-
- pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
- if (entries.size() == 0) {
- pw.println("\t\t None");
- }
- pw.println(
- ListDumper.dumpList(
- entries,
- true,
- "\t\t"));
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index e7b772f1c7b2..7a6d4f1c7d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -98,13 +98,12 @@ public class NotifInflaterImpl implements NotifInflater {
@Override
public void run() {
int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- /**
+ /*
* TODO: determine dismissal surface (ie: shade / headsup / aod)
* see {@link NotificationLogger#logNotificationClear}
*/
mNotifCollection.dismissNotification(
entry,
- 0,
new DismissedByUserStats(
dismissalSurface,
DISMISS_SENTIMENT_NEUTRAL,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 1f77ec229041..a95668b5e48d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -29,9 +29,11 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
-import android.annotation.NonNull;
+import static java.util.Objects.requireNonNull;
+
import android.app.Notification;
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
@@ -50,6 +52,7 @@ import android.util.ArraySet;
import android.view.View;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -59,6 +62,7 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -105,9 +109,18 @@ public final class NotificationEntry extends ListEntry {
/** If this was a group child that was promoted to the top level, then who did the promoting. */
@Nullable NotifPromoter mNotifPromoter;
- /** If this notification had an issue with inflating. Only used with the NewNotifPipeline **/
+ /**
+ * If this notification was cancelled by system server, then the reason that was supplied.
+ * Uncancelled notifications always have REASON_NOT_CANCELED. Note that lifetime-extended
+ * notifications will have this set even though they are still in the active notification set.
+ */
+ @CancellationReason int mCancellationReason = REASON_NOT_CANCELED;
+
+ /** @see #hasInflationError() */
private boolean mHasInflationError;
+ /** @see #getDismissState() */
+ @NonNull private DismissState mDismissState = DismissState.NOT_DISMISSED;
/*
* Old members
@@ -169,9 +182,9 @@ public final class NotificationEntry extends ListEntry {
public NotificationEntry(
@NonNull StatusBarNotification sbn,
@NonNull Ranking ranking) {
- super(Objects.requireNonNull(Objects.requireNonNull(sbn).getKey()));
+ super(requireNonNull(requireNonNull(sbn).getKey()));
- Objects.requireNonNull(ranking);
+ requireNonNull(ranking);
mKey = sbn.getKey();
setSbn(sbn);
@@ -201,8 +214,8 @@ public final class NotificationEntry extends ListEntry {
* TODO: Make this package-private
*/
public void setSbn(@NonNull StatusBarNotification sbn) {
- Objects.requireNonNull(sbn);
- Objects.requireNonNull(sbn.getKey());
+ requireNonNull(sbn);
+ requireNonNull(sbn.getKey());
if (!sbn.getKey().equals(mKey)) {
throw new IllegalArgumentException("New key " + sbn.getKey()
@@ -227,8 +240,8 @@ public final class NotificationEntry extends ListEntry {
* TODO: Make this package-private
*/
public void setRanking(@NonNull Ranking ranking) {
- Objects.requireNonNull(ranking);
- Objects.requireNonNull(ranking.getKey());
+ requireNonNull(ranking);
+ requireNonNull(ranking.getKey());
if (!ranking.getKey().equals(mKey)) {
throw new IllegalArgumentException("New key " + ranking.getKey()
@@ -239,6 +252,34 @@ public final class NotificationEntry extends ListEntry {
}
/*
+ * Bookkeeping getters and setters
+ */
+
+ /**
+ * Whether this notification had an error when attempting to inflate. This is only used in
+ * the NewNotifPipeline
+ */
+ public boolean hasInflationError() {
+ return mHasInflationError;
+ }
+
+ void setHasInflationError(boolean hasError) {
+ mHasInflationError = hasError;
+ }
+
+ /**
+ * Set if the user has dismissed this notif but we haven't yet heard back from system server to
+ * confirm the dismissal.
+ */
+ @NonNull public DismissState getDismissState() {
+ return mDismissState;
+ }
+
+ void setDismissState(@NonNull DismissState dismissState) {
+ mDismissState = requireNonNull(dismissState);
+ }
+
+ /*
* Convenience getters for SBN and Ranking members
*/
@@ -275,7 +316,6 @@ public final class NotificationEntry extends ListEntry {
return mRanking.canBubble();
}
-
public @NonNull List<Notification.Action> getSmartActions() {
return mRanking.getSmartActions();
}
@@ -578,18 +618,6 @@ public final class NotificationEntry extends ListEntry {
remoteInputTextWhenReset = null;
}
- void setHasInflationError(boolean hasError) {
- mHasInflationError = hasError;
- }
-
- /**
- * Whether this notification had an error when attempting to inflate. This is only used in
- * the NewNotifPipeline
- */
- public boolean hasInflationError() {
- return mHasInflationError;
- }
-
public void setHasSentReply() {
hasSentReply = true;
}
@@ -974,6 +1002,16 @@ public final class NotificationEntry extends ListEntry {
}
}
+ /** @see #getDismissState() */
+ public enum DismissState {
+ /** User has not dismissed this notif or its parent */
+ NOT_DISMISSED,
+ /** User has dismissed this notif specifically */
+ DISMISSED,
+ /** User has dismissed this notif's parent (which implicitly dismisses this one as well) */
+ PARENT_DISMISSED,
+ }
+
private static final long LAUNCH_COOLDOWN = 2000;
private static final long REMOTE_INPUT_COOLDOWN = 500;
private static final long INITIALIZATION_DELAY = 400;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
new file mode 100644
index 000000000000..0059e7baa3c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideLocallyDismissedNotifsCoordinator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+/**
+ * Filters out notifications that have been dismissed locally (by the user) but that system server
+ * hasn't yet confirmed the removal of.
+ */
+public class HideLocallyDismissedNotifsCoordinator implements Coordinator {
+ @Override
+ public void attach(NotifPipeline pipeline) {
+ pipeline.addPreGroupFilter(mFilter);
+ }
+
+ private final NotifFilter mFilter = new NotifFilter("HideLocallyDismissedNotifsFilter") {
+ @Override
+ public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ return entry.getDismissState() != NOT_DISMISSED;
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 8d0dd5b111ba..0a1e09f4c99d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -56,6 +56,7 @@ public class NotifCoordinators implements Dumpable {
PreparationCoordinator preparationCoordinator) {
dumpController.registerDumpable(TAG, this);
+ mCoordinators.add(new HideLocallyDismissedNotifsCoordinator());
mCoordinators.add(keyguardCoordinator);
mCoordinators.add(rankingCoordinator);
mCoordinators.add(foregroundCoordinator);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 20c9cbc8790d..41314b86695a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -72,7 +72,7 @@ public class PreparationCoordinator implements Coordinator {
}
@Override
- public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+ public void onEntryRemoved(NotificationEntry entry, int reason) {
abortInflation(entry, "entryRemoved reason=" + reason);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index ff6da12bd0bc..b2c53dae16cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -54,10 +54,7 @@ public interface NotifCollectionListener {
* immediately after a user dismisses a notification: we wait until we receive confirmation from
* system server before considering the notification removed.
*/
- default void onEntryRemoved(
- NotificationEntry entry,
- @CancellationReason int reason,
- boolean removedByUser) {
+ default void onEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 0d0a46adb41f..14e15031056f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -61,6 +61,14 @@ class NotifCollectionLogger @Inject constructor(
})
}
+ fun logNotifDismissed(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "DISMISSED $str1"
+ })
+ }
+
fun logRankingMissing(key: String, rankingMap: RankingMap) {
buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
index 264a683f6a3d..64b0b66a47a9 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -31,9 +31,11 @@ import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.SystemProperties;
import android.provider.Settings;
+import android.util.RotationUtils;
import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
@@ -43,8 +45,6 @@ import android.view.Surface;
import com.android.internal.R;
-import java.util.List;
-
/**
* Contains information about the layout-properties of a display. This refers to internal layout
* like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
@@ -323,28 +323,38 @@ public class DisplayLayout {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return null;
}
+ final Insets waterfallInsets =
+ RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
if (rotation == ROTATION_0) {
- return computeSafeInsets(
- cutout, displayWidth, displayHeight);
+ return computeSafeInsets(cutout, displayWidth, displayHeight);
}
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- Rect[] cutoutRects = computeSafeInsets(cutout, displayWidth, displayHeight)
- .getBoundingRectsAll();
+ Rect[] cutoutRects = cutout.getBoundingRectsAll();
final Rect[] newBounds = new Rect[cutoutRects.length];
final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
for (int i = 0; i < cutoutRects.length; ++i) {
- newBounds[i] = new Rect(cutoutRects[i]);
- rotateBounds(newBounds[i], displayBounds, rotation);
+ final Rect rect = new Rect(cutoutRects[i]);
+ if (!rect.isEmpty()) {
+ rotateBounds(rect, displayBounds, rotation);
+ }
+ newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
}
- return computeSafeInsets(DisplayCutout.fromBounds(newBounds),
+ return computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
rotated ? displayHeight : displayWidth,
rotated ? displayWidth : displayHeight);
}
+ private static int getBoundIndexFromRotation(int index, int rotation) {
+ return (index - rotation) < 0
+ ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
+ : index - rotation;
+ }
+
/** Calculate safe insets. */
public static DisplayCutout computeSafeInsets(DisplayCutout inner,
int displayWidth, int displayHeight) {
- if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+ if (inner == DisplayCutout.NO_CUTOUT) {
return null;
}
@@ -353,58 +363,44 @@ public class DisplayLayout {
return inner.replaceSafeInsets(safeInsets);
}
- private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
- if (displaySize.getWidth() < displaySize.getHeight()) {
- final List<Rect> boundingRects = cutout.replaceSafeInsets(
- new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
- .getBoundingRects();
- int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
- int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
- return new Rect(0, topInset, 0, bottomInset);
- } else if (displaySize.getWidth() > displaySize.getHeight()) {
- final List<Rect> boundingRects = cutout.replaceSafeInsets(
- new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
- .getBoundingRects();
- int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
- int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
- return new Rect(leftInset, 0, right, 0);
- } else {
+ private static Rect computeSafeInsets(
+ Size displaySize, DisplayCutout cutout) {
+ if (displaySize.getWidth() == displaySize.getHeight()) {
throw new UnsupportedOperationException("not implemented: display=" + displaySize
+ " cutout=" + cutout);
}
+
+ int leftInset = Math.max(cutout.getWaterfallInsets().left,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
+ int topInset = Math.max(cutout.getWaterfallInsets().top,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
+ int rightInset = Math.max(cutout.getWaterfallInsets().right,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
+ int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
+ Gravity.BOTTOM));
+
+ return new Rect(leftInset, topInset, rightInset, bottomInset);
}
- private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
+ private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
+ if (boundingRect.isEmpty()) {
+ return 0;
+ }
+
int inset = 0;
- final int size = boundingRects.size();
- for (int i = 0; i < size; i++) {
- Rect boundingRect = boundingRects.get(i);
- switch (gravity) {
- case Gravity.TOP:
- if (boundingRect.top == 0) {
- inset = Math.max(inset, boundingRect.bottom);
- }
- break;
- case Gravity.BOTTOM:
- if (boundingRect.bottom == display.getHeight()) {
- inset = Math.max(inset, display.getHeight() - boundingRect.top);
- }
- break;
- case Gravity.LEFT:
- if (boundingRect.left == 0) {
- inset = Math.max(inset, boundingRect.right);
- }
- break;
- case Gravity.RIGHT:
- if (boundingRect.right == display.getWidth()) {
- inset = Math.max(inset, display.getWidth() - boundingRect.left);
- }
- break;
- default:
- throw new IllegalArgumentException("unknown gravity: " + gravity);
- }
+ switch (gravity) {
+ case Gravity.TOP:
+ return Math.max(inset, boundingRect.bottom);
+ case Gravity.BOTTOM:
+ return Math.max(inset, display.getHeight() - boundingRect.top);
+ case Gravity.LEFT:
+ return Math.max(inset, boundingRect.right);
+ case Gravity.RIGHT:
+ return Math.max(inset, display.getWidth() - boundingRect.left);
+ default:
+ throw new IllegalArgumentException("unknown gravity: " + gravity);
}
- return inset;
}
static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index b65298097ff1..7c94ed20e95a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -17,9 +17,16 @@
package com.android.systemui.statusbar.notification.collection;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -40,7 +47,6 @@ import static java.util.Objects.requireNonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationStats;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
@@ -77,6 +83,7 @@ import org.mockito.Spy;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -236,7 +243,7 @@ public class NotifCollectionTest extends SysuiTestCase {
mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
// THEN the listener is notified
- verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false);
+ verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
verify(mCollectionListener).onEntryCleanUp(entry);
assertEquals(notif.sbn, entry.getSbn());
assertEquals(notif.ranking, entry.getRanking());
@@ -322,24 +329,15 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
- public void testDismissNotification() throws RemoteException {
- // GIVEN a collection with a couple notifications and a lifetime extender
- mCollection.addNotificationLifetimeExtender(mExtender1);
-
+ public void testDismissNotificationSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
// WHEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry2.getKey(), 7, 2, true));
-
- mCollection.dismissNotification(entry2, REASON_CLICK, stats);
-
- // THEN we check for lifetime extension
- verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK);
+ DismissedByUserStats stats = defaultStats(entry2);
+ mCollection.dismissNotification(entry2, defaultStats(entry2));
// THEN we send the dismissal to system server
verify(mStatusBarService).onNotificationClear(
@@ -351,9 +349,96 @@ public class NotifCollectionTest extends SysuiTestCase {
stats.dismissalSurface,
stats.dismissalSentiment,
stats.notificationVisibility);
+ }
+
+ @Test
+ public void testDismissedNotificationsAreMarkedAsDismissedLocally() {
+ // GIVEN a collection with a notification
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN a notification is manually dismissed
+ mCollection.dismissNotification(entry1, defaultStats(entry1));
- // THEN we fire a remove event
- verify(mCollectionListener).onEntryRemoved(entry2, REASON_CLICK, true);
+ // THEN the entry is marked as dismissed locally
+ assertEquals(DISMISSED, entry1.getDismissState());
+ }
+
+ @Test
+ public void testDismissedNotificationsCannotBeLifetimeExtended() {
+ // GIVEN a collection with a notification and a lifetime extender
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN a notification is manually dismissed
+ mCollection.dismissNotification(entry1, defaultStats(entry1));
+
+ // THEN lifetime extenders are never queried
+ verify(mExtender1, never()).shouldExtendLifetime(eq(entry1), anyInt());
+ }
+
+ @Test
+ public void testDismissedNotificationsDoNotTriggerRemovalEvents() {
+ // GIVEN a collection with a notification
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN a notification is manually dismissed
+ mCollection.dismissNotification(entry1, defaultStats(entry1));
+
+ // THEN onEntryRemoved is not called
+ verify(mCollectionListener, never()).onEntryRemoved(eq(entry1), anyInt());
+ }
+
+ @Test
+ public void testDismissedNotificationsStillAppearInNotificationSet() {
+ // GIVEN a collection with a notification
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN a notification is manually dismissed
+ mCollection.dismissNotification(entry1, defaultStats(entry1));
+
+ // THEN the dismissed entry still appears in the notification set
+ assertEquals(
+ new ArraySet<>(Collections.singletonList(entry1)),
+ new ArraySet<>(mCollection.getActiveNotifs()));
+ }
+
+ @Test
+ public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() {
+ // GIVEN A notif group with one summary and two children
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")
+ .setGroup(mContext, GROUP_1));
+ NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag")
+ .setGroup(mContext, GROUP_1));
+
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key);
+
+ // GIVEN that the summary and one child are retracted, but both are lifetime-extended
+ mExtender1.shouldExtendLifetime = true;
+ mNoMan.retractNotif(notif1.sbn, REASON_CANCEL);
+ mNoMan.retractNotif(notif2.sbn, REASON_CANCEL);
+ assertEquals(
+ new ArraySet<>(List.of(entry1, entry2, entry3)),
+ new ArraySet<>(mCollection.getActiveNotifs()));
+
+ // WHEN the summary is dismissed by the user
+ mCollection.dismissNotification(entry1, defaultStats(entry1));
+
+ // THEN the summary is removed, but both children stick around
+ assertEquals(
+ new ArraySet<>(List.of(entry2, entry3)),
+ new ArraySet<>(mCollection.getActiveNotifs()));
+ assertEquals(NOT_DISMISSED, entry2.getDismissState());
+ assertEquals(NOT_DISMISSED, entry3.getDismissState());
}
@Test(expected = IllegalStateException.class)
@@ -366,15 +451,115 @@ public class NotifCollectionTest extends SysuiTestCase {
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
// WHEN we try to dismiss a notification that isn't present
- mCollection.dismissNotification(
- entry2,
- REASON_CLICK,
- new DismissedByUserStats(0, 0, NotificationVisibility.obtain("foo", 47, 3, true)));
+ mCollection.dismissNotification(entry2, defaultStats(entry2));
// THEN an exception is thrown
}
@Test
+ public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
+ // GIVEN a collection with two grouped notifs in it
+ NotifEvent notif0 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0)
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ NotifEvent notif1 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 1)
+ .setGroup(mContext, GROUP_1));
+ NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN the summary is dismissed
+ mCollection.dismissNotification(entry0, defaultStats(entry0));
+
+ // THEN all members of the group are marked as dismissed locally
+ assertEquals(DISMISSED, entry0.getDismissState());
+ assertEquals(PARENT_DISMISSED, entry1.getDismissState());
+ }
+
+ @Test
+ public void testUpdatingDismissedSummaryBringsChildrenBack() {
+ // GIVEN a collection with two grouped notifs in it
+ NotifEvent notif0 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0)
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ NotifEvent notif1 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 1)
+ .setGroup(mContext, GROUP_1));
+ NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN the summary is dismissed but then reposted without a group
+ mCollection.dismissNotification(entry0, defaultStats(entry0));
+ NotifEvent notif0a = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0));
+
+ // THEN it and all of its previous children are no longer dismissed locally
+ assertEquals(NOT_DISMISSED, entry0.getDismissState());
+ assertEquals(NOT_DISMISSED, entry1.getDismissState());
+ }
+
+ @Test
+ public void testDismissedChildrenAreNotResetByParentUpdate() {
+ // GIVEN a collection with three grouped notifs in it
+ NotifEvent notif0 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0)
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ NotifEvent notif1 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 1)
+ .setGroup(mContext, GROUP_1));
+ NotifEvent notif2 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 2)
+ .setGroup(mContext, GROUP_1));
+ NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN a child is dismissed, then the parent is dismissed, then the parent is updated
+ mCollection.dismissNotification(entry1, defaultStats(entry1));
+ mCollection.dismissNotification(entry0, defaultStats(entry0));
+ NotifEvent notif0a = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0));
+
+ // THEN the manually-dismissed child is still marked as dismissed
+ assertEquals(NOT_DISMISSED, entry0.getDismissState());
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(NOT_DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() {
+ // GIVEN a collection with two grouped notifs in it
+ NotifEvent notif0 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0)
+ .setOverrideGroupKey(GROUP_1)
+ .setGroupSummary(mContext, true));
+ NotifEvent notif1 = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 1)
+ .setOverrideGroupKey(GROUP_1));
+ NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+
+ // WHEN the summary is dismissed but then reposted AND in the same update one of the
+ // children's ranking loses its override group
+ mCollection.dismissNotification(entry0, defaultStats(entry0));
+ mNoMan.setRanking(entry1.getKey(), new RankingBuilder()
+ .setKey(entry1.getKey())
+ .build());
+ mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 0)
+ .setOverrideGroupKey(GROUP_1)
+ .setGroupSummary(mContext, true));
+
+ // THEN it and all of its previous children are no longer dismissed locally, including the
+ // child that is no longer part of the group
+ assertEquals(NOT_DISMISSED, entry0.getDismissState());
+ assertEquals(NOT_DISMISSED, entry1.getDismissState());
+ }
+
+ @Test
public void testLifetimeExtendersAreQueriedWhenNotifRemoved() {
// GIVEN a couple notifications and a few lifetime extenders
mExtender1.shouldExtendLifetime = true;
@@ -389,12 +574,12 @@ public class NotifCollectionTest extends SysuiTestCase {
NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
// WHEN a notification is removed
- mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
+ mNoMan.retractNotif(notif2.sbn, REASON_CLICK);
// THEN each extender is asked whether to extend, even if earlier ones return true
- verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
- verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
- verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK);
+ verify(mExtender2).shouldExtendLifetime(entry2, REASON_CLICK);
+ verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK);
// THEN the entry is not removed
assertTrue(mCollection.getActiveNotifs().contains(entry2));
@@ -428,9 +613,9 @@ public class NotifCollectionTest extends SysuiTestCase {
mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
// THEN each extender is re-queried
- verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN);
- verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN);
- verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
+ verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
+ verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
+ verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
// THEN the entry is not removed
assertTrue(mCollection.getActiveNotifs().contains(entry2));
@@ -466,9 +651,9 @@ public class NotifCollectionTest extends SysuiTestCase {
assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN we don't re-query the extenders
- verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
- verify(mExtender2, never()).shouldExtendLifetime(eq(entry2), anyInt());
- verify(mExtender3, never()).shouldExtendLifetime(eq(entry2), anyInt());
+ verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
+ verify(mExtender2, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
+ verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
@@ -501,7 +686,7 @@ public class NotifCollectionTest extends SysuiTestCase {
// THEN the entry removed
assertFalse(mCollection.getActiveNotifs().contains(entry2));
- verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
+ verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN);
}
@Test
@@ -591,6 +776,36 @@ public class NotifCollectionTest extends SysuiTestCase {
assertEquals(notif2a.ranking, entry2.getRanking());
}
+ @Test
+ public void testCancellationReasonIsSetWhenNotifIsCancelled() {
+ // GIVEN a notification
+ NotifEvent notif0 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+ NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
+
+ // WHEN the notification is retracted
+ mNoMan.retractNotif(notif0.sbn, REASON_APP_CANCEL);
+
+ // THEN the retraction reason is stored on the notif
+ assertEquals(REASON_APP_CANCEL, entry0.mCancellationReason);
+ }
+
+ @Test
+ public void testCancellationReasonIsClearedWhenNotifIsUpdated() {
+ // GIVEN a notification and a lifetime extender that will preserve it
+ NotifEvent notif0 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+ NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
+ mCollection.addNotificationLifetimeExtender(mExtender1);
+ mExtender1.shouldExtendLifetime = true;
+
+ // WHEN the notification is retracted and subsequently reposted
+ mNoMan.retractNotif(notif0.sbn, REASON_APP_CANCEL);
+ assertEquals(REASON_APP_CANCEL, entry0.mCancellationReason);
+ mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3));
+
+ // THEN the notification has its cancellation reason cleared
+ assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason);
+ }
+
private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
return new NotificationEntryBuilder()
.setPkg(pkg)
@@ -604,6 +819,13 @@ public class NotifCollectionTest extends SysuiTestCase {
.setId(id);
}
+ private static DismissedByUserStats defaultStats(NotificationEntry entry) {
+ return new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ }
+
private static class RecordingCollectionListener implements NotifCollectionListener {
private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
@@ -621,7 +843,7 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Override
- public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+ public void onEntryRemoved(NotificationEntry entry, int reason) {
}
@Override
@@ -674,4 +896,6 @@ public class NotifCollectionTest extends SysuiTestCase {
private static final String TEST_PACKAGE = "com.android.test.collection";
private static final String TEST_PACKAGE2 = "com.android.test.collection2";
+
+ private static final String GROUP_1 = "group_1";
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 27b6bfb8f5fd..bdcd832e4f4a 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -67,6 +67,7 @@ public abstract class PackageManagerInternal {
public static final int PACKAGE_TELEPHONY = 12;
public static final int PACKAGE_WIFI = 13;
public static final int PACKAGE_COMPANION = 14;
+ public static final int PACKAGE_RETAIL_DEMO = 15;
@IntDef(value = {
INTEGRITY_VERIFICATION_ALLOW,
@@ -105,6 +106,7 @@ public abstract class PackageManagerInternal {
PACKAGE_TELEPHONY,
PACKAGE_WIFI,
PACKAGE_COMPANION,
+ PACKAGE_RETAIL_DEMO,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 79b3a7b0d114..f7d7d6c5d15f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -65,13 +65,13 @@ import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.StatsLog;
import android.util.proto.ProtoOutputStream;
import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1370,8 +1370,8 @@ public class VibratorService extends IVibratorService.Stub
private void noteVibratorOnLocked(int uid, long millis) {
try {
mBatteryStatsService.noteVibratorOn(uid, millis);
- StatsLog.write_non_chained(StatsLog.VIBRATOR_STATE_CHANGED, uid, null,
- StatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, millis);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, millis);
mCurVibUid = uid;
mIsVibrating = true;
} catch (RemoteException e) {
@@ -1382,8 +1382,8 @@ public class VibratorService extends IVibratorService.Stub
if (mCurVibUid >= 0) {
try {
mBatteryStatsService.noteVibratorOff(mCurVibUid);
- StatsLog.write_non_chained(StatsLog.VIBRATOR_STATE_CHANGED, mCurVibUid, null,
- StatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, 0);
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+ mCurVibUid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, 0);
} catch (RemoteException e) { }
mCurVibUid = -1;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6560777edd53..d80ff35ce70d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18924,19 +18924,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// The arguments here are untyped because the base ActivityManagerInternal class
- // doesn't have compile-time visiblity into ActivityServiceConnectionHolder or
+ // doesn't have compile-time visibility into ActivityServiceConnectionHolder or
// ConnectionRecord.
@Override
- public void disconnectActivityFromServices(Object connectionHolder, Object conns) {
+ public void disconnectActivityFromServices(Object connectionHolder) {
// 'connectionHolder' is an untyped ActivityServiceConnectionsHolder
- // 'conns' is an untyped HashSet<ConnectionRecord>
final ActivityServiceConnectionsHolder holder =
(ActivityServiceConnectionsHolder) connectionHolder;
- final HashSet<ConnectionRecord> toDisconnect = (HashSet<ConnectionRecord>) conns;
- synchronized(ActivityManagerService.this) {
- for (ConnectionRecord cr : toDisconnect) {
- mServices.removeConnectionLocked(cr, null, holder);
- }
+ synchronized (ActivityManagerService.this) {
+ holder.forEachConnection(cr -> mServices.removeConnectionLocked(
+ (ConnectionRecord) cr, null /* skipApp */, holder /* skipAct */));
}
}
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
index 5876d433face..64f25dd6f9fd 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -32,7 +32,10 @@ import android.os.ShellCallback;
import android.os.incremental.IIncrementalManager;
import android.util.Slog;
+import com.android.internal.util.DumpUtils;
+
import java.io.FileDescriptor;
+import java.io.PrintWriter;
/**
* This service has the following purposes:
@@ -71,6 +74,13 @@ public class IncrementalManagerService extends IIncrementalManager.Stub {
mNativeInstance = nativeStartService();
}
+ @SuppressWarnings("resource")
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+ nativeDump(mNativeInstance, fd.getInt$());
+ }
+
/**
* Notifies native IIncrementalManager service that system is ready.
*/
@@ -158,4 +168,6 @@ public class IncrementalManagerService extends IIncrementalManager.Stub {
private static native long nativeStartService();
private static native void nativeSystemReady(long nativeInstance);
+
+ private static native void nativeDump(long nativeInstance, int fd);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 00b4c2b060ac..d61379921f2e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import android.util.StatsLog;
+import com.android.internal.util.FrameworkStatsLog;
/**
* Standard implementation of NotificationRecordLogger interface.
@@ -31,7 +31,7 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
if (!p.shouldLog(buzzBeepBlink)) {
return;
}
- StatsLog.write(StatsLog.NOTIFICATION_REPORTED,
+ FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
/* int32 event_id = 1 */ p.getUiEvent().getId(),
/* int32 uid = 2 */ r.getUid(),
/* string package_name = 3 */ r.sbn.getPackageName(),
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 38da8ab26962..bf7bebd10a13 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -101,7 +101,6 @@ import android.os.RevocableFileDescriptor;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.incremental.IncrementalFileStorages;
-import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.provider.Settings.Secure;
import android.stats.devicepolicy.DevicePolicyEnums;
@@ -559,17 +558,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStagedSessionErrorMessage =
stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
- // TODO(b/136132412): sanity check if session should not be incremental
- if (!params.isStaged && isIncrementalInstallation()) {
- IncrementalManager incrementalManager = (IncrementalManager) mContext.getSystemService(
- Context.INCREMENTAL_SERVICE);
- if (incrementalManager != null) {
- mIncrementalFileStorages =
- new IncrementalFileStorages(mPackageName, stageDir, incrementalManager,
- params.dataLoaderParams);
- }
- }
-
if (isStreamingInstallation()
&& this.params.dataLoaderParams.getComponentName().getPackageName()
== SYSTEM_DATA_LOADER_PACKAGE) {
@@ -1040,10 +1028,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- if (mIncrementalFileStorages != null) {
- mIncrementalFileStorages.finishSetUp();
- }
-
dispatchStreamValidateAndCommit();
}
@@ -1052,11 +1036,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
private void handleStreamValidateAndCommit() {
- // TODO(b/136132412): update with new APIs
- if (mIncrementalFileStorages != null) {
- mIncrementalFileStorages.startLoading();
- }
-
boolean success = streamValidateAndCommit();
if (isMultiPackage()) {
@@ -2476,17 +2455,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- if (mIncrementalFileStorages != null) {
- for (InstallationFile file : addedFiles) {
- try {
- mIncrementalFileStorages.addFile(file);
- } catch (IOException ex) {
- // TODO(b/146080380): add incremental-specific error code
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
- "Failed to add and configure Incremental File: " + file.getName(), ex);
- }
+ // TODO(b/136132412): update with new APIs
+ if (isIncrementalInstallation()) {
+ try {
+ mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext,
+ stageDir, params.dataLoaderParams, addedFiles);
+ return true;
+ } catch (IOException e) {
+ throw new PackageManagerException(e);
}
- return true;
}
final DataLoaderManager dataLoaderManager = mContext.getSystemService(
@@ -2761,13 +2738,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
bridge.forceClose();
}
}
+ if (mIncrementalFileStorages != null) {
+ mIncrementalFileStorages.cleanUp();
+ mIncrementalFileStorages = null;
+ }
// For staged sessions, we don't delete the directory where the packages have been copied,
// since these packages are supposed to be read on reboot.
// Those dirs are deleted when the staged session has reached a final state.
if (stageDir != null && !params.isStaged) {
- if (mIncrementalFileStorages != null) {
- mIncrementalFileStorages.cleanUp();
- }
try {
mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
} catch (InstallerException ignored) {
@@ -2783,6 +2761,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
} else {
if (mIncrementalFileStorages != null) {
mIncrementalFileStorages.cleanUp();
+ mIncrementalFileStorages = null;
}
try {
mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c09fb38c4d8a..db8d490d53b9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1544,6 +1544,7 @@ public class PackageManagerService extends IPackageManager.Stub
final @Nullable String[] mTelephonyPackages;
final @NonNull String mServicesExtensionPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
+ final @Nullable String mRetailDemoPackage;
private final PackageUsage mPackageUsage = new PackageUsage();
private final CompilerStats mCompilerStats = new CompilerStats();
@@ -3160,6 +3161,7 @@ public class PackageManagerService extends IPackageManager.Stub
mAppPredictionServicePackage = getAppPredictionServicePackageName();
mIncidentReportApproverPackage = getIncidentReportApproverPackageName();
mTelephonyPackages = getTelephonyPackageNames();
+ mRetailDemoPackage = getRetailDemoPackageName();
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
@@ -19723,6 +19725,41 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Nullable
+ private String getRetailDemoPackageName() {
+ final String predefinedPkgName = mContext.getString(R.string.config_retailDemoPackage);
+ final String predefinedSignature = mContext.getString(
+ R.string.config_retailDemoPackageSignature);
+
+ if (TextUtils.isEmpty(predefinedPkgName) || TextUtils.isEmpty(predefinedSignature)) {
+ return null;
+ }
+
+ final AndroidPackage androidPkg = mPackages.get(predefinedPkgName);
+ if (androidPkg != null) {
+ final SigningDetails signingDetail = androidPkg.getSigningDetails();
+ if (signingDetail != null && signingDetail.signatures != null) {
+ try {
+ final MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
+ for (Signature signature : signingDetail.signatures) {
+ if (TextUtils.equals(predefinedSignature,
+ HexEncoding.encodeToString(msgDigest.digest(
+ signature.toByteArray()), false))) {
+ return predefinedPkgName;
+ }
+ }
+ } catch (NoSuchAlgorithmException e) {
+ Slog.e(
+ TAG,
+ "Unable to verify signatures as getting the retail demo package name",
+ e);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
private String ensureSystemPackageName(@Nullable String packageName) {
if (packageName == null) {
return null;
@@ -23010,6 +23047,10 @@ public class PackageManagerService extends IPackageManager.Stub
return filterOnlySystemPackages(mTelephonyPackages);
case PackageManagerInternal.PACKAGE_COMPANION:
return filterOnlySystemPackages("com.android.companiondevicemanager");
+ case PackageManagerInternal.PACKAGE_RETAIL_DEMO:
+ return TextUtils.isEmpty(mRetailDemoPackage)
+ ? ArrayUtils.emptyArray(String.class)
+ : new String[] {mRetailDemoPackage};
default:
return ArrayUtils.emptyArray(String.class);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f368666a06ba..f7889ea6141c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1977,10 +1977,15 @@ public class ShortcutService extends IShortcutService.Stub {
* After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}.
* Either {@param shortcut} or {@param appWidget} should be non-null.
*/
- private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut,
+ private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut,
AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) {
- verifyCaller(packageName, userId);
- verifyShortcutInfoPackage(packageName, shortcut);
+ verifyCaller(callingPackage, userId);
+ if (shortcut == null || !injectHasAccessShortcutsPermission(
+ injectBinderCallingPid(), injectBinderCallingUid())) {
+ // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS.
+ verifyShortcutInfoPackage(callingPackage, shortcut);
+ }
+ final String shortcutPackage = shortcut.getPackage();
final boolean ret;
synchronized (mLock) {
@@ -1995,13 +2000,13 @@ public class ShortcutService extends IShortcutService.Stub {
// and then proceed the rest of the process.
if (shortcut != null) {
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
- packageName, userId);
+ shortcutPackage, userId);
final String id = shortcut.getId();
if (ps.isShortcutExistsAndInvisibleToPublisher(id)) {
ps.updateInvisibleShortcutForPinRequestWith(shortcut);
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(shortcutPackage, userId);
}
}
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 565a85fd1ac1..e323c9869afb 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -284,6 +284,10 @@ public final class BasePermission {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
}
+ public boolean isRetailDemo() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0;
+ }
+
public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
if (!origPackageName.equals(sourcePackageName)) {
return;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 6167a509b85f..1fc2dd5193e1 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,6 +55,8 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.Context;
@@ -3355,10 +3357,27 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// Special permissions for the system companion device manager.
allowed = true;
}
+ if (!allowed && bp.isRetailDemo()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM),
+ pkg.getPackageName()) && isProfileOwner(pkg.getUid())) {
+ // Special permission granted only to the OEM specified retail demo app
+ allowed = true;
+ }
}
return allowed;
}
+ private static boolean isProfileOwner(int uid) {
+ DevicePolicyManagerInternal dpmInternal =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ if (dpmInternal != null) {
+ return dpmInternal
+ .isActiveAdminWithPolicy(uid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ }
+ return false;
+ }
+
private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
if (!ps.isOem()) {
return false;
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
index eec0d5f35c5c..cc72dd69a5fa 100644
--- a/services/core/java/com/android/server/power/AttentionDetector.java
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -38,9 +38,9 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.attention.AttentionService;
import android.util.Slog;
-import android.util.StatsLog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.wm.WindowManagerInternal;
@@ -285,7 +285,8 @@ public class AttentionDetector {
private void resetConsecutiveExtensionCount() {
final long previousCount = mConsecutiveTimeoutExtendedCount.getAndSet(0);
if (previousCount > 0) {
- StatsLog.write(StatsLog.SCREEN_TIMEOUT_EXTENSION_REPORTED, previousCount);
+ FrameworkStatsLog.write(FrameworkStatsLog.SCREEN_TIMEOUT_EXTENSION_REPORTED,
+ previousCount);
}
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index b45522d11a5d..0b95be15f157 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -49,13 +49,13 @@ import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.EventLog;
import android.util.Slog;
-import android.util.StatsLog;
import android.view.WindowManagerPolicyConstants.OnReason;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -199,8 +199,8 @@ public class Notifier {
try {
mBatteryStats.noteInteractive(true);
} catch (RemoteException ex) { }
- StatsLog.write(StatsLog.INTERACTIVE_STATE_CHANGED,
- StatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON);
+ FrameworkStatsLog.write(FrameworkStatsLog.INTERACTIVE_STATE_CHANGED,
+ FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON);
}
/**
@@ -247,13 +247,15 @@ public class Notifier {
try {
if (workSource != null) {
mBatteryStats.noteLongPartialWakelockStartFromSource(tag, historyTag, workSource);
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, workSource,
- tag, historyTag, StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON);
+ FrameworkStatsLog.write(FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ workSource, tag, historyTag,
+ FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON);
} else {
mBatteryStats.noteLongPartialWakelockStart(tag, historyTag, ownerUid);
- StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
- ownerUid, null, tag, historyTag,
- StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON);
+ FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, ownerUid, null, tag,
+ historyTag,
+ FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON);
}
} catch (RemoteException ex) {
// Ignore
@@ -270,13 +272,15 @@ public class Notifier {
try {
if (workSource != null) {
mBatteryStats.noteLongPartialWakelockFinishFromSource(tag, historyTag, workSource);
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, workSource,
- tag, historyTag, StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF);
+ FrameworkStatsLog.write(FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ workSource, tag, historyTag,
+ FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF);
} else {
mBatteryStats.noteLongPartialWakelockFinish(tag, historyTag, ownerUid);
- StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
- ownerUid, null, tag, historyTag,
- StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF);
+ FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, ownerUid, null, tag,
+ historyTag,
+ FrameworkStatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF);
}
} catch (RemoteException ex) {
// Ignore
@@ -415,9 +419,9 @@ public class Notifier {
try {
mBatteryStats.noteInteractive(interactive);
} catch (RemoteException ex) { }
- StatsLog.write(StatsLog.INTERACTIVE_STATE_CHANGED,
- interactive ? StatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON :
- StatsLog.INTERACTIVE_STATE_CHANGED__STATE__OFF);
+ FrameworkStatsLog.write(FrameworkStatsLog.INTERACTIVE_STATE_CHANGED,
+ interactive ? FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON :
+ FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__OFF);
// Handle early behaviors.
mInteractive = interactive;
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index e0143ae4841b..bf7413b64588 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -187,6 +187,14 @@ class Rollback {
private final int[] mPackageSessionIds;
/**
+ * The number of sessions in the install which are notified with success by
+ * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
+ * This rollback will be enabled only after all child sessions finished with success.
+ */
+ @GuardedBy("mLock")
+ private int mNumPackageSessionsWithSuccess;
+
+ /**
* Constructs a new, empty Rollback instance.
*
* @param rollbackId the id of the rollback.
@@ -840,6 +848,17 @@ class Rollback {
return mPackageSessionIds.length;
}
+ /**
+ * Called when a child session finished with success.
+ * Returns true when all child sessions are notified with success. This rollback will be
+ * enabled only after all child sessions finished with success.
+ */
+ boolean notifySessionWithSuccess() {
+ synchronized (mLock) {
+ return ++mNumPackageSessionsWithSuccess == mPackageSessionIds.length;
+ }
+ }
+
static String rollbackStateToString(@RollbackState int state) {
switch (state) {
case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 4425c0acba2a..3fa114e3b7a3 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -123,7 +123,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
// Rollbacks we are in the process of enabling.
@GuardedBy("mLock")
- private final Set<NewRollback> mNewRollbacks = new ArraySet<>();
+ private final Set<Rollback> mNewRollbacks = new ArraySet<>();
// The list of all rollbacks, including available and committed rollbacks.
@GuardedBy("mLock")
@@ -240,16 +240,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
}
synchronized (mLock) {
- NewRollback found = null;
- for (NewRollback newRollback : mNewRollbacks) {
- if (newRollback.rollback.hasToken(token)) {
+ Rollback found = null;
+ for (Rollback newRollback : mNewRollbacks) {
+ if (newRollback.hasToken(token)) {
found = newRollback;
break;
}
}
if (found != null) {
mNewRollbacks.remove(found);
- found.rollback.delete(mAppDataRollbackHelper);
+ found.delete(mAppDataRollbackHelper);
}
}
}
@@ -442,12 +442,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
rollback.delete(mAppDataRollbackHelper);
}
}
- Iterator<NewRollback> iter2 = mNewRollbacks.iterator();
+ Iterator<Rollback> iter2 = mNewRollbacks.iterator();
while (iter2.hasNext()) {
- NewRollback newRollback = iter2.next();
- if (newRollback.rollback.includesPackage(packageName)) {
+ Rollback newRollback = iter2.next();
+ if (newRollback.includesPackage(packageName)) {
iter2.remove();
- newRollback.rollback.delete(mAppDataRollbackHelper);
+ newRollback.delete(mAppDataRollbackHelper);
}
}
@@ -802,7 +802,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
}
- NewRollback newRollback;
+ Rollback newRollback;
synchronized (mLock) {
// See if we already have a NewRollback that contains this package
// session. If not, create a NewRollback for the parent session
@@ -813,9 +813,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
mNewRollbacks.add(newRollback);
}
}
- newRollback.rollback.addToken(token);
+ newRollback.addToken(token);
- return enableRollbackForPackageSession(newRollback.rollback, packageSession);
+ return enableRollbackForPackageSession(newRollback, packageSession);
}
@WorkerThread
@@ -825,12 +825,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
synchronized (mLock) {
- NewRollback newRollback = getNewRollbackForPackageSessionLocked(sessionId);
+ Rollback newRollback = getNewRollbackForPackageSessionLocked(sessionId);
if (newRollback != null) {
- Slog.w(TAG, "Delete new rollback id=" + newRollback.rollback.info.getRollbackId()
+ Slog.w(TAG, "Delete new rollback id=" + newRollback.info.getRollbackId()
+ " for session id=" + sessionId);
mNewRollbacks.remove(newRollback);
- newRollback.rollback.delete(mAppDataRollbackHelper);
+ newRollback.delete(mAppDataRollbackHelper);
}
Iterator<Rollback> iter = mRollbacks.iterator();
while (iter.hasNext()) {
@@ -972,9 +972,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
}
// non-staged installs
- for (NewRollback rollback : mNewRollbacks) {
- rollback.rollback.snapshotUserData(
- packageName, userIds, mAppDataRollbackHelper);
+ for (Rollback rollback : mNewRollbacks) {
+ rollback.snapshotUserData(packageName, userIds, mAppDataRollbackHelper);
}
}
}
@@ -1016,13 +1015,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
return;
}
- NewRollback newRollback;
+ Rollback newRollback;
synchronized (mLock) {
newRollback = createNewRollbackLocked(session);
}
if (!session.isMultiPackage()) {
- if (!enableRollbackForPackageSession(newRollback.rollback, session)) {
+ if (!enableRollbackForPackageSession(newRollback, session)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
result.offer(-1);
return;
@@ -1036,7 +1035,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
result.offer(-1);
return;
}
- if (!enableRollbackForPackageSession(newRollback.rollback, childSession)) {
+ if (!enableRollbackForPackageSession(newRollback, childSession)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
result.offer(-1);
return;
@@ -1197,7 +1196,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
if (success) {
- NewRollback newRollback;
+ Rollback newRollback;
synchronized (mLock) {
newRollback = getNewRollbackForPackageSessionLocked(sessionId);
if (newRollback != null && newRollback.notifySessionWithSuccess()) {
@@ -1229,8 +1228,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
* or null on error.
*/
@WorkerThread
- private Rollback completeEnableRollback(NewRollback newRollback) {
- Rollback rollback = newRollback.rollback;
+ private Rollback completeEnableRollback(Rollback rollback) {
if (LOCAL_LOGV) {
Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
}
@@ -1341,38 +1339,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
}
- private static class NewRollback {
- public final Rollback rollback;
-
- /**
- * The number of sessions in the install which are notified with success by
- * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
- * This NewRollback will be enabled only after all child sessions finished with success.
- */
- @GuardedBy("mNewRollbackLock")
- private int mNumPackageSessionsWithSuccess;
-
- private final Object mNewRollbackLock = new Object();
-
- NewRollback(Rollback rollback) {
- this.rollback = rollback;
- }
-
- /**
- * Called when a child session finished with success.
- * Returns true when all child sessions are notified with success. This NewRollback will be
- * enabled only after all child sessions finished with success.
- */
- boolean notifySessionWithSuccess() {
- synchronized (mNewRollbackLock) {
- return ++mNumPackageSessionsWithSuccess == rollback.getPackageSessionIdCount();
- }
- }
- }
-
@WorkerThread
@GuardedBy("mLock")
- private NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
+ private Rollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
int rollbackId = allocateRollbackIdLocked();
final int userId;
if (parentSession.getUser() == UserHandle.ALL) {
@@ -1404,7 +1373,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
installerPackageName, packageSessionIds);
}
- return new NewRollback(rollback);
+ return rollback;
}
/**
@@ -1414,11 +1383,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
*/
@WorkerThread
@GuardedBy("mLock")
- NewRollback getNewRollbackForPackageSessionLocked(int packageSessionId) {
+ Rollback getNewRollbackForPackageSessionLocked(int packageSessionId) {
// We expect mNewRollbacks to be a very small list; linear search
// should be plenty fast.
- for (NewRollback newRollback: mNewRollbacks) {
- if (newRollback.rollback.containsSessionId(packageSessionId)) {
+ for (Rollback newRollback: mNewRollbacks) {
+ if (newRollback.containsSessionId(packageSessionId)) {
return newRollback;
}
}
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 79e1a2912147..46ec2f8258ca 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -70,20 +70,27 @@ public final class WatchdogRollbackLogger {
}
}
+ /**
+ * Returns the logging parent of a given package if it exists, {@code null} otherwise.
+ *
+ * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the
+ * metadata of a package's AndroidManifest.xml.
+ */
@VisibleForTesting
+ @Nullable
static VersionedPackage getLogPackage(Context context,
@NonNull VersionedPackage failingPackage) {
String logPackageName;
VersionedPackage loggingParent;
logPackageName = getLoggingParentName(context, failingPackage.getPackageName());
if (logPackageName == null) {
- return failingPackage;
+ return null;
}
try {
loggingParent = new VersionedPackage(logPackageName, context.getPackageManager()
.getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode());
} catch (PackageManager.NameNotFoundException e) {
- return failingPackage;
+ return null;
}
return loggingParent;
}
@@ -105,18 +112,14 @@ public final class WatchdogRollbackLogger {
return;
}
- // Identify the logging parent for this rollback. When all configurations are correct, each
- // package in the rollback refers to the same logging parent, except for the logging parent
- // itself. If a logging parent is missing for a package, we use the package itself for
- // logging. This might result in over-logging, but we prefer this over no logging.
+ // Identify the logging parent for this rollback. When all configurations are correct,
+ // each package in the rollback has a logging parent set in metadata.
final Set<String> loggingPackageNames = new ArraySet<>();
for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
final String loggingParentName = getLoggingParentName(context,
packageRollback.getPackageName());
if (loggingParentName != null) {
loggingPackageNames.add(loggingParentName);
- } else {
- loggingPackageNames.add(packageRollback.getPackageName());
}
}
@@ -168,6 +171,10 @@ public final class WatchdogRollbackLogger {
if (logPackage != null) {
StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(),
logPackage.getVersionCode(), rollbackReason, failingPackageName);
+ } else {
+ // In the case that the log package is null, still log an empty string as an
+ // indication that retrieving the logging parent failed.
+ StatsLog.logWatchdogRollbackOccurred(type, "", 0, rollbackReason, failingPackageName);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index df6dfc4d8ffe..ed38e9a73050 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3037,6 +3037,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// Throw away any services that have been bound by this activity.
mServiceConnectionsHolder.disconnectActivityFromServices();
+ // This activity record is removing, make sure not to disconnect twice.
+ mServiceConnectionsHolder = null;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 6e75f9c9167f..5dfc261480f2 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -18,10 +18,13 @@ package com.android.server.wm;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+
+import android.util.ArraySet;
+import android.util.Slog;
import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.function.Consumer;
/**
@@ -30,6 +33,8 @@ import java.util.function.Consumer;
* instance of this per activity for tracking all services connected to that activity. AM will
* sometimes query this to bump the OOM score for the processes with services connected to visible
* activities.
+ * <p>
+ * Public methods are called in AM lock, otherwise in WM lock.
*/
public class ActivityServiceConnectionsHolder<T> {
@@ -44,7 +49,10 @@ public class ActivityServiceConnectionsHolder<T> {
* on the WM side since we don't perform operations on the object. Mainly here for communication
* and booking with the AM side.
*/
- private HashSet<T> mConnections;
+ private ArraySet<T> mConnections;
+
+ /** Whether all connections of {@link #mActivity} are being removed. */
+ private volatile boolean mIsDisconnecting;
ActivityServiceConnectionsHolder(ActivityTaskManagerService service, ActivityRecord activity) {
mService = service;
@@ -54,8 +62,16 @@ public class ActivityServiceConnectionsHolder<T> {
/** Adds a connection record that the activity has bound to a specific service. */
public void addConnection(T c) {
synchronized (mService.mGlobalLock) {
+ if (mIsDisconnecting) {
+ // This is unlikely to happen because the caller should create a new holder.
+ if (DEBUG_CLEANUP) {
+ Slog.e(TAG_ATM, "Skip adding connection " + c + " to a disconnecting holder of "
+ + mActivity);
+ }
+ return;
+ }
if (mConnections == null) {
- mConnections = new HashSet<>();
+ mConnections = new ArraySet<>();
}
mConnections.add(c);
}
@@ -67,6 +83,9 @@ public class ActivityServiceConnectionsHolder<T> {
if (mConnections == null) {
return;
}
+ if (DEBUG_CLEANUP && mIsDisconnecting) {
+ Slog.v(TAG_ATM, "Remove pending disconnecting " + c + " of " + mActivity);
+ }
mConnections.remove(c);
}
}
@@ -88,26 +107,33 @@ public class ActivityServiceConnectionsHolder<T> {
if (mConnections == null || mConnections.isEmpty()) {
return;
}
- final Iterator<T> it = mConnections.iterator();
- while (it.hasNext()) {
- T c = it.next();
- consumer.accept(c);
+ for (int i = mConnections.size() - 1; i >= 0; i--) {
+ consumer.accept(mConnections.valueAt(i));
}
}
}
- /** Removes the connection between the activity and all services that were connected to it. */
+ /**
+ * Removes the connection between the activity and all services that were connected to it. In
+ * general, this method is used to clean up if the activity didn't unbind services before it
+ * is destroyed.
+ */
void disconnectActivityFromServices() {
- if (mConnections == null || mConnections.isEmpty()) {
+ if (mConnections == null || mConnections.isEmpty() || mIsDisconnecting) {
return;
}
- // Capture and null out mConnections, to guarantee that we process
+ // Mark as disconnecting, to guarantee that we process
// disconnect of these specific connections exactly once even if
// we're racing with rapid activity lifecycle churn and this
// method is invoked more than once on this object.
- final Object disc = mConnections;
- mConnections = null;
- mService.mH.post(() -> mService.mAmInternal.disconnectActivityFromServices(this, disc));
+ // It is possible that {@link #removeConnection} is called while the disconnect-runnable is
+ // still in the message queue, so keep the reference of {@link #mConnections} to make sure
+ // the connection list is up-to-date.
+ mIsDisconnecting = true;
+ mService.mH.post(() -> {
+ mService.mAmInternal.disconnectActivityFromServices(this);
+ mIsDisconnecting = false;
+ });
}
public void dump(PrintWriter pw, String prefix) {
@@ -116,4 +142,9 @@ public class ActivityServiceConnectionsHolder<T> {
}
}
+ /** Used by {@link ActivityRecord#dump}. */
+ @Override
+ public String toString() {
+ return String.valueOf(mConnections);
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 16ac9fb41f30..810aa3438ea0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -194,6 +194,7 @@ import android.provider.Settings;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.IntArray;
+import android.util.RotationUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -1603,17 +1604,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return WmDisplayCutout.NO_CUTOUT;
}
+ final Insets waterfallInsets =
+ RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
if (rotation == ROTATION_0) {
return WmDisplayCutout.computeSafeInsets(
cutout, mInitialDisplayWidth, mInitialDisplayHeight);
}
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final Rect[] newBounds = mRotationUtil.getRotatedBounds(
- WmDisplayCutout.computeSafeInsets(
- cutout, mInitialDisplayWidth, mInitialDisplayHeight)
- .getDisplayCutout().getBoundingRectsAll(),
+ cutout.getBoundingRectsAll(),
rotation, mInitialDisplayWidth, mInitialDisplayHeight);
- return WmDisplayCutout.computeSafeInsets(DisplayCutout.fromBounds(newBounds),
+ return WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
}
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index 3be5d3176df5..46fff032e053 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -21,7 +21,6 @@ import android.util.Size;
import android.view.DisplayCutout;
import android.view.Gravity;
-import java.util.List;
import java.util.Objects;
/**
@@ -41,12 +40,17 @@ public class WmDisplayCutout {
mFrameSize = frameSize;
}
- public static WmDisplayCutout computeSafeInsets(DisplayCutout inner,
- int displayWidth, int displayHeight) {
- if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+ /**
+ * Compute the safe insets according to the given DisplayCutout and the display size.
+ *
+ * @return return a WmDisplayCutout with calculated safe insets.
+ * @hide
+ */
+ public static WmDisplayCutout computeSafeInsets(
+ DisplayCutout inner, int displayWidth, int displayHeight) {
+ if (inner == DisplayCutout.NO_CUTOUT) {
return NO_CUTOUT;
}
-
final Size displaySize = new Size(displayWidth, displayHeight);
final Rect safeInsets = computeSafeInsets(displaySize, inner);
return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
@@ -112,57 +116,42 @@ public class WmDisplayCutout {
}
private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
- if (displaySize.getWidth() < displaySize.getHeight()) {
- final List<Rect> boundingRects = cutout.replaceSafeInsets(
- new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
- .getBoundingRects();
- int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
- int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
- return new Rect(0, topInset, 0, bottomInset);
- } else if (displaySize.getWidth() > displaySize.getHeight()) {
- final List<Rect> boundingRects = cutout.replaceSafeInsets(
- new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
- .getBoundingRects();
- int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
- int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
- return new Rect(leftInset, 0, right, 0);
- } else {
+ if (displaySize.getWidth() == displaySize.getHeight()) {
throw new UnsupportedOperationException("not implemented: display=" + displaySize +
" cutout=" + cutout);
}
+
+ int leftInset = Math.max(cutout.getWaterfallInsets().left,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
+ int topInset = Math.max(cutout.getWaterfallInsets().top,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
+ int rightInset = Math.max(cutout.getWaterfallInsets().right,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
+ int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
+ findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
+ Gravity.BOTTOM));
+
+ return new Rect(leftInset, topInset, rightInset, bottomInset);
}
- private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
+ private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
+ if (boundingRect.isEmpty()) {
+ return 0;
+ }
+
int inset = 0;
- final int size = boundingRects.size();
- for (int i = 0; i < size; i++) {
- Rect boundingRect = boundingRects.get(i);
- switch (gravity) {
- case Gravity.TOP:
- if (boundingRect.top == 0) {
- inset = Math.max(inset, boundingRect.bottom);
- }
- break;
- case Gravity.BOTTOM:
- if (boundingRect.bottom == display.getHeight()) {
- inset = Math.max(inset, display.getHeight() - boundingRect.top);
- }
- break;
- case Gravity.LEFT:
- if (boundingRect.left == 0) {
- inset = Math.max(inset, boundingRect.right);
- }
- break;
- case Gravity.RIGHT:
- if (boundingRect.right == display.getWidth()) {
- inset = Math.max(inset, display.getWidth() - boundingRect.left);
- }
- break;
- default:
- throw new IllegalArgumentException("unknown gravity: " + gravity);
- }
+ switch (gravity) {
+ case Gravity.TOP:
+ return Math.max(inset, boundingRect.bottom);
+ case Gravity.BOTTOM:
+ return Math.max(inset, display.getHeight() - boundingRect.top);
+ case Gravity.LEFT:
+ return Math.max(inset, boundingRect.right);
+ case Gravity.RIGHT:
+ return Math.max(inset, display.getWidth() - boundingRect.left);
+ default:
+ throw new IllegalArgumentException("unknown gravity: " + gravity);
}
- return inset;
}
public DisplayCutout getDisplayCutout() {
diff --git a/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp b/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
index 5e255f46fa05..10bac94f77e2 100644
--- a/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
+++ b/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
@@ -33,9 +33,14 @@ static void nativeSystemReady(JNIEnv* env, jclass klass, jlong self) {
Incremental_IncrementalService_OnSystemReady(self);
}
+static void nativeDump(JNIEnv* env, jclass klass, jlong self, jint fd) {
+ Incremental_IncrementalService_OnDump(self, fd);
+}
+
static const JNINativeMethod method_table[] = {
{"nativeStartService", "()J", (void*)nativeStartService},
{"nativeSystemReady", "(J)V", (void*)nativeSystemReady},
+ {"nativeDump", "(JI)V", (void*)nativeDump},
};
int register_android_server_incremental_IncrementalManagerService(JNIEnv* env) {
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 91d05723a605..0941831f5299 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -17,12 +17,14 @@
#include "BinderIncrementalService.h"
#include <binder/IResultReceiver.h>
+#include <binder/PermissionCache.h>
#include <incfs.h>
#include "ServiceWrappers.h"
#include "jni.h"
#include "nativehelper/JNIHelp.h"
#include "path.h"
+#include <android-base/logging.h>
using namespace std::literals;
using namespace android::incremental;
@@ -90,8 +92,13 @@ BinderIncrementalService* BinderIncrementalService::start() {
return self.get();
}
-status_t BinderIncrementalService::dump(int fd, const Vector<String16>& args) {
- return OK;
+status_t BinderIncrementalService::dump(int fd, const Vector<String16>&) {
+ static const String16 kDump("android.permission.DUMP");
+ if (!PermissionCache::checkCallingPermission(kDump)) {
+ return PERMISSION_DENIED;
+ }
+ mImpl.onDump(fd);
+ return NO_ERROR;
}
void BinderIncrementalService::onSystemReady() {
@@ -280,3 +287,10 @@ void Incremental_IncrementalService_OnSystemReady(jlong self) {
((android::os::incremental::BinderIncrementalService*)self)->onSystemReady();
}
}
+void Incremental_IncrementalService_OnDump(jlong self, jint fd) {
+ if (self) {
+ ((android::os::incremental::BinderIncrementalService*)self)->dump(fd, {});
+ } else {
+ dprintf(fd, "BinderIncrementalService is stopped.");
+ }
+}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index b7cea1f18537..dbd97cfa039a 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -35,6 +35,7 @@
#include <uuid/uuid.h>
#include <zlib.h>
+#include <ctime>
#include <iterator>
#include <span>
#include <stack>
@@ -256,6 +257,68 @@ FileId IncrementalService::idFromMetadata(std::span<const uint8_t> metadata) {
IncrementalService::~IncrementalService() = default;
+inline const char* toString(TimePoint t) {
+ using SystemClock = std::chrono::system_clock;
+ time_t time = SystemClock::to_time_t(SystemClock::now() + std::chrono::duration_cast<SystemClock::duration>(t - Clock::now()));
+ return std::ctime(&time);
+}
+
+inline const char* toString(IncrementalService::BindKind kind) {
+ switch (kind) {
+ case IncrementalService::BindKind::Temporary:
+ return "Temporary";
+ case IncrementalService::BindKind::Permanent:
+ return "Permanent";
+ }
+}
+
+void IncrementalService::onDump(int fd) {
+ dprintf(fd, "Incremental is %s\n", incfs::enabled() ? "ENABLED" : "DISABLED");
+ dprintf(fd, "Incremental dir: %s\n", mIncrementalDir.c_str());
+
+ std::unique_lock l(mLock);
+
+ dprintf(fd, "Mounts (%d):\n", int(mMounts.size()));
+ for (auto&& [id, ifs] : mMounts) {
+ const IncFsMount& mnt = *ifs.get();
+ dprintf(fd, "\t[%d]:\n", id);
+ dprintf(fd, "\t\tmountId: %d\n", mnt.mountId);
+ dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
+ dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load());
+ dprintf(fd, "\t\tconnectionLostTime: %s\n", toString(mnt.connectionLostTime));
+ if (mnt.savedDataLoaderParams) {
+ const auto& params = mnt.savedDataLoaderParams.value();
+ dprintf(fd, "\t\tsavedDataLoaderParams:\n");
+ dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str());
+ dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str());
+ dprintf(fd, "\t\t\tclassName: %s\n", params.className.c_str());
+ dprintf(fd, "\t\t\targuments: %s\n", params.arguments.c_str());
+ dprintf(fd, "\t\t\tdynamicArgs: %d\n", int(params.dynamicArgs.size()));
+ }
+ dprintf(fd, "\t\tstorages (%d):\n", int(mnt.storages.size()));
+ for (auto&& [storageId, storage] : mnt.storages) {
+ dprintf(fd, "\t\t\t[%d] -> [%s]\n", storageId, storage.name.c_str());
+ }
+
+ dprintf(fd, "\t\tbindPoints (%d):\n", int(mnt.bindPoints.size()));
+ for (auto&& [target, bind] : mnt.bindPoints) {
+ dprintf(fd, "\t\t\t[%s]->[%d]:\n", target.c_str(), bind.storage);
+ dprintf(fd, "\t\t\t\tsavedFilename: %s\n", bind.savedFilename.c_str());
+ dprintf(fd, "\t\t\t\tsourceDir: %s\n", bind.sourceDir.c_str());
+ dprintf(fd, "\t\t\t\tkind: %s\n", toString(bind.kind));
+ }
+ }
+
+ dprintf(fd, "Sorted binds (%d):\n", int(mBindsByPath.size()));
+ for (auto&& [target, mountPairIt] : mBindsByPath) {
+ const auto& bind = mountPairIt->second;
+ dprintf(fd, "\t\t[%s]->[%d]:\n", target.c_str(), bind.storage);
+ dprintf(fd, "\t\t\tsavedFilename: %s\n", bind.savedFilename.c_str());
+ dprintf(fd, "\t\t\tsourceDir: %s\n", bind.sourceDir.c_str());
+ dprintf(fd, "\t\t\tkind: %s\n", toString(bind.kind));
+ }
+}
+
std::optional<std::future<void>> IncrementalService::onSystemReady() {
std::promise<void> threadFinished;
if (mSystemReady.exchange(true)) {
@@ -617,15 +680,7 @@ int IncrementalService::bind(StorageId storage, std::string_view source, std::st
if (storageInfo == ifs->storages.end()) {
return -EINVAL;
}
- std::string normSource;
- if (path::isAbsolute(source)) {
- normSource = path::normalize(source);
- } else {
- normSource = path::normalize(path::join(storageInfo->second.name, source));
- }
- if (!path::startsWith(normSource, storageInfo->second.name)) {
- return -EINVAL;
- }
+ std::string normSource = normalizePathToStorage(ifs, storage, source);
l.unlock();
std::unique_lock l2(mLock, std::defer_lock);
return addBindMount(*ifs, storage, storageInfo->second.name, std::move(normSource),
@@ -674,20 +729,29 @@ int IncrementalService::unbind(StorageId storage, std::string_view target) {
return 0;
}
+std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr ifs,
+ StorageId storage, std::string_view path) {
+ const auto storageInfo = ifs->storages.find(storage);
+ if (storageInfo == ifs->storages.end()) {
+ return {};
+ }
+ std::string normPath;
+ if (path::isAbsolute(path)) {
+ normPath = path::normalize(path);
+ } else {
+ normPath = path::normalize(path::join(storageInfo->second.name, path));
+ }
+ if (!path::startsWith(normPath, storageInfo->second.name)) {
+ return {};
+ }
+ return normPath;
+}
+
int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id,
incfs::NewFileParams params) {
if (auto ifs = getIfs(storage)) {
- const auto storageInfo = ifs->storages.find(storage);
- if (storageInfo == ifs->storages.end()) {
- return -EINVAL;
- }
- std::string normPath;
- if (path::isAbsolute(path)) {
- normPath = path::normalize(path);
- } else {
- normPath = path::normalize(path::join(storageInfo->second.name, path));
- }
- if (!path::startsWith(normPath, storageInfo->second.name)) {
+ std::string normPath = normalizePathToStorage(ifs, storage, path);
+ if (normPath.empty()) {
return -EINVAL;
}
auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params);
@@ -706,7 +770,11 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m
int IncrementalService::makeDir(StorageId storageId, std::string_view path, int mode) {
if (auto ifs = getIfs(storageId)) {
- return mIncFs->makeDir(ifs->control, path, mode);
+ std::string normPath = normalizePathToStorage(ifs, storageId, path);
+ if (normPath.empty()) {
+ return -EINVAL;
+ }
+ return mIncFs->makeDir(ifs->control, normPath, mode);
}
return -EINVAL;
}
@@ -716,31 +784,40 @@ int IncrementalService::makeDirs(StorageId storageId, std::string_view path, int
if (!ifs) {
return -EINVAL;
}
-
- auto err = mIncFs->makeDir(ifs->control, path, mode);
+ std::string normPath = normalizePathToStorage(ifs, storageId, path);
+ if (normPath.empty()) {
+ return -EINVAL;
+ }
+ auto err = mIncFs->makeDir(ifs->control, normPath, mode);
if (err == -EEXIST) {
return 0;
} else if (err != -ENOENT) {
return err;
}
- if (auto err = makeDirs(storageId, path::dirname(path), mode)) {
+ if (auto err = makeDirs(storageId, path::dirname(normPath), mode)) {
return err;
}
- return mIncFs->makeDir(ifs->control, path, mode);
+ return mIncFs->makeDir(ifs->control, normPath, mode);
}
int IncrementalService::link(StorageId sourceStorageId, std::string_view oldPath,
StorageId destStorageId, std::string_view newPath) {
if (auto ifsSrc = getIfs(sourceStorageId), ifsDest = getIfs(destStorageId);
ifsSrc && ifsSrc == ifsDest) {
- return mIncFs->link(ifsSrc->control, oldPath, newPath);
+ std::string normOldPath = normalizePathToStorage(ifsSrc, sourceStorageId, oldPath);
+ std::string normNewPath = normalizePathToStorage(ifsDest, destStorageId, newPath);
+ if (normOldPath.empty() || normNewPath.empty()) {
+ return -EINVAL;
+ }
+ return mIncFs->link(ifsSrc->control, normOldPath, normNewPath);
}
return -EINVAL;
}
int IncrementalService::unlink(StorageId storage, std::string_view path) {
if (auto ifs = getIfs(storage)) {
- return mIncFs->unlink(ifs->control, path);
+ std::string normOldPath = normalizePathToStorage(ifs, storage, path);
+ return mIncFs->unlink(ifs->control, normOldPath);
}
return -EINVAL;
}
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index b08e68b0c6cd..dec9f64f2084 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -18,8 +18,8 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
-#include <android/os/incremental/IIncrementalManager.h>
#include <android/content/pm/DataLoaderParamsParcel.h>
+#include <android/os/incremental/IIncrementalManager.h>
#include <binder/IServiceManager.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
@@ -90,10 +90,11 @@ public:
return idFromMetadata({(const uint8_t*)metadata.data(), metadata.size()});
}
+ void onDump(int fd);
+
std::optional<std::future<void>> onSystemReady();
- StorageId createStorage(std::string_view mountPoint,
- DataLoaderParamsParcel&& dataLoaderParams,
+ StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams,
CreateOptions options = CreateOptions::Default);
StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
CreateOptions options = CreateOptions::Default);
@@ -207,6 +208,8 @@ private:
void deleteStorage(IncFsMount& ifs);
void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock);
MountMap::iterator getStorageSlotLocked();
+ std::string normalizePathToStorage(const IfsMountPtr incfs, StorageId storage,
+ std::string_view path);
// Member variables
std::unique_ptr<VoldServiceWrapper> mVold;
diff --git a/services/incremental/include/incremental_service.h b/services/incremental/include/incremental_service.h
index 7109d953ba4d..4a34b11261b9 100644
--- a/services/incremental/include/incremental_service.h
+++ b/services/incremental/include/incremental_service.h
@@ -26,6 +26,7 @@ __BEGIN_DECLS
jlong Incremental_IncrementalService_Start();
void Incremental_IncrementalService_OnSystemReady(jlong self);
+void Incremental_IncrementalService_OnDump(jlong self, jint fd);
__END_DECLS
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 28268181f173..9cdc83e75055 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -406,29 +406,16 @@ TEST_F(IncrementalServiceTest, testMakeDirectory) {
int storageId =
mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
IncrementalService::CreateOptions::CreateNew);
- std::string_view dir_path("test");
- EXPECT_CALL(*mIncFs, makeDir(_, dir_path, _));
- auto res = mIncrementalService->makeDir(storageId, dir_path, 0555);
- ASSERT_EQ(res, 0);
-}
+ std::string dir_path("test");
-TEST_F(IncrementalServiceTest, testMakeDirectoryNested) {
- mVold->mountIncFsSuccess();
- mIncFs->makeFileSuccess();
- mVold->bindMountSuccess();
- mIncrementalManager->prepareDataLoaderSuccess();
- mIncrementalManager->startDataLoaderSuccess();
- TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
- IncrementalService::CreateOptions::CreateNew);
- auto first = "first"sv;
- auto second = "second"sv;
- std::string dir_path = std::string(first) + "/" + std::string(second);
- EXPECT_CALL(*mIncFs, makeDir(_, first, _)).Times(0);
- EXPECT_CALL(*mIncFs, makeDir(_, second, _)).Times(0);
- EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(dir_path), _)).Times(1);
+ std::string tempPath(tempDir.path);
+ std::replace(tempPath.begin(), tempPath.end(), '/', '_');
+ std::string mount_dir = std::string(mRootDir.path) + "/" + tempPath.substr(1);
+ std::string normalized_dir_path = mount_dir + "/mount/st_1_0/" + dir_path;
+ // Expecting incfs to call makeDir on a path like:
+ // /data/local/tmp/TemporaryDir-06yixG/data_local_tmp_TemporaryDir-xwdFhT/mount/st_1_0/test
+ EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_dir_path), _));
auto res = mIncrementalService->makeDir(storageId, dir_path, 0555);
ASSERT_EQ(res, 0);
}
@@ -446,15 +433,29 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) {
auto first = "first"sv;
auto second = "second"sv;
auto third = "third"sv;
+
+ std::string tempPath(tempDir.path);
+ std::replace(tempPath.begin(), tempPath.end(), '/', '_');
+ std::string mount_dir = std::string(mRootDir.path) + "/" + tempPath.substr(1);
+
InSequence seq;
auto parent_path = std::string(first) + "/" + std::string(second);
auto dir_path = parent_path + "/" + std::string(third);
- EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(dir_path), _)).WillOnce(Return(-ENOENT));
- EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(parent_path), _)).WillOnce(Return(-ENOENT));
- EXPECT_CALL(*mIncFs, makeDir(_, first, _)).WillOnce(Return(0));
- EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(parent_path), _)).WillOnce(Return(0));
- EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(dir_path), _)).WillOnce(Return(0));
- auto res = mIncrementalService->makeDirs(storageId, dir_path, 0555);
+
+ std::string normalized_first_path = mount_dir + "/mount/st_1_0/" + std::string(first);
+ std::string normalized_parent_path = mount_dir + "/mount/st_1_0/" + parent_path;
+ std::string normalized_dir_path = mount_dir + "/mount/st_1_0/" + dir_path;
+
+ EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_dir_path), _))
+ .WillOnce(Return(-ENOENT));
+ EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_parent_path), _))
+ .WillOnce(Return(-ENOENT));
+ EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_first_path), _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_parent_path), _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_dir_path), _)).WillOnce(Return(0));
+ auto res = mIncrementalService->makeDirs(storageId, normalized_dir_path, 0555);
ASSERT_EQ(res, 0);
}
} // namespace android::os::incremental
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index 164c88382828..9719509659ec 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -291,6 +291,17 @@ public class RollbackUnitTest {
verify(mMockDataHelper).restoreAppData(123, pkgInfo1, 7, 333, "blah");
}
+ @Test
+ public void notifySessionWithSuccess() {
+ int[] sessionIds = new int[]{ 7777, 8888 };
+ Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER,
+ sessionIds);
+ // The 1st invocation returns false because not all child sessions are notified.
+ assertThat(rollback.notifySessionWithSuccess()).isFalse();
+ // The 2nd invocation returns true because now all child sessions are notified.
+ assertThat(rollback.notifySessionWithSuccess()).isTrue();
+ }
+
private static PackageRollbackInfo newPkgInfoFor(
String packageName, long fromVersion, long toVersion, boolean isApex) {
return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
diff --git a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
index 61117f18445b..ba493d4f9646 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/WatchdogRollbackLoggerTest.java
@@ -60,18 +60,18 @@ public class WatchdogRollbackLoggerTest {
}
/**
- * Ensures that the original package is returned if the application info has no metadata.
+ * Ensures that null is returned if the application info has no metadata.
*/
@Test
public void testLogPackageHasNoMetadata() throws Exception {
when(mMockPm.getApplicationInfo(anyString(), anyInt())).thenReturn(mApplicationInfo);
VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
sTestPackageV1);
- assertThat(logPackage).isEqualTo(sTestPackageV1);
+ assertThat(logPackage).isNull();
}
/**
- * Ensures the original package is returned if the application info does not contain a logging
+ * Ensures that null is returned if the application info does not contain a logging
* parent key.
*/
@Test
@@ -81,7 +81,7 @@ public class WatchdogRollbackLoggerTest {
bundle.putString(LOGGING_PARENT_KEY, null);
VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
sTestPackageV1);
- assertThat(logPackage).isEqualTo(sTestPackageV1);
+ assertThat(logPackage).isNull();
}
/**
@@ -102,8 +102,7 @@ public class WatchdogRollbackLoggerTest {
}
/**
- * Ensures that the original package is returned if Package Manager does not know about the
- * logging parent.
+ * Ensures that null is returned if Package Manager does not know about the logging parent.
*/
@Test
public void testLogPackageNameNotFound() throws Exception {
@@ -114,6 +113,6 @@ public class WatchdogRollbackLoggerTest {
new PackageManager.NameNotFoundException());
VersionedPackage logPackage = WatchdogRollbackLogger.getLogPackage(mMockContext,
sTestPackageV1);
- assertThat(logPackage).isEqualTo(sTestPackageV1);
+ assertThat(logPackage).isNull();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
index fb8ba7bffd4c..a283476bfdf0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
@@ -47,6 +47,7 @@ import org.junit.Test;
@SmallTest
@Presubmit
public class WmDisplayCutoutTest {
+ private static final Rect ZERO_RECT = new Rect();
private final DisplayCutout mCutoutTop = new DisplayCutout(
Insets.of(0, 100, 0, 0),
null /* boundLeft */, new Rect(50, 0, 75, 100) /* boundTop */,
@@ -99,41 +100,204 @@ public class WmDisplayCutoutTest {
}
@Test
- public void computeSafeInsets_top() {
+ public void computeSafeInsets_cutoutTop() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- fromBoundingRect(0, 0, 100, 20, BOUNDS_POSITION_TOP), 200, 400);
+ fromBoundingRect(80, 0, 120, 20, BOUNDS_POSITION_TOP), 200, 400);
assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
}
@Test
- public void computeSafeInsets_left() {
+ public void computeSafeInsets_cutoutLeft() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- fromBoundingRect(0, 0, 20, 100, BOUNDS_POSITION_LEFT), 400, 200);
+ fromBoundingRect(0, 180, 20, 220, BOUNDS_POSITION_LEFT), 200, 400);
assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
}
@Test
- public void computeSafeInsets_bottom() {
+ public void computeSafeInsets_cutoutBottom() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- fromBoundingRect(0, 180, 100, 200, BOUNDS_POSITION_BOTTOM), 100, 200);
+ fromBoundingRect(80, 380, 120, 400, BOUNDS_POSITION_BOTTOM), 200, 400);
assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
}
@Test
- public void computeSafeInsets_right() {
+ public void computeSafeInsets_cutoutRight() {
WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
- fromBoundingRect(180, 0, 200, 100, BOUNDS_POSITION_RIGHT), 200, 100);
+ fromBoundingRect(180, 180, 200, 220, BOUNDS_POSITION_RIGHT), 200, 400);
assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
}
@Test
+ public void computeSafeInsets_topLeftCornerCutout_portrait() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 20, 20, BOUNDS_POSITION_TOP), 200, 400);
+
+ assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_topRightCornerCutout_portrait() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(180, 0, 200, 20, BOUNDS_POSITION_TOP), 200, 400);
+
+ assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_bottomLeftCornerCutout_portrait() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 380, 20, 400, BOUNDS_POSITION_BOTTOM), 200, 400);
+
+ assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_bottomRightCornerCutout_portrait() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(180, 380, 200, 400, BOUNDS_POSITION_BOTTOM), 200, 400);
+
+ assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_topLeftCornerCutout_landscape() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 0, 20, 20, BOUNDS_POSITION_LEFT), 400, 200);
+
+ assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_topRightCornerCutout_landscape() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(380, 0, 400, 20, BOUNDS_POSITION_RIGHT), 400, 200);
+
+ assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_bottomLeftCornerCutout_landscape() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(0, 180, 20, 200, BOUNDS_POSITION_LEFT), 400, 200);
+
+ assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_bottomRightCornerCutout_landscape() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ fromBoundingRect(380, 180, 400, 200, BOUNDS_POSITION_RIGHT), 400, 200);
+
+ assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_waterfall() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT},
+ Insets.of(1, 2, 3, 4)),
+ 200, 400);
+
+ assertEquals(new Rect(1, 2, 3, 4), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutTop_greaterThan_waterfallTop() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
+ Insets.of(0, 20, 0, 0)),
+ 200, 400);
+
+ assertEquals(new Rect(0, 30, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutTop_lessThan_waterfallTop() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
+ Insets.of(0, 40, 0, 0)),
+ 200, 400);
+
+ assertEquals(new Rect(0, 40, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutLeft_greaterThan_waterfallLeft() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
+ Insets.of(20, 0, 0, 0)),
+ 200, 400);
+
+ assertEquals(new Rect(30, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutLeft_lessThan_waterfallLeft() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
+ Insets.of(40, 0, 0, 0)),
+ 200, 400);
+
+ assertEquals(new Rect(40, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutBottom_greaterThan_waterfallBottom() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
+ Insets.of(0, 0, 0, 20)),
+ 200, 400);
+
+ assertEquals(new Rect(0, 0, 0, 30), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutBottom_lessThan_waterfallBottom() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
+ Insets.of(0, 0, 0, 40)),
+ 200, 400);
+
+ assertEquals(new Rect(0, 0, 0, 40), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutRight_greaterThan_waterfallRight() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
+ Insets.of(0, 0, 20, 0)),
+ 200, 400);
+
+ assertEquals(new Rect(0, 0, 30, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
+ public void computeSafeInsets_cutoutRight_lessThan_waterfallRight() {
+ WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+ DisplayCutout.fromBoundsAndWaterfall(
+ new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
+ Insets.of(0, 0, 40, 0)),
+ 200, 400);
+
+ assertEquals(new Rect(0, 0, 40, 0), cutout.getDisplayCutout().getSafeInsets());
+ }
+
+ @Test
public void computeSafeInsets_bounds() {
- DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000,
- 2000).getDisplayCutout();
+ DisplayCutout cutout =
+ WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000, 2000).getDisplayCutout();
assertEquals(mCutoutTop.getBoundingRects(), cutout.getBoundingRects());
}
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index d3958a65c704..0e32aee93ebd 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -30,7 +30,6 @@ cc_binary_host {
"utils.cpp",
],
cflags: [
- "-DSTATS_SCHEMA_LEGACY",
"-Wall",
"-Werror",
],
diff --git a/wifi/java/android/net/wifi/IScoreChangeCallback.aidl b/wifi/java/android/net/wifi/IScoreChangeCallback.aidl
index fd236107bc6e..462a97844d76 100644
--- a/wifi/java/android/net/wifi/IScoreChangeCallback.aidl
+++ b/wifi/java/android/net/wifi/IScoreChangeCallback.aidl
@@ -16,6 +16,8 @@
package android.net.wifi;
+import android.net.NetworkScore;
+
/**
* Interface for Wi-Fi network score callback.
*
@@ -23,7 +25,7 @@ package android.net.wifi;
*/
oneway interface IScoreChangeCallback
{
- void onStatusChange(int sessionId, boolean exiting);
+ void onScoreChange(int sessionId, in NetworkScore score);
void onTriggerUpdateOfWifiUsabilityStats(int sessionId);
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 76f97164032c..da521ed4d8a1 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -38,6 +38,7 @@ import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.MacAddress;
import android.net.Network;
+import android.net.NetworkScore;
import android.net.NetworkStack;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
@@ -5948,10 +5949,11 @@ public class WifiManager {
*
* @param sessionId The ID to indicate current Wi-Fi network connection obtained from
* {@link WifiConnectedNetworkScorer#start(int)}.
- * @param isUsable The bit to indicate whether current Wi-Fi network is usable or not.
- * Populated by connected network scorer in applications.
+ * @param score The {@link android.net.NetworkScore} object representing the
+ * characteristics of current Wi-Fi network. Populated by connected network
+ * scorer in applications.
*/
- void onStatusChange(int sessionId, boolean isUsable);
+ void onScoreChange(int sessionId, @NonNull NetworkScore score);
/**
* Called by applications to trigger an update of {@link WifiUsabilityStatsEntry}.
@@ -5977,9 +5979,9 @@ public class WifiManager {
}
@Override
- public void onStatusChange(int sessionId, boolean isUsable) {
+ public void onScoreChange(int sessionId, @NonNull NetworkScore score) {
try {
- mScoreChangeCallback.onStatusChange(sessionId, isUsable);
+ mScoreChangeCallback.onScoreChange(sessionId, score);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}