Merge "Add test for NetworkAgentConfig" into rvc-dev
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 7f23df7..da9f165 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,7 +41,7 @@
]
stubs_defaults {
- name: "metalava-non-updatable-api-stubs-default",
+ name: "metalava-base-api-stubs-default",
srcs: [
":framework-non-updatable-sources",
"core/java/**/*.logtags",
@@ -70,12 +70,18 @@
}
stubs_defaults {
- name: "metalava-api-stubs-default",
- defaults: ["metalava-non-updatable-api-stubs-default"],
+ name: "metalava-full-api-stubs-default",
+ defaults: ["metalava-base-api-stubs-default"],
srcs: [":framework-updatable-sources"],
sdk_version: "core_platform",
}
+stubs_defaults {
+ name: "metalava-non-updatable-api-stubs-default",
+ defaults: ["metalava-base-api-stubs-default"],
+ sdk_version: "system_current",
+}
+
/////////////////////////////////////////////////////////////////////
// *-api-stubs-docs modules providing source files for the stub libraries
/////////////////////////////////////////////////////////////////////
@@ -85,7 +91,7 @@
// modules
droidstubs {
name: "api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-full-api-stubs-default"],
api_filename: "public_api.txt",
private_api_filename: "private.txt",
removed_api_filename: "removed.txt",
@@ -124,7 +130,7 @@
droidstubs {
name: "system-api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-full-api-stubs-default"],
api_tag_name: "SYSTEM",
api_filename: "system-api.txt",
private_api_filename: "system-private.txt",
@@ -155,7 +161,7 @@
droidstubs {
name: "test-api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-full-api-stubs-default"],
api_tag_name: "TEST",
api_filename: "test-api.txt",
removed_api_filename: "test-removed.txt",
@@ -188,7 +194,7 @@
droidstubs {
name: "module-lib-api",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-full-api-stubs-default"],
arg_files: ["core/res/AndroidManifest.xml"],
args: metalava_framework_docs_args + module_libs,
check_api: {
@@ -216,7 +222,7 @@
droidstubs {
name: "module-lib-api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-non-updatable-api-stubs-default"],
arg_files: ["core/res/AndroidManifest.xml"],
args: metalava_framework_docs_args + priv_apps + module_libs,
}
@@ -266,6 +272,7 @@
name: "android_module_lib_stubs_current",
srcs: [ ":module-lib-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
+ libs: ["android_system_stubs_current"],
}
/////////////////////////////////////////////////////////////////////
@@ -317,7 +324,7 @@
droidstubs {
name: "hiddenapi-lists-docs",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-full-api-stubs-default"],
arg_files: [
"core/res/AndroidManifest.xml",
],
@@ -332,7 +339,7 @@
droidstubs {
name: "hiddenapi-mappings",
- defaults: ["metalava-api-stubs-default"],
+ defaults: ["metalava-full-api-stubs-default"],
srcs: [
":opt-telephony-common-srcs",
],
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index f8b598a..6d9e3ed 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -5,6 +5,7 @@
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.SystemForcedReasons;
import android.content.Context;
import android.os.Looper;
@@ -123,9 +124,10 @@
* appropriate time.
*
* @param restrictReason The restrictReason for restricting the app. Should be one of the
- * UsageStatsManager.REASON_SUB_RESTRICT_* reasons.
+ * UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_* reasons.
*/
- void restrictApp(@NonNull String packageName, int userId, int restrictReason);
+ void restrictApp(@NonNull String packageName, int userId,
+ @SystemForcedReasons int restrictReason);
void addActiveDeviceAdmin(String adminPkg, int userId);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index fc29c9c..819f253 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -974,7 +974,7 @@
if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
mAppStandbyInternal.restrictApp(
- pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
+ pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
final boolean isDebuggable;
synchronized (mLock) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 249bc52..e14ca99 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -58,6 +58,7 @@
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager.StandbyBuckets;
+import android.app.usage.UsageStatsManager.SystemForcedReasons;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -1153,6 +1154,13 @@
}
}
+ @VisibleForTesting
+ int getAppStandbyBucketReason(String packageName, int userId, long elapsedRealtime) {
+ synchronized (mAppIdleLock) {
+ return mAppIdleHistory.getAppStandbyReason(packageName, userId, elapsedRealtime);
+ }
+ }
+
@Override
public List<AppStandbyInfo> getAppStandbyBuckets(int userId) {
synchronized (mAppIdleLock) {
@@ -1161,7 +1169,8 @@
}
@Override
- public void restrictApp(@NonNull String packageName, int userId, int restrictReason) {
+ public void restrictApp(@NonNull String packageName, int userId,
+ @SystemForcedReasons int restrictReason) {
// If the package is not installed, don't allow the bucket to be set.
if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
Slog.e(TAG, "Tried to restrict uninstalled app: " + packageName);
@@ -1248,30 +1257,52 @@
// Don't allow changing bucket if higher than ACTIVE
if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
- // Don't allow prediction to change from/to NEVER or from RESTRICTED.
- if ((app.currentBucket == STANDBY_BUCKET_NEVER
- || app.currentBucket == STANDBY_BUCKET_RESTRICTED
- || newBucket == STANDBY_BUCKET_NEVER)
+ // Don't allow prediction to change from/to NEVER.
+ if ((app.currentBucket == STANDBY_BUCKET_NEVER || newBucket == STANDBY_BUCKET_NEVER)
&& predicted) {
return;
}
+ final boolean wasForcedBySystem =
+ (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM;
+
// If the bucket was forced, don't allow prediction to override
if (predicted
&& ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER
- || (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM)) {
+ || wasForcedBySystem)) {
+ return;
+ }
+
+ final boolean isForcedBySystem =
+ (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM;
+
+ if (app.currentBucket == newBucket && wasForcedBySystem && isForcedBySystem) {
+ mAppIdleHistory
+ .noteRestrictionAttempt(packageName, userId, elapsedRealtime, reason);
+ // Keep track of all restricting reasons
+ reason = REASON_MAIN_FORCED_BY_SYSTEM
+ | (app.bucketingReason & REASON_SUB_MASK)
+ | (reason & REASON_SUB_MASK);
+ mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime,
+ newBucket, reason, resetTimeout);
return;
}
final boolean isForcedByUser =
(reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER;
- // If the current bucket is RESTRICTED, only user force or usage should bring it out,
- // unless the app was put into the bucket due to timing out.
- if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason)
- && !isForcedByUser
- && (app.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_TIMEOUT) {
- return;
+ if (app.currentBucket == STANDBY_BUCKET_RESTRICTED) {
+ if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_TIMEOUT) {
+ if (predicted && newBucket >= STANDBY_BUCKET_RARE) {
+ // Predicting into RARE or below means we don't expect the user to use the
+ // app anytime soon, so don't elevate it from RESTRICTED.
+ return;
+ }
+ } else if (!isUserUsage(reason) && !isForcedByUser) {
+ // If the current bucket is RESTRICTED, only user force or usage should bring
+ // it out, unless the app was put into the bucket due to timing out.
+ return;
+ }
}
if (newBucket == STANDBY_BUCKET_RESTRICTED) {
diff --git a/api/current.txt b/api/current.txt
index 3ed4125..5e8158b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37160,12 +37160,10 @@
method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
method @NonNull public android.os.VibrationEffect compose();
field public static final int PRIMITIVE_CLICK = 1; // 0x1
- field public static final int PRIMITIVE_LIGHT_TICK = 7; // 0x7
field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
field public static final int PRIMITIVE_QUICK_RISE = 4; // 0x4
field public static final int PRIMITIVE_SLOW_RISE = 5; // 0x5
- field public static final int PRIMITIVE_SPIN = 3; // 0x3
- field public static final int PRIMITIVE_THUD = 2; // 0x2
+ field public static final int PRIMITIVE_TICK = 7; // 0x7
}
public abstract class Vibrator {
@@ -47835,6 +47833,7 @@
method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, long);
+ method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
method public void sendTextMessage(@NonNull String, @Nullable String, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent, long);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
@@ -49000,8 +48999,8 @@
ctor public RegistrationManager.RegistrationCallback();
method public void onRegistered(int);
method public void onRegistering(int);
- method public void onTechnologyChangeFailed(int, @Nullable android.telephony.ims.ImsReasonInfo);
- method public void onUnregistered(@Nullable android.telephony.ims.ImsReasonInfo);
+ method public void onTechnologyChangeFailed(int, @NonNull android.telephony.ims.ImsReasonInfo);
+ method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo);
}
}
@@ -55588,7 +55587,7 @@
public interface WindowInsetsController {
method public void addOnControllableInsetsChangedListener(@NonNull android.view.WindowInsetsController.OnControllableInsetsChangedListener);
- method @NonNull public android.os.CancellationSignal controlWindowInsetsAnimation(int, long, @Nullable android.view.animation.Interpolator, @NonNull android.view.WindowInsetsAnimationControlListener);
+ method public void controlWindowInsetsAnimation(int, long, @Nullable android.view.animation.Interpolator, @Nullable android.os.CancellationSignal, @NonNull android.view.WindowInsetsAnimationControlListener);
method public int getSystemBarsAppearance();
method public int getSystemBarsBehavior();
method public void hide(int);
@@ -56812,7 +56811,7 @@
public static final class InlinePresentationSpec.Builder {
ctor public InlinePresentationSpec.Builder(@NonNull android.util.Size, @NonNull android.util.Size);
method @NonNull public android.view.inline.InlinePresentationSpec build();
- method @NonNull public android.view.inline.InlinePresentationSpec.Builder setStyle(@Nullable android.os.Bundle);
+ method @NonNull public android.view.inline.InlinePresentationSpec.Builder setStyle(@NonNull android.os.Bundle);
}
}
@@ -57024,7 +57023,7 @@
ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.view.inline.InlinePresentationSpec>);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(@NonNull android.view.inline.InlinePresentationSpec);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
- method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 843fef9..ab3bcc1 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -252,9 +252,6 @@
public static final class R.array {
field public static final int config_keySystemUuidMapping = 17235973; // 0x1070005
- field public static final int config_restrictedPreinstalledCarrierApps = 17235975; // 0x1070007
- field public static final int config_sms_enabled_locking_shift_tables = 17235977; // 0x1070009
- field public static final int config_sms_enabled_single_shift_tables = 17235976; // 0x1070008
field public static final int simColors = 17235974; // 0x1070006
}
@@ -307,7 +304,6 @@
field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
field public static final int config_systemGallery = 17039402; // 0x104002a
- field public static final int low_memory = 17039403; // 0x104002b
}
public static final class R.style {
@@ -408,7 +404,6 @@
field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
field public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
- field public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
@@ -758,7 +753,6 @@
public class StatusBarManager {
method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
- method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
}
public static final class StatusBarManager.DisableInfo {
@@ -2282,7 +2276,6 @@
field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
field public static final int MATCH_ANY_USER = 4194304; // 0x400000
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
- field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
field public static final int MATCH_INSTANT = 8388608; // 0x800000
field public static final int MODULE_APEX_NAME = 1; // 0x1
field public static final int RESTRICTION_HIDE_FROM_SUGGESTIONS = 1; // 0x1
@@ -5366,8 +5359,9 @@
field public static final int SIGNAL_TYPE_UNDEFINED = 0; // 0x0
}
- public static class AnalogFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder> {
+ public static class AnalogFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings build();
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSifStandard(int);
method @NonNull public android.media.tv.tuner.frontend.AnalogFrontendSettings.Builder setSignalType(int);
}
@@ -5431,10 +5425,11 @@
field public static final int TIME_INTERLEAVE_MODE_UNDEFINED = 0; // 0x0
}
- public static class Atsc3FrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder> {
+ public static class Atsc3FrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setBandwidth(int);
method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setDemodOutputFormat(int);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.Atsc3FrontendSettings.Builder setPlpSettings(@NonNull android.media.tv.tuner.frontend.Atsc3PlpSettings[]);
}
@@ -5475,8 +5470,9 @@
field public static final int MODULATION_UNDEFINED = 0; // 0x0
}
- public static class AtscFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.AtscFrontendSettings.Builder> {
+ public static class AtscFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings build();
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.AtscFrontendSettings.Builder setModulation(int);
}
@@ -5514,9 +5510,10 @@
field public static final int SPECTRAL_INVERSION_UNDEFINED = 0; // 0x0
}
- public static class DvbcFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder> {
+ public static class DvbcFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setAnnex(int);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setInnerFec(long);
method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setModulation(int);
method @NonNull public android.media.tv.tuner.frontend.DvbcFrontendSettings.Builder setOuterFec(int);
@@ -5592,9 +5589,10 @@
field public static final int VCM_MODE_UNDEFINED = 0; // 0x0
}
- public static class DvbsFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder> {
+ public static class DvbsFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCodeRate(@Nullable android.media.tv.tuner.frontend.DvbsCodeRate);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setInputStreamId(int);
method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setModulation(int);
method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setPilot(int);
@@ -5691,10 +5689,11 @@
field public static final int TRANSMISSION_MODE_UNDEFINED = 0; // 0x0
}
- public static class DvbtFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder> {
+ public static class DvbtFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setBandwidth(int);
method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setConstellation(int);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setGuardInterval(int);
method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHierarchy(int);
method @NonNull public android.media.tv.tuner.frontend.DvbtFrontendSettings.Builder setHighPriority(boolean);
@@ -5775,10 +5774,6 @@
field public static final int TYPE_UNDEFINED = 0; // 0x0
}
- public abstract static class FrontendSettings.Builder<T extends android.media.tv.tuner.frontend.FrontendSettings.Builder<T>> {
- method @IntRange(from=1) @NonNull public T setFrequency(int);
- }
-
public class FrontendStatus {
method public int getAgc();
method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpInfo[] getAtsc3PlpInfo();
@@ -5870,9 +5865,10 @@
field public static final int ROLLOFF_UNDEFINED = 0; // 0x0
}
- public static class Isdbs3FrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder> {
+ public static class Isdbs3FrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setCodeRate(int);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setModulation(int);
method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setRolloff(int);
method @NonNull public android.media.tv.tuner.frontend.Isdbs3FrontendSettings.Builder setStreamId(int);
@@ -5912,9 +5908,10 @@
field public static final int STREAM_ID_TYPE_RELATIVE_NUMBER = 1; // 0x1
}
- public static class IsdbsFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder> {
+ public static class IsdbsFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setCodeRate(int);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setModulation(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setRolloff(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbsFrontendSettings.Builder setStreamId(int);
@@ -5957,10 +5954,11 @@
field public static final int MODULATION_UNDEFINED = 0; // 0x0
}
- public static class IsdbtFrontendSettings.Builder extends android.media.tv.tuner.frontend.FrontendSettings.Builder<android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder> {
+ public static class IsdbtFrontendSettings.Builder {
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings build();
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setBandwidth(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setCodeRate(int);
+ method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setFrequency(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setGuardInterval(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setMode(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.Builder setModulation(int);
@@ -7461,24 +7459,7 @@
}
public final class WifiMigration {
- method @Nullable public static android.net.wifi.WifiMigration.ConfigStoreMigrationData loadFromConfigStore();
method @NonNull public static android.net.wifi.WifiMigration.SettingsMigrationData loadFromSettings(@NonNull android.content.Context);
- method public static void removeConfigStore();
- }
-
- public static final class WifiMigration.ConfigStoreMigrationData implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
- method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiMigration.ConfigStoreMigrationData> CREATOR;
- }
-
- public static final class WifiMigration.ConfigStoreMigrationData.Builder {
- ctor public WifiMigration.ConfigStoreMigrationData.Builder();
- method @NonNull public android.net.wifi.WifiMigration.ConfigStoreMigrationData build();
- method @NonNull public android.net.wifi.WifiMigration.ConfigStoreMigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
- method @NonNull public android.net.wifi.WifiMigration.ConfigStoreMigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
}
public static final class WifiMigration.SettingsMigrationData implements android.os.Parcelable {
@@ -9342,7 +9323,6 @@
field public static final String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length";
field public static final String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length";
field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled";
- field public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
field public static final String DOZE_ALWAYS_ON = "doze_always_on";
field public static final String HUSH_GESTURE_USED = "hush_gesture_used";
@@ -11278,7 +11258,6 @@
public class ServiceState implements android.os.Parcelable {
method public void fillInNotifierBundle(@NonNull android.os.Bundle);
- method public int getDataNetworkType();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForTransportType(int);
@@ -11424,7 +11403,6 @@
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
- method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
@@ -11523,6 +11501,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getAidForAppType(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
+ method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CallForwardingInfo getCallForwarding(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCallWaitingStatus();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
@@ -11541,12 +11520,11 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
method @Deprecated public boolean getDataEnabled();
method @Deprecated public boolean getDataEnabled(int);
- method @Nullable public static android.content.ComponentName getDefaultRespondViaMessageApplication(@NonNull android.content.Context, boolean);
+ method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getDefaultRespondViaMessageApplication();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDeviceSoftwareVersion(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -12283,10 +12261,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
}
- public class ImsManager {
- field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
- }
-
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -12552,82 +12526,12 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
- field public static final int KEY_1X_EPDG_TIMER_SEC = 64; // 0x40
- field public static final int KEY_1X_THRESHOLD = 59; // 0x3b
- field public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50; // 0x32
- field public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0; // 0x0
- field public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53; // 0x35
- field public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49; // 0x31
- field public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48; // 0x30
- field public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1; // 0x1
- field public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47; // 0x2f
- field public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52; // 0x34
- field public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51; // 0x33
- field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
- field public static final int KEY_ENABLE_SILENT_REDIAL = 6; // 0x6
- field public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31; // 0x1f
- field public static final int KEY_LTE_EPDG_TIMER_SEC = 62; // 0x3e
- field public static final int KEY_LTE_THRESHOLD_1 = 56; // 0x38
- field public static final int KEY_LTE_THRESHOLD_2 = 57; // 0x39
- field public static final int KEY_LTE_THRESHOLD_3 = 58; // 0x3a
- field public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3; // 0x3
- field public static final int KEY_MOBILE_DATA_ENABLED = 29; // 0x1d
- field public static final int KEY_MULTIENDPOINT_ENABLED = 65; // 0x41
- field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
- field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
- field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
- field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
- field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
- field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
- field public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; // 0x10
- field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
- field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
- field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc
- field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21
- field public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34; // 0x22
- field public static final int KEY_RTP_SPEECH_END_PORT = 36; // 0x24
- field public static final int KEY_RTP_SPEECH_START_PORT = 35; // 0x23
- field public static final int KEY_RTT_ENABLED = 66; // 0x42
- field public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43; // 0x2b
- field public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44; // 0x2c
- field public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38; // 0x26
- field public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4; // 0x4
- field public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37; // 0x25
- field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42; // 0x2a
- field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39; // 0x27
- field public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; // 0x20
- field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45; // 0x2d
- field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40; // 0x28
- field public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46; // 0x2e
- field public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41; // 0x29
- field public static final int KEY_SIP_SESSION_TIMER_SEC = 2; // 0x2
- field public static final int KEY_SMS_FORMAT = 13; // 0xd
- field public static final int KEY_SMS_OVER_IP_ENABLED = 14; // 0xe
- field public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54; // 0x36
- field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
- field public static final int KEY_T2_TIMER_VALUE_MS = 8; // 0x8
- field public static final int KEY_TF_TIMER_VALUE_MS = 9; // 0x9
- field public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5; // 0x5
- field public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24; // 0x18
- field public static final int KEY_VIDEO_QUALITY = 55; // 0x37
- field public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28; // 0x1c
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
- field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
- field public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30; // 0x1e
- field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
- field public static final int KEY_WIFI_EPDG_TIMER_SEC = 63; // 0x3f
- field public static final int KEY_WIFI_THRESHOLD_A = 60; // 0x3c
- field public static final int KEY_WIFI_THRESHOLD_B = 61; // 0x3d
- field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
- field public static final int SMS_FORMAT_3GPP = 1; // 0x1
- field public static final int SMS_FORMAT_3GPP2 = 0; // 0x0
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
- field public static final int VIDEO_QUALITY_HIGH = 1; // 0x1
- field public static final int VIDEO_QUALITY_LOW = 0; // 0x0
}
public static class ProvisioningManager.Callback {
@@ -12636,56 +12540,6 @@
method public void onProvisioningStringChanged(int, @NonNull String);
}
- public final class RcsContactUceCapability implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
- method @NonNull public android.net.Uri getContactUri();
- method @Nullable public android.net.Uri getServiceUri(long);
- method public boolean isCapable(long);
- method public boolean isCapable(@NonNull String);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CAPABILITY_CALL_COMPOSER = 4194304; // 0x400000
- field public static final int CAPABILITY_CHAT_BOT = 67108864; // 0x4000000
- field public static final int CAPABILITY_CHAT_BOT_ROLE = 134217728; // 0x8000000
- field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
- field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
- field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
- field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
- field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
- field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
- field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
- field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
- field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
- field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
- field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
- field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
- field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
- field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
- field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
- field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
- field public static final int CAPABILITY_MMTEL_CALL_COMPOSER = 1073741824; // 0x40000000
- field public static final int CAPABILITY_PLUG_IN = 268435456; // 0x10000000
- field public static final int CAPABILITY_POST_CALL = 8388608; // 0x800000
- field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
- field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
- field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
- field public static final int CAPABILITY_SHARED_MAP = 16777216; // 0x1000000
- field public static final int CAPABILITY_SHARED_SKETCH = 33554432; // 0x2000000
- field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
- field public static final int CAPABILITY_STANDALONE_CHAT_BOT = 536870912; // 0x20000000
- field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
- field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
- }
-
- public static class RcsContactUceCapability.Builder {
- ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
- method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long, @NonNull android.net.Uri);
- method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long);
- method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
- method @NonNull public android.telephony.ims.RcsContactUceCapability build();
- }
-
public class RcsUceAdapter {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
}
@@ -12932,7 +12786,6 @@
method public int transact(android.os.Bundle);
method public int updateCallBarring(int, int, String[]);
method public int updateCallBarringForServiceClass(int, int, String[], int);
- method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String);
method public int updateCallForward(int, int, String, int, int);
method public int updateCallWaiting(boolean, int);
method public int updateClip(boolean);
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 55333cf..10c96a3 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -246,12 +246,6 @@
-ResourceValueFieldName: android.R.array#config_sms_enabled_locking_shift_tables:
- Expected resource name in `android.R.array` to be in the `fooBarBaz` style, was `config_sms_enabled_locking_shift_tables`
-ResourceValueFieldName: android.R.array#config_sms_enabled_single_shift_tables:
- Expected resource name in `android.R.array` to be in the `fooBarBaz` style, was `config_sms_enabled_single_shift_tables`
-
-
SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
diff --git a/api/test-current.txt b/api/test-current.txt
index e98a058..a1a652f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -857,6 +857,7 @@
method @NonNull public android.content.integrity.RuleSet getCurrentRuleSet();
method @NonNull public String getCurrentRuleSetProvider();
method @NonNull public String getCurrentRuleSetVersion();
+ method @NonNull public java.util.List<java.lang.String> getWhitelistedRuleProviders();
method public void updateRuleSet(@NonNull android.content.integrity.RuleSet, @NonNull android.content.IntentSender);
field public static final String EXTRA_STATUS = "android.content.integrity.extra.STATUS";
field public static final int STATUS_FAILURE = 1; // 0x1
@@ -3434,13 +3435,15 @@
package android.service.quickaccesswallet {
- public interface QuickAccessWalletClient {
+ public interface QuickAccessWalletClient extends java.io.Closeable {
method public void addWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
+ method public void addWalletServiceEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener);
method @NonNull public static android.service.quickaccesswallet.QuickAccessWalletClient create(@NonNull android.content.Context);
method @Nullable public android.content.Intent createWalletIntent();
method @Nullable public android.content.Intent createWalletSettingsIntent();
method public void disconnect();
method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
+ method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback);
method public boolean isWalletFeatureAvailable();
method public boolean isWalletFeatureAvailableWhenDeviceLocked();
method public boolean isWalletServiceAvailable();
@@ -3735,7 +3738,6 @@
public final class SmsManager {
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int checkSmsShortCodeDestination(String, String);
- method public void sendMultipartTextMessage(@NonNull String, @Nullable String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
field public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1; // 0x1
field public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0; // 0x0
field public static final int SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3; // 0x3
@@ -3756,9 +3758,10 @@
public class TelephonyManager {
method public int addDevicePolicyOverrideApn(@NonNull android.content.Context, @NonNull android.telephony.data.ApnSetting);
method public int checkCarrierPrivilegesForPackage(String);
+ method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method public int getCarrierIdListVersion();
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
- method @Nullable public static android.content.ComponentName getDefaultRespondViaMessageApplication(@NonNull android.content.Context, boolean);
+ method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getDefaultRespondViaMessageApplication();
method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getEmergencyNumberDbVersion();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
@@ -4034,10 +4037,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
}
- public class ImsManager {
- field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
- }
-
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
method @Deprecated @NonNull @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -4299,82 +4298,12 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
- field public static final int KEY_1X_EPDG_TIMER_SEC = 64; // 0x40
- field public static final int KEY_1X_THRESHOLD = 59; // 0x3b
- field public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50; // 0x32
- field public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0; // 0x0
- field public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53; // 0x35
- field public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49; // 0x31
- field public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48; // 0x30
- field public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1; // 0x1
- field public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47; // 0x2f
- field public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52; // 0x34
- field public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51; // 0x33
- field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
- field public static final int KEY_ENABLE_SILENT_REDIAL = 6; // 0x6
- field public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31; // 0x1f
- field public static final int KEY_LTE_EPDG_TIMER_SEC = 62; // 0x3e
- field public static final int KEY_LTE_THRESHOLD_1 = 56; // 0x38
- field public static final int KEY_LTE_THRESHOLD_2 = 57; // 0x39
- field public static final int KEY_LTE_THRESHOLD_3 = 58; // 0x3a
- field public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3; // 0x3
- field public static final int KEY_MOBILE_DATA_ENABLED = 29; // 0x1d
- field public static final int KEY_MULTIENDPOINT_ENABLED = 65; // 0x41
- field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
- field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
- field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
- field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
- field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
- field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
- field public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16; // 0x10
- field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
- field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
- field public static final int KEY_REGISTRATION_DOMAIN_NAME = 12; // 0xc
- field public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33; // 0x21
- field public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34; // 0x22
- field public static final int KEY_RTP_SPEECH_END_PORT = 36; // 0x24
- field public static final int KEY_RTP_SPEECH_START_PORT = 35; // 0x23
- field public static final int KEY_RTT_ENABLED = 66; // 0x42
- field public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43; // 0x2b
- field public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44; // 0x2c
- field public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38; // 0x26
- field public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4; // 0x4
- field public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37; // 0x25
- field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42; // 0x2a
- field public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39; // 0x27
- field public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32; // 0x20
- field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45; // 0x2d
- field public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40; // 0x28
- field public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46; // 0x2e
- field public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41; // 0x29
- field public static final int KEY_SIP_SESSION_TIMER_SEC = 2; // 0x2
- field public static final int KEY_SMS_FORMAT = 13; // 0xd
- field public static final int KEY_SMS_OVER_IP_ENABLED = 14; // 0xe
- field public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54; // 0x36
- field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
- field public static final int KEY_T2_TIMER_VALUE_MS = 8; // 0x8
- field public static final int KEY_TF_TIMER_VALUE_MS = 9; // 0x9
- field public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5; // 0x5
- field public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24; // 0x18
- field public static final int KEY_VIDEO_QUALITY = 55; // 0x37
- field public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28; // 0x1c
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
- field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
- field public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30; // 0x1e
- field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
- field public static final int KEY_WIFI_EPDG_TIMER_SEC = 63; // 0x3f
- field public static final int KEY_WIFI_THRESHOLD_A = 60; // 0x3c
- field public static final int KEY_WIFI_THRESHOLD_B = 61; // 0x3d
- field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
- field public static final int SMS_FORMAT_3GPP = 1; // 0x1
- field public static final int SMS_FORMAT_3GPP2 = 0; // 0x0
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
- field public static final int VIDEO_QUALITY_HIGH = 1; // 0x1
- field public static final int VIDEO_QUALITY_LOW = 0; // 0x0
}
public static class ProvisioningManager.Callback {
@@ -4383,56 +4312,6 @@
method public void onProvisioningStringChanged(int, @NonNull String);
}
- public final class RcsContactUceCapability implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
- method @NonNull public android.net.Uri getContactUri();
- method @Nullable public android.net.Uri getServiceUri(long);
- method public boolean isCapable(long);
- method public boolean isCapable(@NonNull String);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CAPABILITY_CALL_COMPOSER = 4194304; // 0x400000
- field public static final int CAPABILITY_CHAT_BOT = 67108864; // 0x4000000
- field public static final int CAPABILITY_CHAT_BOT_ROLE = 134217728; // 0x8000000
- field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
- field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
- field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
- field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
- field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
- field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
- field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
- field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
- field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
- field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
- field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
- field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
- field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
- field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
- field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
- field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
- field public static final int CAPABILITY_MMTEL_CALL_COMPOSER = 1073741824; // 0x40000000
- field public static final int CAPABILITY_PLUG_IN = 268435456; // 0x10000000
- field public static final int CAPABILITY_POST_CALL = 8388608; // 0x800000
- field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
- field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
- field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
- field public static final int CAPABILITY_SHARED_MAP = 16777216; // 0x1000000
- field public static final int CAPABILITY_SHARED_SKETCH = 33554432; // 0x2000000
- field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
- field public static final int CAPABILITY_STANDALONE_CHAT_BOT = 536870912; // 0x20000000
- field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
- field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
- }
-
- public static class RcsContactUceCapability.Builder {
- ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
- method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long, @NonNull android.net.Uri);
- method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(long);
- method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
- method @NonNull public android.telephony.ims.RcsContactUceCapability build();
- }
-
public class RcsUceAdapter {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
}
@@ -4679,7 +4558,6 @@
method public int transact(android.os.Bundle);
method public int updateCallBarring(int, int, String[]);
method public int updateCallBarringForServiceClass(int, int, String[], int);
- method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String);
method public int updateCallForward(int, int, String, int, int);
method public int updateCallWaiting(boolean, int);
method public int updateClip(boolean);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index ec11043..489a0de 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Activity manager local system service interface.
@@ -124,6 +125,12 @@
public abstract int getUidProcessState(int uid);
/**
+ * Get a map of pid and package name that process of that pid Android/data and Android/obb
+ * directory is not mounted to lowerfs.
+ */
+ public abstract Map<Integer, String> getProcessesWithPendingBindMounts(int userId);
+
+ /**
* @return {@code true} if system is ready, {@code false} otherwise.
*/
public abstract boolean isSystemReady();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0b26b2b..fa4aa19 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1369,7 +1369,6 @@
@SystemApi
public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
/** @hide Read device identifiers */
- @SystemApi
public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
/** @hide Query all packages on device */
public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 061e5ff..864af3d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7526,9 +7526,11 @@
p,
bindResult);
addExtras(mBuilder.mN.extras);
- // also update the end margin if there is an image
- contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
- bindResult.getIconMarginEnd());
+ if (!CONVERSATION_LAYOUT_ENABLED) {
+ // also update the end margin if there is an image
+ contentView.setViewLayoutMarginEnd(R.id.notification_messaging,
+ bindResult.getIconMarginEnd());
+ }
contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p)
: mBuilder.resolveContrastColor(p));
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index dc8269f..81396fe 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -34,6 +34,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -306,7 +307,8 @@
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java
* bind}
*/
-public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
+public abstract class Service extends ContextWrapper implements ComponentCallbacks2,
+ ContentCaptureManager.ContentCaptureClient {
private static final String TAG = "Service";
/**
@@ -817,8 +819,16 @@
writer.println("nothing to dump");
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ if (newBase != null) {
+ newBase.setContentCaptureOptions(getContentCaptureOptions());
+ }
+ }
+
// ------------------ Internal API ------------------
-
+
/**
* @hide
*/
@@ -835,6 +845,7 @@
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
+ setContentCaptureOptions(application.getContentCaptureOptions());
}
/**
@@ -849,6 +860,18 @@
return mClassName;
}
+ /** @hide */
+ @Override
+ public final ContentCaptureManager.ContentCaptureClient getContentCaptureClient() {
+ return this;
+ }
+
+ /** @hide */
+ @Override
+ public final ComponentName contentCaptureClientGetComponentName() {
+ return new ComponentName(this, mClassName);
+ }
+
// set by the thread after the constructor and before onCreate(Bundle icicle) is called.
@UnsupportedAppUsage
private ActivityThread mThread = null;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 42563b5..1329fa4 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -399,7 +399,6 @@
* {@code false}, re-enables expansion of the status bar.
* @hide
*/
- @SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
public void setDisabledForSimNetworkLock(boolean disabled) {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 1a84547..b2dd0ef 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -23,6 +23,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.RemoteException;
@@ -161,6 +162,13 @@
*/
public @WindowConfiguration.ActivityType int topActivityType;
+ /**
+ * The {@link ActivityInfo} of the top activity in this task.
+ * @hide
+ */
+ @Nullable
+ public ActivityInfo topActivityInfo;
+
TaskInfo() {
// Do nothing
}
@@ -217,8 +225,11 @@
token = IWindowContainer.Stub.asInterface(source.readStrongBinder());
topActivityType = source.readInt();
pictureInPictureParams = source.readInt() != 0
- ? PictureInPictureParams.CREATOR.createFromParcel(source)
- : null;
+ ? PictureInPictureParams.CREATOR.createFromParcel(source)
+ : null;
+ topActivityInfo = source.readInt() != 0
+ ? ActivityInfo.CREATOR.createFromParcel(source)
+ : null;
}
/**
@@ -262,6 +273,12 @@
dest.writeInt(1);
pictureInPictureParams.writeToParcel(dest, flags);
}
+ if (topActivityInfo == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ topActivityInfo.writeToParcel(dest, flags);
+ }
}
@Override
@@ -278,6 +295,7 @@
+ " resizeMode=" + resizeMode
+ " token=" + token
+ " topActivityType=" + topActivityType
- + " pictureInPictureParams=" + pictureInPictureParams;
+ + " pictureInPictureParams=" + pictureInPictureParams
+ + " topActivityInfo=" + topActivityInfo;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bc8d05e..32e0f73 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5736,6 +5736,10 @@
throwIfParentInstance("isAlwaysOnVpnLockdownEnabled");
if (mService != null) {
try {
+ // Starting from Android R, the caller can pass the permission check in
+ // DevicePolicyManagerService if it holds android.permission.MAINLINE_NETWORK_STACK.
+ // Note that the android.permission.MAINLINE_NETWORK_STACK is a signature permission
+ // which is used by the NetworkStack mainline module.
return mService.isAlwaysOnVpnLockdownEnabled(admin);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 2c701b4..0d66198 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -288,25 +288,25 @@
*/
public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001;
/**
- * The reason for restricting the app is unknown or undefined.
+ * The reason the system forced the app into the bucket is unknown or undefined.
* @hide
*/
- public static final int REASON_SUB_RESTRICT_UNDEFINED = 0x0000;
+ public static final int REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED = 0;
/**
* The app was unnecessarily using system resources (battery, memory, etc) in the background.
* @hide
*/
- public static final int REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE = 0x0001;
+ public static final int REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE = 1 << 0;
/**
* The app was deemed to be intentionally abusive.
* @hide
*/
- public static final int REASON_SUB_RESTRICT_ABUSE = 0x0002;
+ public static final int REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE = 1 << 1;
/**
* The app was displaying buggy behavior.
* @hide
*/
- public static final int REASON_SUB_RESTRICT_BUGGY = 0x0003;
+ public static final int REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY = 1 << 2;
/** @hide */
@@ -322,6 +322,17 @@
@Retention(RetentionPolicy.SOURCE)
public @interface StandbyBuckets {}
+ /** @hide */
+ @IntDef(flag = true, prefix = {"REASON_SUB_FORCED_SYSTEM_FLAG_FLAG_"}, value = {
+ REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
+ REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
+ REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
+ REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemForcedReasons {
+ }
+
/**
* Observer id of the registered observer for the group of packages that reached the usage
* time limit. Included as an extra in the PendingIntent that was registered.
@@ -1053,6 +1064,7 @@
/** @hide */
public static String reasonToString(int standbyReason) {
+ final int subReason = standbyReason & REASON_SUB_MASK;
StringBuilder sb = new StringBuilder();
switch (standbyReason & REASON_MAIN_MASK) {
case REASON_MAIN_DEFAULT:
@@ -1060,19 +1072,8 @@
break;
case REASON_MAIN_FORCED_BY_SYSTEM:
sb.append("s");
- switch (standbyReason & REASON_SUB_MASK) {
- case REASON_SUB_RESTRICT_ABUSE:
- sb.append("-ra");
- break;
- case REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE:
- sb.append("-rbru");
- break;
- case REASON_SUB_RESTRICT_BUGGY:
- sb.append("-rb");
- break;
- case REASON_SUB_RESTRICT_UNDEFINED:
- sb.append("-r");
- break;
+ if (subReason > 0) {
+ sb.append("-").append(Integer.toBinaryString(subReason));
}
break;
case REASON_MAIN_FORCED_BY_USER:
@@ -1080,7 +1081,7 @@
break;
case REASON_MAIN_PREDICTED:
sb.append("p");
- switch (standbyReason & REASON_SUB_MASK) {
+ switch (subReason) {
case REASON_SUB_PREDICTED_RESTORED:
sb.append("-r");
break;
@@ -1091,7 +1092,7 @@
break;
case REASON_MAIN_USAGE:
sb.append("u");
- switch (standbyReason & REASON_SUB_MASK) {
+ switch (subReason) {
case REASON_SUB_USAGE_SYSTEM_INTERACTION:
sb.append("-si");
break;
diff --git a/core/java/android/content/integrity/AppIntegrityManager.java b/core/java/android/content/integrity/AppIntegrityManager.java
index 9f95d4d..2869abb 100644
--- a/core/java/android/content/integrity/AppIntegrityManager.java
+++ b/core/java/android/content/integrity/AppIntegrityManager.java
@@ -25,6 +25,8 @@
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
+import java.util.List;
+
/**
* Class for pushing rules used to check the integrity of app installs.
*
@@ -121,4 +123,21 @@
throw e.rethrowAsRuntimeException();
}
}
+
+ /**
+ * Get the package names of all whitelisted rule providers.
+ *
+ * <p>Warning: this method is only used for tests.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public List<String> getWhitelistedRuleProviders() {
+ try {
+ return mManager.getWhitelistedRuleProviders();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/content/integrity/IAppIntegrityManager.aidl b/core/java/android/content/integrity/IAppIntegrityManager.aidl
index 4714ad7..94197bb 100644
--- a/core/java/android/content/integrity/IAppIntegrityManager.aidl
+++ b/core/java/android/content/integrity/IAppIntegrityManager.aidl
@@ -19,6 +19,7 @@
import android.content.integrity.Rule;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
+import java.util.List;
/** @hide */
interface IAppIntegrityManager {
@@ -26,4 +27,5 @@
String getCurrentRuleSetVersion();
String getCurrentRuleSetProvider();
ParceledListSlice<Rule> getCurrentRules();
+ List<String> getWhitelistedRuleProviders();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ea941cc..05ff830 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -557,7 +557,6 @@
* Internal {@link PackageInfo} flag used to indicate that a package is a hidden system app.
* @hide
*/
- @SystemApi
public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 0x20000000;
/**
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index d0091440..1710ccb 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -368,7 +368,14 @@
/** Class that includes information for a suspected data stall on a specific Network */
public static final class DataStallReport implements Parcelable {
+ /**
+ * Indicates that the Data Stall was detected using DNS events.
+ */
public static final int DETECTION_METHOD_DNS_EVENTS = 1;
+
+ /**
+ * Indicates that the Data Stall was detected using TCP metrics.
+ */
public static final int DETECTION_METHOD_TCP_METRICS = 2;
/** @hide */
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index f2fb5b2..09ccb72 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -23,6 +23,7 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
+import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -88,6 +89,16 @@
private static final File DIR_APEX_ROOT = getDirectory(ENV_APEX_ROOT,
"/apex");
+ /**
+ * See definition in com.android.providers.media.LocalCallingIdentity
+ */
+ private static final long DEFAULT_SCOPED_STORAGE = 149924527L;
+
+ /**
+ * See definition in com.android.providers.media.LocalCallingIdentity
+ */
+ private static final long FORCE_ENABLE_SCOPED_STORAGE = 132649864L;
+
@UnsupportedAppUsage
private static UserEnvironment sCurrentUser;
private static boolean sUserRequired;
@@ -1191,12 +1202,13 @@
}
/**
- * Returns whether the primary shared/external storage media is a legacy
- * view that includes files not owned by the app.
+ * Returns whether the shared/external storage media is a
+ * legacy view that includes files not owned by the app.
* <p>
* This value may be different from the value requested by
* {@code requestLegacyExternalStorage} in the app's manifest, since an app
- * may inherit its legacy state based on when it was first installed.
+ * may inherit its legacy state based on when it was first installed, target sdk and other
+ * factors.
* <p>
* Non-legacy apps can continue to discover and read media belonging to
* other apps via {@link android.provider.MediaStore}.
@@ -1207,18 +1219,19 @@
}
/**
- * Returns whether the shared/external storage media at the given path is a
+ * Returns whether the shared/external storage media is a
* legacy view that includes files not owned by the app.
* <p>
* This value may be different from the value requested by
* {@code requestLegacyExternalStorage} in the app's manifest, since an app
- * may inherit its legacy state based on when it was first installed.
+ * may inherit its legacy state based on when it was first installed, target sdk and other
+ * factors.
* <p>
* Non-legacy apps can continue to discover and read media belonging to
* other apps via {@link android.provider.MediaStore}.
*
* @throws IllegalArgumentException if the path is not a valid storage
- * device.
+ * device.
*/
public static boolean isExternalStorageLegacy(@NonNull File path) {
final Context context = AppGlobals.getInitialApplication();
@@ -1232,24 +1245,23 @@
return false;
}
- if (packageManager.checkPermission(Manifest.permission.WRITE_MEDIA_STORAGE,
- context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+ // TODO(b/150672994): Compat flags do not override instant app and isolated process's
+ // behavior.
+ boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE);
+ boolean forceEnableScopedStorage = Compatibility.isChangeEnabled(
+ FORCE_ENABLE_SCOPED_STORAGE);
+ // if Scoped Storage is strictly enforced, the app does *not* have legacy storage access
+ // Note: does not require packagename/uid as this is directly called from an app process
+ if (isScopedStorageEnforced(defaultScopedStorage, forceEnableScopedStorage)) {
+ return false;
+ }
+ // if Scoped Storage is strictly disabled, the app has legacy storage access
+ // Note: does not require packagename/uid as this is directly called from an app process
+ if (isScopedStorageDisabled(defaultScopedStorage, forceEnableScopedStorage)) {
return true;
}
- if (packageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
- context.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- final String[] packagesForUid = packageManager.getPackagesForUid(uid);
- for (String packageName : packagesForUid) {
- if (appOps.checkOpNoThrow(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
- uid, packageName) == AppOpsManager.MODE_ALLOWED) {
- return true;
- }
- }
-
return appOps.checkOpNoThrow(AppOpsManager.OP_LEGACY_STORAGE,
uid, context.getOpPackageName()) == AppOpsManager.MODE_ALLOWED;
}
@@ -1298,6 +1310,16 @@
}
}
+ private static boolean isScopedStorageEnforced(boolean defaultScopedStorage,
+ boolean forceEnableScopedStorage) {
+ return defaultScopedStorage && forceEnableScopedStorage;
+ }
+
+ private static boolean isScopedStorageDisabled(boolean defaultScopedStorage,
+ boolean forceEnableScopedStorage) {
+ return !defaultScopedStorage && !forceEnableScopedStorage;
+ }
+
static File getDirectory(String variableName, String defaultPath) {
String path = System.getenv(variableName);
return path == null ? new File(defaultPath) : new File(path);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index d7af1b9..a557bd9 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -607,6 +607,7 @@
* started.
* @param pkgDataInfoMap Map from related package names to private data directory
* volume UUID and inode number.
+ * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -630,12 +631,13 @@
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
+ boolean bindMountAppStorageDirs,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
zygotePolicyFlags, isTopApp, disabledCompatChanges,
- pkgDataInfoMap, zygoteArgs);
+ pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
}
/** @hide */
@@ -659,7 +661,7 @@
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
- disabledCompatChanges, /* pkgDataInfoMap */ null, zygoteArgs);
+ disabledCompatChanges, /* pkgDataInfoMap */ null, false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 2d218f4..1992f1d 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -963,7 +963,7 @@
PRIMITIVE_QUICK_RISE,
PRIMITIVE_SLOW_RISE,
PRIMITIVE_QUICK_FALL,
- PRIMITIVE_LIGHT_TICK,
+ PRIMITIVE_TICK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Primitive {}
@@ -981,10 +981,14 @@
* A haptic effect that simulates downwards movement with gravity. Often
* followed by extra energy of hitting and reverberation to augment
* physicality.
+ *
+ * @hide Not confident enough to expose publicly yet
*/
public static final int PRIMITIVE_THUD = 2;
/**
* A haptic effect that simulates spinning momentum.
+ *
+ * @hide Not confident enough to expose publicly yet
*/
public static final int PRIMITIVE_SPIN = 3;
/**
@@ -1003,7 +1007,8 @@
* This very short effect should produce a light crisp sensation intended
* to be used repetitively for dynamic feedback.
*/
- public static final int PRIMITIVE_LIGHT_TICK = 7;
+ // Internally this maps to the HAL constant CompositePrimitive::LIGHT_TICK
+ public static final int PRIMITIVE_TICK = 7;
private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>();
@@ -1081,7 +1086,7 @@
*
*/
static int checkPrimitive(int primitiveId) {
- Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LIGHT_TICK,
+ Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_TICK,
"primitiveId");
return primitiveId;
}
@@ -1108,8 +1113,8 @@
return "PRIMITIVE_SLOW_RISE";
case PRIMITIVE_QUICK_FALL:
return "PRIMITIVE_QUICK_FALL";
- case PRIMITIVE_LIGHT_TICK:
- return "PRIMITIVE_LIGHT_TICK";
+ case PRIMITIVE_TICK:
+ return "PRIMITIVE_TICK";
default:
return Integer.toString(id);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 34cec06..5f3f14f 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -333,6 +333,7 @@
* started.
* @param pkgDataInfoMap Map from related package names to private data directory
* volume UUID and inode number.
+ * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
*
* @param zygoteArgs Additional arguments to supply to the Zygote process.
* @return An object that describes the result of the attempt to start the process.
@@ -354,6 +355,7 @@
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
+ boolean bindMountAppStorageDirs,
@Nullable String[] zygoteArgs) {
// TODO (chriswailes): Is there a better place to check this value?
if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -365,7 +367,7 @@
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
- pkgDataInfoMap, zygoteArgs);
+ pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -606,6 +608,7 @@
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
* @param pkgDataInfoMap Map from related package names to private data directory volume UUID
* and inode number.
+ * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
@@ -628,6 +631,7 @@
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
pkgDataInfoMap,
+ boolean bindMountAppStorageDirs,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<>();
@@ -725,6 +729,10 @@
argsForZygote.add(sb.toString());
}
+ if (bindMountAppStorageDirs) {
+ argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS);
+ }
+
if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
StringBuilder sb = new StringBuilder();
sb.append("--disabled-compat-changes=");
@@ -1282,7 +1290,9 @@
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
- null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
+ null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
+ /* bindMountAppStorageDirs */ false, extraArgs);
+
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index a2def7f..f43a252 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -100,10 +100,11 @@
}
/**
- * Check if fuse is running in target user, if it's running then setup its obb directories.
- * TODO: System server should store a list of active pids that obb is not mounted and use it.
+ * Create storage directories if it does not exist.
+ * Return true if the directories were setup correctly, otherwise false.
*/
- public abstract void prepareObbDirs(int userId, Set<String> packageList, String processName);
+ public abstract boolean prepareStorageDirs(int userId, Set<String> packageList,
+ String processName);
/**
* Add a listener to listen to reset event in StorageManagerService.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ce9b449..d8679b20 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8497,7 +8497,6 @@
*
* @hide
*/
- @SystemApi
public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
/**
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0cd96b8..c52b02b 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -55,7 +55,6 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Slog;
import android.widget.RemoteViews;
import com.android.internal.annotations.GuardedBy;
@@ -1570,6 +1569,7 @@
private boolean mVisuallyInterruptive;
private boolean mIsConversation;
private ShortcutInfo mShortcutInfo;
+ private boolean mIsBubble;
private static final int PARCEL_VERSION = 2;
@@ -1604,6 +1604,7 @@
out.writeBoolean(mVisuallyInterruptive);
out.writeBoolean(mIsConversation);
out.writeParcelable(mShortcutInfo, flags);
+ out.writeBoolean(mIsBubble);
}
/** @hide */
@@ -1639,6 +1640,7 @@
mVisuallyInterruptive = in.readBoolean();
mIsConversation = in.readBoolean();
mShortcutInfo = in.readParcelable(cl);
+ mIsBubble = in.readBoolean();
}
@@ -1844,6 +1846,14 @@
}
/**
+ * Returns whether this notification is actively a bubble.
+ * @hide
+ */
+ public boolean isBubble() {
+ return mIsBubble;
+ }
+
+ /**
* @hide
*/
public @Nullable ShortcutInfo getShortcutInfo() {
@@ -1862,7 +1872,8 @@
int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies, boolean canBubble,
- boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo) {
+ boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo,
+ boolean isBubble) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1886,6 +1897,7 @@
mVisuallyInterruptive = visuallyInterruptive;
mIsConversation = isConversation;
mShortcutInfo = shortcutInfo;
+ mIsBubble = isBubble;
}
/**
@@ -1913,7 +1925,8 @@
other.mCanBubble,
other.mVisuallyInterruptive,
other.mIsConversation,
- other.mShortcutInfo);
+ other.mShortcutInfo,
+ other.mIsBubble);
}
/**
@@ -1970,7 +1983,8 @@
&& Objects.equals(mIsConversation, other.mIsConversation)
// Shortcutinfo doesn't have equals either; use id
&& Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()),
- (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()));
+ (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId()))
+ && Objects.equals(mIsBubble, other.mIsBubble);
}
}
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
index d2494a5..ae67068 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
@@ -24,8 +24,6 @@
import android.text.TextUtils;
import android.util.Log;
-import java.util.List;
-
/**
* Handles response from the {@link QuickAccessWalletService} for {@link GetWalletCardsRequest}
*
@@ -56,7 +54,6 @@
* presented as the selected card.
*/
public void onSuccess(@NonNull GetWalletCardsResponse response) {
- Log.i(TAG, "onSuccess");
if (isValidResponse(response)) {
mHandler.post(() -> onSuccessInternal(response));
} else {
@@ -78,7 +75,6 @@
}
private void onSuccessInternal(GetWalletCardsResponse response) {
- Log.i(TAG, "onSuccessInternal");
if (mCalled) {
Log.w(TAG, "already called");
return;
@@ -86,7 +82,6 @@
mCalled = true;
try {
mCallback.onGetWalletCardsSuccess(response);
- Log.i(TAG, "onSuccessInternal: returned response");
} catch (RemoteException e) {
Log.w(TAG, "Error returning wallet cards", e);
}
@@ -106,29 +101,53 @@
}
private boolean isValidResponse(@NonNull GetWalletCardsResponse response) {
- return response != null
- && response.getWalletCards() != null
- && response.getSelectedIndex() >= 0
- && (response.getWalletCards().isEmpty() // selectedIndex may be 0 when list is empty
- || response.getSelectedIndex() < response.getWalletCards().size())
- && response.getWalletCards().size() < mRequest.getMaxCards()
- && areValidCards(response.getWalletCards());
- }
-
- private boolean areValidCards(List<WalletCard> walletCards) {
- for (WalletCard walletCard : walletCards) {
- if (walletCard == null
- || walletCard.getCardId() == null
- || walletCard.getCardImage() == null
- || TextUtils.isEmpty(walletCard.getContentDescription())
- || walletCard.getPendingIntent() == null) {
+ if (response == null) {
+ Log.w(TAG, "Invalid response: response is null");
+ return false;
+ }
+ if (response.getWalletCards() == null) {
+ Log.w(TAG, "Invalid response: walletCards is null");
+ return false;
+ }
+ if (response.getSelectedIndex() < 0) {
+ Log.w(TAG, "Invalid response: selectedIndex is negative");
+ return false;
+ }
+ if (!response.getWalletCards().isEmpty()
+ && response.getSelectedIndex() >= response.getWalletCards().size()) {
+ Log.w(TAG, "Invalid response: selectedIndex out of bounds");
+ return false;
+ }
+ if (response.getWalletCards().size() > mRequest.getMaxCards()) {
+ Log.w(TAG, "Invalid response: too many cards");
+ return false;
+ }
+ for (WalletCard walletCard : response.getWalletCards()) {
+ if (walletCard == null) {
+ Log.w(TAG, "Invalid response: card is null");
+ return false;
+ }
+ if (walletCard.getCardId() == null) {
+ Log.w(TAG, "Invalid response: cardId is null");
return false;
}
Icon cardImage = walletCard.getCardImage();
+ if (cardImage == null) {
+ Log.w(TAG, "Invalid response: cardImage is null");
+ return false;
+ }
if (cardImage.getType() == Icon.TYPE_BITMAP
- && walletCard.getCardImage().getBitmap().getConfig()
- != Bitmap.Config.HARDWARE) {
- Log.w(TAG, "WalletCard bitmaps should be hardware bitmaps");
+ && cardImage.getBitmap().getConfig() != Bitmap.Config.HARDWARE) {
+ Log.w(TAG, "Invalid response: cardImage bitmaps must be hardware bitmaps");
+ return false;
+ }
+ if (TextUtils.isEmpty(walletCard.getContentDescription())) {
+ Log.w(TAG, "Invalid response: contentDescription is null");
+ return false;
+ }
+ if (walletCard.getPendingIntent() == null) {
+ Log.w(TAG, "Invalid response: pendingIntent is null");
+ return false;
}
}
return true;
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
index be9ab11..4f5b139 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java
@@ -16,19 +16,23 @@
package android.service.quickaccesswallet;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
+import java.io.Closeable;
+import java.util.concurrent.Executor;
+
/**
* Facilitates accessing cards from the {@link QuickAccessWalletService}.
*
* @hide
*/
@TestApi
-public interface QuickAccessWalletClient {
+public interface QuickAccessWalletClient extends Closeable {
/**
* Create a client for accessing wallet cards from the {@link QuickAccessWalletService}. If the
@@ -92,6 +96,14 @@
@NonNull OnWalletCardsRetrievedCallback callback);
/**
+ * Get wallet cards from the {@link QuickAccessWalletService}.
+ */
+ void getWalletCards(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GetWalletCardsRequest request,
+ @NonNull OnWalletCardsRetrievedCallback callback);
+
+ /**
* Callback for getWalletCards
*/
interface OnWalletCardsRetrievedCallback {
@@ -111,12 +123,19 @@
void notifyWalletDismissed();
/**
- * Unregister event listener.
+ * Register an event listener.
*/
void addWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
/**
- * Unregister event listener
+ * Register an event listener.
+ */
+ void addWalletServiceEventListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WalletServiceEventListener listener);
+
+ /**
+ * Unregister an event listener
*/
void removeWalletServiceEventListener(@NonNull WalletServiceEventListener listener);
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 37a8703..e4dd082 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -18,6 +18,7 @@
import static android.service.quickaccesswallet.QuickAccessWalletService.SERVICE_INTERFACE;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -36,16 +37,19 @@
import com.android.internal.widget.LockPatternUtils;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
+import java.util.concurrent.Executor;
/**
* Implements {@link QuickAccessWalletClient}. The client connects, performs requests, waits for
- * responses, and disconnects automatically after a short period of time. The client may
+ * responses, and disconnects automatically one minute after the last call is performed.
+ *
* @hide
*/
public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, ServiceConnection {
@@ -78,15 +82,14 @@
@Override
public boolean isWalletServiceAvailable() {
- boolean available = mServiceInfo != null;
- Log.i(TAG, "isWalletServiceAvailable: " + available);
- return available;
+ return mServiceInfo != null;
}
@Override
public boolean isWalletFeatureAvailable() {
int currentUser = ActivityManager.getCurrentUser();
- return checkUserSetupComplete()
+ return currentUser == UserHandle.USER_SYSTEM
+ && checkUserSetupComplete()
&& checkSecureSetting(Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED)
&& !new LockPatternUtils(mContext).isUserInLockdown(currentUser);
}
@@ -101,23 +104,29 @@
public void getWalletCards(
@NonNull GetWalletCardsRequest request,
@NonNull OnWalletCardsRetrievedCallback callback) {
+ getWalletCards(mContext.getMainExecutor(), request, callback);
+ }
- Log.i(TAG, "getWalletCards");
-
+ @Override
+ public void getWalletCards(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull GetWalletCardsRequest request,
+ @NonNull OnWalletCardsRetrievedCallback callback) {
if (!isWalletServiceAvailable()) {
- callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null));
+ executor.execute(
+ () -> callback.onWalletCardRetrievalError(new GetWalletCardsError(null, null)));
return;
}
BaseCallbacks serviceCallback = new BaseCallbacks() {
@Override
public void onGetWalletCardsSuccess(GetWalletCardsResponse response) {
- mHandler.post(() -> callback.onWalletCardsRetrieved(response));
+ executor.execute(() -> callback.onWalletCardsRetrieved(response));
}
@Override
public void onGetWalletCardsFailure(GetWalletCardsError error) {
- mHandler.post(() -> callback.onWalletCardRetrievalError(error));
+ executor.execute(() -> callback.onWalletCardRetrievalError(error));
}
};
@@ -132,11 +141,11 @@
serviceCallback.onGetWalletCardsFailure(new GetWalletCardsError(null, null));
}
});
+
}
@Override
public void selectWalletCard(@NonNull SelectWalletCardRequest request) {
- Log.i(TAG, "selectWalletCard");
if (!isWalletServiceAvailable()) {
return;
}
@@ -153,7 +162,6 @@
if (!isWalletServiceAvailable()) {
return;
}
- Log.i(TAG, "notifyWalletDismissed");
executeApiCall(new ApiCaller("onWalletDismissed") {
@Override
public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
@@ -164,15 +172,20 @@
@Override
public void addWalletServiceEventListener(WalletServiceEventListener listener) {
+ addWalletServiceEventListener(mContext.getMainExecutor(), listener);
+ }
+
+ @Override
+ public void addWalletServiceEventListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WalletServiceEventListener listener) {
if (!isWalletServiceAvailable()) {
return;
}
- Log.i(TAG, "registerWalletServiceEventListener");
BaseCallbacks callback = new BaseCallbacks() {
@Override
public void onWalletServiceEvent(WalletServiceEvent event) {
- Log.i(TAG, "onWalletServiceEvent");
- mHandler.post(() -> listener.onWalletServiceEvent(event));
+ executor.execute(() -> listener.onWalletServiceEvent(event));
}
};
@@ -193,7 +206,6 @@
if (!isWalletServiceAvailable()) {
return;
}
- Log.i(TAG, "unregisterWalletServiceEventListener");
executeApiCall(new ApiCaller("unregisterListener") {
@Override
public void performApiCall(IQuickAccessWalletService service) throws RemoteException {
@@ -209,8 +221,12 @@
}
@Override
+ public void close() throws IOException {
+ disconnect();
+ }
+
+ @Override
public void disconnect() {
- Log.i(TAG, "disconnect");
mHandler.post(() -> disconnectInternal(true));
}
@@ -241,18 +257,15 @@
}
private void connect() {
- Log.i(TAG, "connect");
mHandler.post(this::connectInternal);
}
private void connectInternal() {
- Log.i(TAG, "connectInternal");
if (mServiceInfo == null) {
Log.w(TAG, "Wallet service unavailable");
return;
}
if (mIsConnected) {
- Log.w(TAG, "already connected");
return;
}
mIsConnected = true;
@@ -264,23 +277,14 @@
}
private void onConnectedInternal(IQuickAccessWalletService service) {
- Log.i(TAG, "onConnectedInternal");
if (!mIsConnected) {
Log.w(TAG, "onConnectInternal but connection closed");
mService = null;
return;
}
mService = service;
- Log.i(TAG, "onConnectedInternal success: request queue size " + mRequestQueue.size());
for (ApiCaller apiCaller : new ArrayList<>(mRequestQueue)) {
- try {
- apiCaller.performApiCall(mService);
- } catch (RemoteException e) {
- Log.e(TAG, "onConnectedInternal error", e);
- apiCaller.onApiError();
- disconnect();
- break;
- }
+ performApiCallInternal(apiCaller, mService);
mRequestQueue.remove(apiCaller);
}
}
@@ -290,7 +294,6 @@
* posting a new delayed message.
*/
private void resetServiceConnectionTimeout() {
- Log.i(TAG, "resetServiceConnectionTimeout");
mHandler.removeMessages(MSG_TIMEOUT_SERVICE);
mHandler.postDelayed(
() -> disconnectInternal(true),
@@ -299,13 +302,11 @@
}
private void disconnectInternal(boolean clearEventListeners) {
- Log.i(TAG, "disconnectInternal: " + clearEventListeners);
if (!mIsConnected) {
Log.w(TAG, "already disconnected");
return;
}
if (clearEventListeners && !mEventListeners.isEmpty()) {
- Log.i(TAG, "disconnectInternal: clear event listeners");
for (WalletServiceEventListener listener : mEventListeners.keySet()) {
removeWalletServiceEventListener(listener);
}
@@ -320,29 +321,33 @@
}
private void executeApiCall(ApiCaller apiCaller) {
- Log.i(TAG, "execute: " + apiCaller.mDesc);
mHandler.post(() -> executeInternal(apiCaller));
}
- private void executeInternal(ApiCaller apiCall) {
- Log.i(TAG, "executeInternal: " + apiCall.mDesc);
+ private void executeInternal(ApiCaller apiCaller) {
if (mIsConnected && mService != null) {
- try {
- apiCall.performApiCall(mService);
- Log.i(TAG, "executeInternal success: " + apiCall.mDesc);
- resetServiceConnectionTimeout();
- } catch (RemoteException e) {
- Log.w(TAG, "executeInternal error: " + apiCall.mDesc, e);
- apiCall.onApiError();
- disconnect();
- }
+ performApiCallInternal(apiCaller, mService);
} else {
- Log.i(TAG, "executeInternal: queued" + apiCall.mDesc);
- mRequestQueue.add(apiCall);
+ mRequestQueue.add(apiCaller);
connect();
}
}
+ private void performApiCallInternal(ApiCaller apiCaller, IQuickAccessWalletService service) {
+ if (service == null) {
+ apiCaller.onApiError();
+ return;
+ }
+ try {
+ apiCaller.performApiCall(service);
+ resetServiceConnectionTimeout();
+ } catch (RemoteException e) {
+ Log.w(TAG, "executeInternal error: " + apiCaller.mDesc, e);
+ apiCaller.onApiError();
+ disconnect();
+ }
+ }
+
private abstract static class ApiCaller {
private final String mDesc;
@@ -350,7 +355,8 @@
this.mDesc = desc;
}
- abstract void performApiCall(IQuickAccessWalletService service) throws RemoteException;
+ abstract void performApiCall(IQuickAccessWalletService service)
+ throws RemoteException;
void onApiError() {
Log.w(TAG, "api error: " + mDesc);
@@ -359,7 +365,6 @@
@Override // ServiceConnection
public void onServiceConnected(ComponentName name, IBinder binder) {
- Log.i(TAG, "onServiceConnected: " + name);
IQuickAccessWalletService service = IQuickAccessWalletService.Stub.asInterface(binder);
mHandler.post(() -> onConnectedInternal(service));
}
@@ -367,19 +372,16 @@
@Override // ServiceConnection
public void onServiceDisconnected(ComponentName name) {
// Do not disconnect, as we may later be re-connected
- Log.w(TAG, "onServiceDisconnected");
}
@Override // ServiceConnection
public void onBindingDied(ComponentName name) {
// This is a recoverable error but the client will need to reconnect.
- Log.w(TAG, "onBindingDied");
disconnect();
}
@Override // ServiceConnection
public void onNullBinding(ComponentName name) {
- Log.w(TAG, "onNullBinding");
disconnect();
}
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
index 23173a8..31e51bb 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -66,13 +66,11 @@
static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
if (defaultPaymentApp == null) {
- Log.d(TAG, "create: default payment app not set");
return null;
}
ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
if (serviceInfo == null) {
- Log.d(TAG, "create: unable to resolve service intent");
return null;
}
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index e6ae0ab..b09d2e9 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -180,7 +180,7 @@
* GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
* GetWalletCardsRequest#getCardHeightPx()}. If the card image
* does not have these dimensions, it may appear distorted when it
- * is scaled to fit these dimensions on screen. Bitmaps should be
+ * is scaled to fit these dimensions on screen. Bitmaps must be
* of type {@link android.graphics.Bitmap.Config#HARDWARE} for
* performance reasons.
* @param contentDescription The content description of the card image. This field is
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6b87a10..cfbe393 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,7 +71,7 @@
// introduced in R and will be removed in the next release (b/148367230).
DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "false");
- DEFAULT_FLAGS.put("settings_tether_all_in_one", "true");
+ DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
DEFAULT_FLAGS.put(SETTINGS_SCHEDULES_FLAG, "false");
DEFAULT_FLAGS.put("settings_contextual_home2", "false");
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 88e7f2e..123b9db 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -53,7 +53,6 @@
import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import java.io.PrintWriter;
@@ -518,14 +517,13 @@
PendingControlRequest pendingRequest = mPendingImeControlRequest;
mPendingImeControlRequest = null;
mHandler.removeCallbacks(mPendingControlTimeout);
- CancellationSignal cancellationSignal = controlAnimationUnchecked(
- pendingRequest.types,
+ controlAnimationUnchecked(
+ pendingRequest.types, pendingRequest.cancellationSignal,
pendingRequest.listener, mFrame,
true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator,
false /* fade */, pendingRequest.animationType,
pendingRequest.layoutInsetsDuringAnimation,
pendingRequest.useInsetsAnimationThread);
- pendingRequest.cancellationSignal.setOnCancelListener(cancellationSignal::cancel);
return;
}
@@ -571,24 +569,26 @@
}
@Override
- public CancellationSignal controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
+ public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@Nullable Interpolator interpolator,
+ @Nullable CancellationSignal cancellationSignal,
@NonNull WindowInsetsAnimationControlListener listener) {
- return controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs,
- interpolator, ANIMATION_TYPE_USER);
+ controlWindowInsetsAnimation(types, cancellationSignal, listener,
+ false /* fromIme */, durationMillis, interpolator, ANIMATION_TYPE_USER);
}
- private CancellationSignal controlWindowInsetsAnimation(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs,
- @Nullable Interpolator interpolator, @AnimationType int animationType) {
+ private void controlWindowInsetsAnimation(@InsetsType int types,
+ @Nullable CancellationSignal cancellationSignal,
+ WindowInsetsAnimationControlListener listener,
+ boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
+ @AnimationType int animationType) {
if (!checkDisplayFramesForControlling()) {
listener.onCancelled();
- CancellationSignal cancellationSignal = new CancellationSignal();
- cancellationSignal.cancel();
- return cancellationSignal;
+ return;
}
- return controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, interpolator,
- false /* fade */, animationType, getLayoutInsetsDuringAnimationMode(types),
+ controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
+ interpolator, false /* fade */, animationType,
+ getLayoutInsetsDuringAnimationMode(types),
false /* useInsetsAnimationThread */);
}
@@ -599,18 +599,17 @@
return mState.getDisplayFrame().equals(mFrame);
}
- private CancellationSignal controlAnimationUnchecked(@InsetsType int types,
+ private void controlAnimationUnchecked(@InsetsType int types,
+ @Nullable CancellationSignal cancellationSignal,
WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
long durationMs, Interpolator interpolator, boolean fade,
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread) {
- CancellationSignal cancellationSignal = new CancellationSignal();
if (types == 0) {
// nothing to animate.
listener.onCancelled();
- cancellationSignal.cancel();
- return cancellationSignal;
+ return;
}
cancelExistingControllers(types);
mLastStartedAnimTypes |= types;
@@ -631,18 +630,19 @@
useInsetsAnimationThread);
mPendingImeControlRequest = request;
mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS);
- cancellationSignal.setOnCancelListener(() -> {
- if (mPendingImeControlRequest == request) {
- abortPendingImeControlRequest();
- }
- });
- return cancellationSignal;
+ if (cancellationSignal != null) {
+ cancellationSignal.setOnCancelListener(() -> {
+ if (mPendingImeControlRequest == request) {
+ abortPendingImeControlRequest();
+ }
+ });
+ }
+ return;
}
if (typesReady == 0) {
listener.onCancelled();
- cancellationSignal.cancel();
- return cancellationSignal;
+ return;
}
@@ -654,13 +654,14 @@
frame, mState, listener, typesReady, this, durationMs, interpolator, fade,
animationType);
mRunningAnimations.add(new RunningAnimation(runner, animationType));
- cancellationSignal.setOnCancelListener(runner::cancel);
+ if (cancellationSignal != null) {
+ cancellationSignal.setOnCancelListener(runner::cancel);
+ }
if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
showDirectly(types);
} else {
hideDirectly(types, false /* animationFinished */, animationType);
}
- return cancellationSignal;
}
/**
@@ -922,7 +923,8 @@
// Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct.
controlAnimationUnchecked(
- types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
+ types, null /* cancellationSignal */, listener, mState.getDisplayFrame(), fromIme,
+ listener.getDurationMs(),
INTERPOLATOR, true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index 7f36418..e8d9bb5 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.CancellationSignal;
import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
@@ -60,21 +62,6 @@
}
@Override
- public CancellationSignal controlWindowInsetsAnimation(int types, long durationMillis,
- Interpolator interpolator,
- WindowInsetsAnimationControlListener listener) {
- if (mReplayedInsetsController != null) {
- return mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis,
- interpolator, listener);
- } else {
- listener.onCancelled();
- CancellationSignal cancellationSignal = new CancellationSignal();
- cancellationSignal.cancel();
- return cancellationSignal;
- }
- }
-
- @Override
public void setSystemBarsAppearance(int appearance, int mask) {
if (mReplayedInsetsController != null) {
mReplayedInsetsController.setSystemBarsAppearance(appearance, mask);
@@ -176,6 +163,19 @@
mReplayedInsetsController = null;
}
+ @Override
+ public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+ @Nullable Interpolator interpolator,
+ CancellationSignal cancellationSignal,
+ @NonNull WindowInsetsAnimationControlListener listener) {
+ if (mReplayedInsetsController != null) {
+ mReplayedInsetsController.controlWindowInsetsAnimation(types, durationMillis,
+ interpolator, cancellationSignal, listener);
+ } else {
+ listener.onCancelled();
+ }
+ }
+
private interface PendingRequest {
void replay(InsetsController controller);
}
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 701bd31..faaf920 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,7 +16,6 @@
package android.view;
-import android.annotation.Hide;
import android.annotation.NonNull;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.EditorInfo;
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 2ad557e..0282eca 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -156,16 +156,17 @@
* calculate {@link WindowInsetsAnimation#getInterpolatedFraction()}.
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
- * @return A cancellation signal that the caller can use to cancel the request to obtain
- * control, or once they have control, to cancel the control.
+ * @param cancellationSignal A cancellation signal that the caller can use to cancel the
+ * request to obtain control, or once they have control, to cancel the
+ * control.
* @see WindowInsetsAnimation#getFraction()
* @see WindowInsetsAnimation#getInterpolatedFraction()
* @see WindowInsetsAnimation#getInterpolator()
* @see WindowInsetsAnimation#getDurationMillis()
*/
- @NonNull
- CancellationSignal controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
+ void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@Nullable Interpolator interpolator,
+ @Nullable CancellationSignal cancellationSignal,
@NonNull WindowInsetsAnimationControlListener listener);
/**
diff --git a/core/java/android/view/inline/InlinePresentationSpec.java b/core/java/android/view/inline/InlinePresentationSpec.java
index 3cc04b8..3788e2b 100644
--- a/core/java/android/view/inline/InlinePresentationSpec.java
+++ b/core/java/android/view/inline/InlinePresentationSpec.java
@@ -58,7 +58,7 @@
- // Code below generated by codegen v1.0.14.
+ // Code below generated by codegen v1.0.15.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -104,8 +104,8 @@
}
/**
- * The extras encoding the UI style information. Defaults to null in which case the default
- * system UI style will be used.
+ * The extras encoding the UI style information. Defaults to {@code null} in which case the
+ * default system UI style will be used.
*/
@DataClass.Generated.Member
public @Nullable Bundle getStyle() {
@@ -244,11 +244,11 @@
}
/**
- * The extras encoding the UI style information. Defaults to null in which case the default
- * system UI style will be used.
+ * The extras encoding the UI style information. Defaults to {@code null} in which case the
+ * default system UI style will be used.
*/
@DataClass.Generated.Member
- public @NonNull Builder setStyle(@Nullable Bundle value) {
+ public @NonNull Builder setStyle(@NonNull Bundle value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
mStyle = value;
@@ -279,8 +279,8 @@
}
@DataClass.Generated(
- time = 1582078731418L,
- codegenVersion = "1.0.14",
+ time = 1584067238741L,
+ codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java",
inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable android.os.Bundle mStyle\nprivate static android.os.Bundle defaultStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 8e8c7df..2945a86 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -72,7 +72,6 @@
/**
* The extras state propagated from the IME to pass extra data.
*/
- @DataClass.MaySetToNull
private @Nullable Bundle mExtras;
/**
@@ -81,7 +80,6 @@
*
* @hide
*/
- @DataClass.MaySetToNull
private @Nullable IBinder mHostInputToken;
/**
@@ -498,7 +496,7 @@
* The extras state propagated from the IME to pass extra data.
*/
@DataClass.Generated.Member
- public @NonNull Builder setExtras(@Nullable Bundle value) {
+ public @NonNull Builder setExtras(@NonNull Bundle value) {
checkNotUsed();
mBuilderFieldsSet |= 0x10;
mExtras = value;
@@ -513,7 +511,7 @@
*/
@DataClass.Generated.Member
@Override
- @NonNull Builder setHostInputToken(@Nullable IBinder value) {
+ @NonNull Builder setHostInputToken(@NonNull IBinder value) {
checkNotUsed();
mBuilderFieldsSet |= 0x20;
mHostInputToken = value;
@@ -578,10 +576,10 @@
}
@DataClass.Generated(
- time = 1583975428858L,
+ time = 1584067152935L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @com.android.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.os.Bundle mExtras\nprivate @com.android.internal.util.DataClass.MaySetToNull @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic void setHostInputToken(android.os.IBinder)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.Nullable android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.Nullable android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\npublic void setHostInputToken(android.os.IBinder)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.Nullable android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 816612f..16d9ed3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4589,7 +4589,7 @@
private float mTouchOffsetY;
// Where the touch position should be on the handle to ensure a maximum cursor visibility.
// This is the distance in pixels from the top of the handle view.
- private float mIdealVerticalOffset;
+ private final float mIdealVerticalOffset;
// Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
// Parent's (TextView) previous position on screen
@@ -4638,8 +4638,18 @@
final int handleHeight = getPreferredHeight();
mTouchOffsetY = -0.3f * handleHeight;
- mIdealVerticalOffset = 0.7f * handleHeight;
- mIdealFingerToCursorOffset = (int)(mIdealVerticalOffset - mTouchOffsetY);
+ final int distance = AppGlobals.getIntCoreSetting(
+ WidgetFlags.KEY_FINGER_TO_CURSOR_DISTANCE,
+ WidgetFlags.FINGER_TO_CURSOR_DISTANCE_DEFAULT);
+ if (distance < 0 || distance > 100) {
+ mIdealVerticalOffset = 0.7f * handleHeight;
+ mIdealFingerToCursorOffset = (int)(mIdealVerticalOffset - mTouchOffsetY);
+ } else {
+ mIdealFingerToCursorOffset = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, distance,
+ mTextView.getContext().getResources().getDisplayMetrics());
+ mIdealVerticalOffset = mIdealFingerToCursorOffset + mTouchOffsetY;
+ }
}
public float getIdealVerticalOffset() {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 01a0e9b..7f6c0d2 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2818,7 +2818,7 @@
/**
* When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
- * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
+ * costly to set PendingIntents on the individual items, and is hence not recommended. Instead
* this method should be used to set a single PendingIntent template on the collection, and
* individual items can differentiate their on-click behavior using
* {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
@@ -2834,7 +2834,7 @@
/**
* When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
- * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
+ * costly to set PendingIntents on the individual items, and is hence not recommended. Instead
* a single PendingIntent template can be set on the collection, see {@link
* RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
* action of a given item can be distinguished by setting a fillInIntent on that item. The
@@ -3960,7 +3960,7 @@
/**
* When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is
- * very costly to set PendingIntents on the individual items, and is hence not permitted.
+ * very costly to set PendingIntents on the individual items, and is hence not recommended.
* Instead a single PendingIntent template can be set on the collection, see {@link
* RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
* action of a given item can be distinguished by setting a fillInIntent on that item. The
diff --git a/core/java/android/widget/WidgetFlags.java b/core/java/android/widget/WidgetFlags.java
index bce5497..09ab5aa 100644
--- a/core/java/android/widget/WidgetFlags.java
+++ b/core/java/android/widget/WidgetFlags.java
@@ -41,6 +41,25 @@
public static final boolean ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT = true;
/**
+ * The flag of finger-to-cursor distance in DP for cursor dragging.
+ * The value unit is DP and the range is {0..100}. If the value is out of range, the legacy
+ * value, which is based on handle size, will be used.
+ */
+ public static final String FINGER_TO_CURSOR_DISTANCE =
+ "CursorControlFeature__finger_to_cursor_distance";
+
+ /**
+ * The key used in app core settings for the flag {@link #FINGER_TO_CURSOR_DISTANCE}.
+ */
+ public static final String KEY_FINGER_TO_CURSOR_DISTANCE =
+ "widget__finger_to_cursor_distance";
+
+ /**
+ * Default value for the flag {@link #FINGER_TO_CURSOR_DISTANCE}.
+ */
+ public static final int FINGER_TO_CURSOR_DISTANCE_DEFAULT = -1;
+
+ /**
* Whether additional gestures should be enabled for the insertion cursor handle (e.g.
* long-press or double-tap on the handle to trigger selection).
*/
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 633aa2c..ff03f1a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -205,6 +205,9 @@
/** List of packages with the same uid, and its app data info: volume uuid and inode. */
public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+ /** Bind mount app storage dirs to lower fs not via fuse */
+ public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
+
/**
* An extraArg passed when a zygote process is forking a child-zygote, specifying a name
* in the abstract socket namespace. This socket name is what the new child zygote
@@ -310,6 +313,7 @@
* @param isTopApp true if the process is for top (high priority) application.
* @param pkgDataInfoList A list that stores related packages and its app data
* info: volume uuid and inode.
+ * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
*
* @return 0 if this is the child, pid of the child
* if this is the parent, or -1 on error.
@@ -317,13 +321,13 @@
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- boolean isTopApp, String[] pkgDataInfoList) {
+ boolean isTopApp, String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
- pkgDataInfoList);
+ pkgDataInfoList, bindMountAppStorageDirs);
if (pid == 0) {
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -339,7 +343,8 @@
private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
- String appDataDir, boolean isTopApp, String[] pkgDataInfoList);
+ String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
+ boolean bindMountAppStorageDirs);
/**
* Specialize an unspecialized app process. The current VM must have been started
@@ -366,14 +371,15 @@
* volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
* app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
* app_b_ce_inode, ...];
+ * @param bindMountAppStorageDirs True if the zygote needs to mount storage dirs.
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
- String[] pkgDataInfoList) {
+ String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
- pkgDataInfoList);
+ pkgDataInfoList, bindMountAppStorageDirs);
// Note that this event ends at the end of handleChildProc.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -393,7 +399,7 @@
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
- String[] pkgDataInfoList);
+ String[] pkgDataInfoList, boolean bindMountAppStorageDirs);
/**
* Called to do any initialization before starting an application.
@@ -718,7 +724,7 @@
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
- args.mPkgDataInfoList);
+ args.mPkgDataInfoList, args.mBindMountAppStorageDirs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 37f570b..1a63765 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -227,6 +227,11 @@
String[] mPkgDataInfoList;
/**
+ * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
+ */
+ boolean mBindMountAppStorageDirs;
+
+ /**
* Constructs instance and parses args
*
* @param args zygote command-line args
@@ -447,6 +452,8 @@
}
} else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
mPkgDataInfoList = getAssignmentList(arg);
+ } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
+ mBindMountAppStorageDirs = true;
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4949811..bc8dfd4 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,7 @@
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
- parsedArgs.mPkgDataInfoList);
+ parsedArgs.mPkgDataInfoList, parsedArgs.mBindMountAppStorageDirs);
try {
if (pid == 0) {
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index 8446bbd..e4a4408 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -200,8 +200,9 @@
try {
return doInvoke();
} finally {
- if (isRecycleOnUse()) doRecycle();
- if (!isRecycled()) {
+ if (isRecycleOnUse()) {
+ doRecycle();
+ } else if (!isRecycled()) {
int argsSize = ArrayUtils.size(mArgs);
for (int i = 0; i < argsSize; i++) {
popArg(i);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 07be113..73da600 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -122,6 +122,11 @@
private CharSequence mFallbackChatName;
private CharSequence mFallbackGroupChatName;
private CharSequence mConversationTitle;
+ private int mNotificationHeaderExpandedPadding;
+ private View mConversationHeader;
+ private View mContentContainer;
+ private boolean mExpandable = true;
+ private int mContentMarginEnd;
public ConversationLayout(@NonNull Context context) {
super(context);
@@ -166,12 +171,18 @@
});
mConversationText = findViewById(R.id.conversation_text);
mExpandButtonContainer = findViewById(R.id.expand_button_container);
+ mConversationHeader = findViewById(R.id.conversation_header);
+ mContentContainer = findViewById(R.id.notification_action_list_margin_target);
mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container);
mExpandButton = findViewById(R.id.expand_button);
mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize(
R.dimen.conversation_expand_button_top_margin_expanded);
mExpandButtonExpandedSize = getResources().getDimensionPixelSize(
R.dimen.conversation_expand_button_expanded_size);
+ mNotificationHeaderExpandedPadding = getResources().getDimensionPixelSize(
+ R.dimen.conversation_header_expanded_padding_end);
+ mContentMarginEnd = getResources().getDimensionPixelSize(
+ R.dimen.notification_content_margin_end);
mBadgedSideMargins = getResources().getDimensionPixelSize(
R.dimen.conversation_badge_side_margin);
mIconSizeBadged = getResources().getDimensionPixelSize(
@@ -207,6 +218,7 @@
mIsCollapsed = isCollapsed;
mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
updateExpandButton();
+ updateContentPaddings();
}
@RemotableViewMethod
@@ -652,6 +664,7 @@
mAddedGroups.add(newGroup);
}
newGroup.setDisplayImagesAtEnd(mIsCollapsed);
+ newGroup.setIsInConversation(true);
newGroup.setLayoutColor(mLayoutColor);
newGroup.setTextColors(mSenderTextColor, mMessageTextColor);
Person sender = senders.get(groupIndex);
@@ -851,10 +864,39 @@
mExpandButton.setLayoutParams(layoutParams);
mExpandButtonContainer.setContentDescription(mContext.getText(contentDescriptionId));
+ }
+ private void updateContentPaddings() {
+
+ // Let's make sure the conversation header can't run into the expand button when we're
+ // collapsed and update the paddings of the content
+ int headerPaddingEnd;
+ int contentPaddingEnd;
+ if (!mExpandable) {
+ headerPaddingEnd = 0;
+ contentPaddingEnd = mContentMarginEnd;
+ } else if (mIsCollapsed) {
+ headerPaddingEnd = 0;
+ contentPaddingEnd = 0;
+ } else {
+ headerPaddingEnd = mNotificationHeaderExpandedPadding;
+ contentPaddingEnd = mContentMarginEnd;
+ }
+ mConversationHeader.setPaddingRelative(
+ mConversationHeader.getPaddingStart(),
+ mConversationHeader.getPaddingTop(),
+ headerPaddingEnd,
+ mConversationHeader.getPaddingBottom());
+
+ mContentContainer.setPaddingRelative(
+ mContentContainer.getPaddingStart(),
+ mContentContainer.getPaddingTop(),
+ contentPaddingEnd,
+ mContentContainer.getPaddingBottom());
}
public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
+ mExpandable = expandable;
if (expandable) {
mExpandButtonContainer.setVisibility(VISIBLE);
mExpandButtonContainer.setOnClickListener(onClickListener);
@@ -862,5 +904,6 @@
// TODO: handle content paddings to end of layout
mExpandButtonContainer.setVisibility(GONE);
}
+ updateContentPaddings();
}
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 9977903..c68da97 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -85,6 +85,10 @@
private int mSenderTextPaddingSingleLine;
private boolean mIsFirstGroupInLayout = true;
private boolean mCanHideSenderIfFirst;
+ private boolean mIsInConversation = true;
+ private ViewGroup mMessagingIconContainer;
+ private int mConversationContentStart;
+ private int mNonConversationMarginEnd;
public MessagingGroup(@NonNull Context context) {
super(context);
@@ -112,6 +116,7 @@
mAvatarView = findViewById(R.id.message_icon);
mImageContainer = findViewById(R.id.messaging_group_icon_container);
mSendingSpinner = findViewById(R.id.messaging_group_sending_progress);
+ mMessagingIconContainer = findViewById(R.id.message_icon_container);
mContentContainer = findViewById(R.id.messaging_group_content_container);
mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
@@ -119,6 +124,10 @@
mDisplaySize.y = displayMetrics.heightPixels;
mSenderTextPaddingSingleLine = getResources().getDimensionPixelSize(
R.dimen.messaging_group_singleline_sender_padding_end);
+ mConversationContentStart = getResources().getDimensionPixelSize(
+ R.dimen.conversation_content_start);
+ mNonConversationMarginEnd = getResources().getDimensionPixelSize(
+ R.dimen.messaging_layout_margin_end);
}
public void updateClipRect() {
@@ -644,4 +653,21 @@
public boolean isSingleLine() {
return mSingleLine;
}
+
+ /**
+ * Set this group to be displayed in a conversation and adjust the visual appearance
+ *
+ * @param isInConversation is this in a conversation
+ */
+ public void setIsInConversation(boolean isInConversation) {
+ if (mIsInConversation != isInConversation) {
+ mIsInConversation = isInConversation;
+ MarginLayoutParams layoutParams =
+ (MarginLayoutParams) mMessagingIconContainer.getLayoutParams();
+ layoutParams.width = mIsInConversation ? mConversationContentStart
+ : ViewPager.LayoutParams.WRAP_CONTENT;
+ layoutParams.setMarginEnd(mIsInConversation ? 0 : mNonConversationMarginEnd);
+ mMessagingIconContainer.setLayoutParams(layoutParams);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 503f3f1..3fb5d43 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -448,6 +448,7 @@
mAddedGroups.add(newGroup);
}
newGroup.setDisplayImagesAtEnd(mDisplayImagesAtEnd);
+ newGroup.setIsInConversation(false);
newGroup.setLayoutColor(mLayoutColor);
newGroup.setTextColors(mSenderTextColor, mMessageTextColor);
Person sender = senders.get(groupIndex);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index dafb377..ea3c0fa 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1567,23 +1567,6 @@
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
const userid_t user_id = multiuser_get_user_id(uid);
- // If FUSE is not ready for this user, skip it
- // TODO(148772775): Pass primary volume name from zygote argument to here
- std::string tmp = GetProperty("vold.fuse_running_users", "");
- std::istringstream fuse_running_users(tmp);
- bool user_found = false;
- std::string s;
- std::string user_id_str = std::to_string(user_id);
- while (!user_found && std::getline(fuse_running_users, s, ',')) {
- if (user_id_str == s) {
- user_found = true;
- }
- }
- if (!user_found) {
- ALOGI("User %d is not running fuse yet, fuse_running_users=%s", user_id, tmp.c_str());
- return;
- }
-
// Fuse is ready, so we can start using fuse path.
int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
@@ -1611,7 +1594,7 @@
jstring managed_nice_name, bool is_system_server,
bool is_child_zygote, jstring managed_instruction_set,
jstring managed_app_data_dir, bool is_top_app,
- jobjectArray pkg_data_info_list) {
+ jobjectArray pkg_data_info_list, bool mount_storage_dirs) {
const char* process_name = is_system_server ? "system_server" : "zygote";
auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1650,10 +1633,7 @@
isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
-
- if ((mount_external != MOUNT_EXTERNAL_INSTALLER) &&
- GetBoolProperty(kPropFuse, false) &&
- GetBoolProperty(ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ if ((mount_external != MOUNT_EXTERNAL_INSTALLER) && mount_storage_dirs) {
BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
@@ -2023,7 +2003,7 @@
jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
- jobjectArray pkg_data_info_list) {
+ jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -2060,7 +2040,8 @@
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE, pkg_data_info_list);
+ is_top_app == JNI_TRUE, pkg_data_info_list,
+ mount_storage_dirs == JNI_TRUE);
}
return pid;
}
@@ -2095,7 +2076,7 @@
permitted_capabilities, effective_capabilities,
MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
false, nullptr, nullptr, /* is_top_app= */ false,
- /* pkg_data_info_list */ nullptr);
+ /* pkg_data_info_list */ nullptr, false);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -2225,14 +2206,15 @@
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
- jobjectArray pkg_data_info_list) {
+ jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE, pkg_data_info_list);
+ is_top_app == JNI_TRUE, pkg_data_info_list,
+ mount_storage_dirs == JNI_TRUE);
}
/**
@@ -2426,7 +2408,7 @@
static const JNINativeMethod gMethods[] = {
{"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
- "String;Z[Ljava/lang/String;)I",
+ "String;Z[Ljava/lang/String;Z)I",
(void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
{"nativeForkSystemServer", "(II[II[[IJJ)I",
(void*)com_android_internal_os_Zygote_nativeForkSystemServer},
@@ -2439,7 +2421,7 @@
{"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
{"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
- "String;Z[Ljava/lang/String;)V",
+ "String;Z[Ljava/lang/String;Z)V",
(void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
{"nativeInitNativeState", "(Z)V",
(void*)com_android_internal_os_Zygote_nativeInitNativeState},
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index dc52e97..2348dee 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -93,9 +93,10 @@
android:clipChildren="false"
android:clipToPadding="false">
<!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
- <FrameLayout
+ <com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="0dp"
+ android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_weight="1">
@@ -163,12 +164,12 @@
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="40dp"
+ android:layout_marginTop="2dp"
android:spacing="@dimen/notification_messaging_spacing"
android:clipToPadding="false"
android:clipChildren="false"
/>
- </FrameLayout>
+ </com.android.internal.widget.RemeasuringLinearLayout>
<!-- Unread Count -->
<!-- <TextView /> -->
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
index 15146c0..3188861 100644
--- a/core/res/res/layout/notification_template_messaging_group.xml
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -21,8 +21,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal" >
<FrameLayout
+ android:id="@+id/message_icon_container"
android:layout_width="@dimen/conversation_content_start"
- android:layout_height="wrap_content"> <!--TODO: make sure to make this padding dynamic-->
+ android:layout_height="wrap_content">
<ImageView
android:layout_gravity="top|center_horizontal"
android:id="@+id/message_icon"
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 619b392..176f289 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:fontFamily="@string/config_headlineFontFamilyMedium"
android:textColor="@color/resolver_empty_state_text"
- android:textSize="18sp"
+ android:textSize="14sp"
android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/resolver_empty_state_subtitle"
@@ -47,7 +47,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/resolver_empty_state_text"
- android:textSize="14sp"
+ android:textSize="12sp"
android:gravity="center_horizontal"
android:layout_centerHorizontal="true" />
<Button
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6f468e0..6060d9a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4420,5 +4420,9 @@
<string name="config_customSessionPolicyProvider"></string>
<!-- The max scale for the wallpaper when it's zoomed in -->
- <item name="config_wallpaperMaxScale" format="float" type="dimen">1</item>
+ <item name="config_wallpaperMaxScale" format="float" type="dimen">1.15</item>
+
+ <!-- Package name that will receive an explicit manifest broadcast for
+ android.os.action.POWER_SAVE_MODE_CHANGED. -->
+ <string name="config_powerSaveModeChangedListenerPackage" translatable="false"></string>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e3fe982..15ef09c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -699,6 +699,12 @@
<!-- margin on the top when the icon is centered for group conversations -->
<dimen name="conversation_icon_margin_top_centered">5dp</dimen>
+ <!-- The padding of the conversation header when expanded. This is calculated from the expand button size + notification_content_margin_end -->
+ <dimen name="conversation_header_expanded_padding_end">38dp</dimen>
+
+ <!-- margin at the end of messaging group icons when not conversations -->
+ <dimen name="messaging_layout_margin_end">12dp</dimen>
+
<!-- Padding between text and sender when singleline -->
<dimen name="messaging_group_singleline_sender_padding_end">4dp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7230cc4..cf68aff 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3044,8 +3044,6 @@
<public name="config_defaultCallScreening" />
<!-- @hide @SystemApi @TestApi -->
<public name="config_systemGallery" />
- <!-- @hide @SystemApi -->
- <public name="low_memory" />
</public-group>
<public-group type="bool" first-id="0x01110005">
@@ -3073,12 +3071,6 @@
<public-group type="array" first-id="0x01070006">
<!-- @hide @SystemApi -->
<public name="simColors" />
- <!-- @hide @SystemApi -->
- <public name="config_restrictedPreinstalledCarrierApps" />
- <!-- @hide @SystemApi -->
- <public name="config_sms_enabled_single_shift_tables" />
- <!-- @hide @SystemApi -->
- <public name="config_sms_enabled_locking_shift_tables" />
</public-group>
<public-group type="string" first-id="0x0104002c">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 49a0f17..4c0dd8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3615,6 +3615,7 @@
<java-symbol type="bool" name="config_batterySaverStickyBehaviourDisabled" />
<java-symbol type="integer" name="config_dynamicPowerSavingsDefaultDisableThreshold" />
<java-symbol type="string" name="config_batterySaverScheduleProvider" />
+ <java-symbol type="string" name="config_powerSaveModeChangedListenerPackage" />
<!-- For car devices -->
<java-symbol type="string" name="car_loading_profile" />
@@ -3890,6 +3891,7 @@
<java-symbol type="id" name="conversation_face_pile_top" />
<java-symbol type="id" name="conversation_face_pile" />
<java-symbol type="id" name="conversation_text" />
+ <java-symbol type="id" name="message_icon_container" />
<java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" />
<java-symbol type="dimen" name="conversation_expand_button_expanded_size" />
<java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" />
@@ -3897,6 +3899,9 @@
<java-symbol type="dimen" name="conversation_icon_size_badged" />
<java-symbol type="dimen" name="conversation_icon_size_centered" />
<java-symbol type="dimen" name="conversation_icon_margin_top_centered" />
+ <java-symbol type="dimen" name="conversation_content_start" />
+ <java-symbol type="dimen" name="messaging_layout_margin_end" />
+ <java-symbol type="dimen" name="conversation_header_expanded_padding_end" />
<java-symbol type="layout" name="notification_template_material_conversation" />
<java-symbol type="layout" name="conversation_face_pile_layout" />
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index f1ab8dd..42ab2e7 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -58,12 +58,12 @@
import android.view.test.InsetsModeSession;
import android.widget.TextView;
-import com.android.server.testutils.OffsettableClock;
-import com.android.server.testutils.TestHandler;
-
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -74,7 +74,6 @@
import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
-import java.util.function.Supplier;
/**
* Tests for {@link InsetsController}.
@@ -199,7 +198,7 @@
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
- new LinearInterpolator(), mockListener);
+ new LinearInterpolator(), new CancellationSignal(), mockListener);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -219,7 +218,7 @@
WindowInsetsAnimationControlListener controlListener =
mock(WindowInsetsAnimationControlListener.class);
mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, new LinearInterpolator(),
- controlListener);
+ new CancellationSignal(), controlListener);
mController.addOnControllableInsetsChangedListener(
(controller, typeMask) -> assertEquals(0, typeMask));
verify(controlListener).onCancelled();
@@ -498,7 +497,7 @@
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
- new LinearInterpolator(), mockListener);
+ new LinearInterpolator(), new CancellationSignal(), mockListener);
ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
@@ -523,9 +522,10 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- CancellationSignal cancellationSignal = mController.controlWindowInsetsAnimation(
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ mController.controlWindowInsetsAnimation(
statusBars(), 0 /* durationMs */,
- new LinearInterpolator(), mockListener);
+ new LinearInterpolator(), cancellationSignal, mockListener);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -548,7 +548,8 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener);
+ mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+ null /* cancellationSignal */, listener);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -572,7 +573,8 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener);
+ mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+ null /* cancellationSignal */, listener);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -592,7 +594,8 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener);
+ mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+ null /* cancellationSignal */, listener);
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -613,8 +616,10 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(), listener)
- .cancel();
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ mController.controlWindowInsetsAnimation(ime(), 0, new LinearInterpolator(),
+ cancellationSignal, listener);
+ cancellationSignal.cancel();
verify(listener).onCancelled();
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 9797178..33f859e 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -20,8 +20,9 @@
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -35,12 +36,12 @@
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.animation.LinearInterpolator;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.runner.AndroidJUnit4;
-
/**
* Tests for {@link PendingInsetsControllerTest}.
*
@@ -95,10 +96,11 @@
public void testControl() {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
- CancellationSignal signal = mPendingInsetsController.controlWindowInsetsAnimation(
- systemBars(), 0, new LinearInterpolator(), listener);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ mPendingInsetsController.controlWindowInsetsAnimation(
+ systemBars(), 0, new LinearInterpolator(), cancellationSignal, listener);
verify(listener).onCancelled();
- assertTrue(signal.isCanceled());
+ assertFalse(cancellationSignal.isCanceled());
}
@Test
@@ -106,10 +108,11 @@
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
mPendingInsetsController.replayAndAttach(mReplayedController);
- mPendingInsetsController.controlWindowInsetsAnimation(
- systemBars(), 0L, new LinearInterpolator(), listener);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ mPendingInsetsController.controlWindowInsetsAnimation(systemBars(), 0L,
+ new LinearInterpolator(), cancellationSignal, listener);
verify(mReplayedController).controlWindowInsetsAnimation(eq(systemBars()), eq(0L), any(),
- eq(listener));
+ eq(cancellationSignal), eq(listener));
}
@Test
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 487a6e9..5466ac8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -62,6 +62,10 @@
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.launcher3">
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.location.fused">
<permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index bc933ff..670ef5e 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -82,6 +82,63 @@
}
}
+ private static void addSids(KeymasterArguments args, UserAuthArgs spec) {
+ // If both biometric and credential are accepted, then just use the root sid from gatekeeper
+ if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG
+ | KeyProperties.AUTH_DEVICE_CREDENTIAL)) {
+ if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(spec.getBoundToSpecificSecureUserId()));
+ } else {
+ // The key is authorized for use for the specified amount of time after the user has
+ // authenticated. Whatever unlocks the secure lock screen should authorize this key.
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(getRootSid()));
+ }
+ } else {
+ List<Long> sids = new ArrayList<>();
+ if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) {
+ final BiometricManager bm = KeyStore.getApplicationContext()
+ .getSystemService(BiometricManager.class);
+
+ // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer
+ // needed here.
+
+ final long[] biometricSids = bm.getAuthenticatorIds();
+
+ if (biometricSids.length == 0) {
+ throw new IllegalStateException(
+ "At least one biometric must be enrolled to create keys requiring user"
+ + " authentication for every use");
+ }
+
+ if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
+ sids.add(spec.getBoundToSpecificSecureUserId());
+ } else if (spec.isInvalidatedByBiometricEnrollment()) {
+ // The biometric-only SIDs will change on biometric enrollment or removal of all
+ // enrolled templates, invalidating the key.
+ for (long sid : biometricSids) {
+ sids.add(sid);
+ }
+ } else {
+ // The root SID will *not* change on fingerprint enrollment, or removal of all
+ // enrolled fingerprints, allowing the key to remain valid.
+ sids.add(getRootSid());
+ }
+ } else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL)
+ != 0) {
+ sids.add(getRootSid());
+ } else {
+ throw new IllegalStateException("Invalid or no authentication type specified.");
+ }
+
+ for (int i = 0; i < sids.size(); i++) {
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(sids.get(i)));
+ }
+ }
+ }
+
/**
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
@@ -114,40 +171,7 @@
if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
// Every use of this key needs to be authorized by the user.
- final BiometricManager bm = KeyStore.getApplicationContext()
- .getSystemService(BiometricManager.class);
-
- // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer
- // needed here.
-
- final long[] biometricSids = bm.getAuthenticatorIds();
-
- if (biometricSids.length == 0) {
- throw new IllegalStateException(
- "At least one biometric must be enrolled to create keys requiring user"
- + " authentication for every use");
- }
-
- List<Long> sids = new ArrayList<>();
- if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
- sids.add(spec.getBoundToSpecificSecureUserId());
- } else if (spec.isInvalidatedByBiometricEnrollment()) {
- // The biometric-only SIDs will change on biometric enrollment or removal of all
- // enrolled templates, invalidating the key.
- for (long sid : biometricSids) {
- sids.add(sid);
- }
- } else {
- // The root SID will *not* change on fingerprint enrollment, or removal of all
- // enrolled fingerprints, allowing the key to remain valid.
- sids.add(getRootSid());
- }
-
- for (int i = 0; i < sids.size(); i++) {
- args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
- KeymasterArguments.toUint64(sids.get(i)));
- }
-
+ addSids(args, spec);
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
if (spec.isUserAuthenticationValidWhileOnBody()) {
@@ -155,16 +179,7 @@
+ "supported for keys requiring fingerprint authentication");
}
} else {
- long sid;
- if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
- sid = spec.getBoundToSpecificSecureUserId();
- } else {
- // The key is authorized for use for the specified amount of time after the user has
- // authenticated. Whatever unlocks the secure lock screen should authorize this key.
- sid = getRootSid();
- }
- args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
- KeymasterArguments.toUint64(sid));
+ addSids(args, spec);
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds());
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 80545e5..bde45d7 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -419,8 +419,7 @@
controller.release();
- final int requestId;
- requestId = mControllerCreationRequestCnt.getAndIncrement();
+ final int requestId = mControllerCreationRequestCnt.getAndIncrement();
ControllerCreationRequest request =
new ControllerCreationRequest(requestId, controller, route);
@@ -610,10 +609,16 @@
}
if (sessionInfo != null) {
- RoutingController newController = new RoutingController(sessionInfo);
- synchronized (sRouterLock) {
- mRoutingControllers.put(newController.getId(), newController);
+ RoutingController newController;
+ if (sessionInfo.isSystemSession()) {
+ newController = getSystemController();
+ } else {
+ newController = new RoutingController(sessionInfo);
+ synchronized (sRouterLock) {
+ mRoutingControllers.put(newController.getId(), newController);
+ }
}
+ //TODO: Determine oldController properly when transfer is launched by Output Switcher.
notifyTransferred(matchingRequest != null ? matchingRequest.mController :
getSystemController(), newController);
}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index 7b85fa8..382cc85 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -213,13 +214,24 @@
/**
* Builder for {@link AnalogFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mSignalType;
private int mSifStandard;
private Builder() {}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets analog signal type.
*/
@NonNull
@@ -244,10 +256,5 @@
public AnalogFrontendSettings build() {
return new AnalogFrontendSettings(mFrequency, mSignalType, mSifStandard);
}
-
- @Override
- Builder self() {
- return this;
- }
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index b40ab00..1394716 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -325,7 +326,8 @@
/**
* Builder for {@link Atsc3FrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mBandwidth;
private int mDemodOutputFormat;
private Atsc3PlpSettings[] mPlpSettings;
@@ -334,6 +336,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets bandwidth.
*/
@NonNull
@@ -366,11 +378,6 @@
return new Atsc3FrontendSettings(
mFrequency, mBandwidth, mDemodOutputFormat, mPlpSettings);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index fc82a1c..53352f0 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -91,13 +92,24 @@
/**
* Builder for {@link AtscFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mModulation;
private Builder() {
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Modulation.
*/
@NonNull
@@ -113,11 +125,6 @@
public AtscFrontendSettings build() {
return new AtscFrontendSettings(mFrequency, mModulation);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index 197c1c5..6d58570 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -218,7 +219,8 @@
/**
* Builder for {@link DvbcFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mModulation;
private long mInnerFec;
private int mSymbolRate;
@@ -230,6 +232,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Modulation.
*/
@NonNull
@@ -286,11 +298,6 @@
return new DvbcFrontendSettings(mFrequency, mModulation, mInnerFec, mSymbolRate,
mOuterFec, mAnnex, mSpectralInversion);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index 4a4fed5..9c45dd1 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -303,7 +304,8 @@
/**
* Builder for {@link DvbsFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mModulation;
private DvbsCodeRate mCodeRate;
private int mSymbolRate;
@@ -317,6 +319,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Modulation.
*/
@NonNull
@@ -389,11 +401,6 @@
return new DvbsFrontendSettings(mFrequency, mModulation, mCodeRate, mSymbolRate,
mRolloff, mPilot, mInputStreamId, mStandard, mVcmMode);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index 1510b2d..4accabb 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -506,7 +507,8 @@
/**
* Builder for {@link DvbtFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mTransmissionMode;
private int mBandwidth;
private int mConstellation;
@@ -525,6 +527,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Transmission Mode.
*/
@NonNull
@@ -638,11 +650,6 @@
mConstellation, mHierarchy, mHpCodeRate, mLpCodeRate, mGuardInterval,
mIsHighPriority, mStandard, mIsMiso, mPlpMode, mPlpId, mPlpGroupId);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
index 9071526..2f2fa97 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -17,9 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.LongDef;
-import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
@@ -265,27 +263,4 @@
public int getFrequency() {
return mFrequency;
}
-
- /**
- * Builder for {@link FrontendSettings}.
- *
- * @param <T> The subclass to be built.
- */
- public abstract static class Builder<T extends Builder<T>> {
- /* package */ int mFrequency;
-
- /* package */ Builder() {}
-
- /**
- * Sets frequency in Hz.
- */
- @NonNull
- @IntRange(from = 1)
- public T setFrequency(int frequency) {
- mFrequency = frequency;
- return self();
- }
-
- /* package */ abstract T self();
- }
}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 9b0e533..f385379 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -224,7 +225,8 @@
/**
* Builder for {@link Isdbs3FrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mStreamId;
private int mStreamIdType;
private int mModulation;
@@ -236,6 +238,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Stream ID.
*/
@NonNull
@@ -292,11 +304,6 @@
return new Isdbs3FrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
mCodeRate, mSymbolRate, mRolloff);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 14c08b1..4667b9b 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -208,7 +209,8 @@
/**
* Builder for {@link IsdbsFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mStreamId;
private int mStreamIdType;
private int mModulation;
@@ -220,6 +222,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Stream ID.
*/
@NonNull
@@ -276,11 +288,6 @@
return new IsdbsFrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
mCodeRate, mSymbolRate, mRolloff);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index de3c80d..b607623 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -17,6 +17,7 @@
package android.media.tv.tuner.frontend;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -202,7 +203,8 @@
/**
* Builder for {@link IsdbtFrontendSettings}.
*/
- public static class Builder extends FrontendSettings.Builder<Builder> {
+ public static class Builder {
+ private int mFrequency;
private int mModulation;
private int mBandwidth;
private int mMode;
@@ -214,6 +216,16 @@
}
/**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
* Sets Modulation.
*/
@NonNull
@@ -270,11 +282,6 @@
return new IsdbtFrontendSettings(mFrequency, mModulation, mBandwidth, mMode, mCodeRate,
mGuardInterval, mServiceAreaId);
}
-
- @Override
- Builder self() {
- return this;
- }
}
@Override
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index 83f3b6e..efeb335 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -120,8 +120,9 @@
}
if (mCaptureThread != 0) {
+ sp<CaptureThread> t = mCaptureThread;
mCaptureLock.unlock();
- mCaptureThread->requestExitAndWait();
+ t->requestExitAndWait();
mCaptureLock.lock();
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index b63162b..3ee92bd7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -238,10 +238,12 @@
}
buildNavBarContent();
- // If the UI was rebuilt (day/night change) while the keyguard was up we need to
- // correctly respect that state.
+ // If the UI was rebuilt (day/night change or user change) while the keyguard was up we need
+ // to correctly respect that state.
if (mKeyguardStateControllerLazy.get().isShowing()) {
mCarNavigationBarController.showAllKeyguardButtons(isDeviceSetupForUser());
+ } else {
+ mCarNavigationBarController.hideAllKeyguardButtons(isDeviceSetupForUser());
}
// Upon restarting the Navigation Bar, CarFacetButtonController should immediately apply the
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index c04e47f..f2748b8 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -46,8 +46,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -68,12 +66,8 @@
@Mock
private ButtonSelectionStateListener mButtonSelectionStateListener;
@Mock
- private Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
- @Mock
private KeyguardStateController mKeyguardStateController;
@Mock
- private Lazy<NavigationBarController> mNavigationBarControllerLazy;
- @Mock
private NavigationBarController mNavigationBarController;
@Mock
private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
@@ -89,13 +83,11 @@
mCarNavigationBar = new CarNavigationBar(mContext, mCarNavigationBarController,
mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext),
mAutoHideController, mButtonSelectionStateListener, mHandler,
- mKeyguardStateControllerLazy, mNavigationBarControllerLazy,
+ () -> mKeyguardStateController, () -> mNavigationBarController,
mSuperStatusBarViewFactory, mButtonSelectionStateController);
StatusBarWindowView statusBarWindowView = (StatusBarWindowView) LayoutInflater.from(
mContext).inflate(R.layout.super_status_bar, /* root= */ null);
when(mSuperStatusBarViewFactory.getStatusBarWindowView()).thenReturn(statusBarWindowView);
- when(mNavigationBarControllerLazy.get()).thenReturn(mNavigationBarController);
- when(mKeyguardStateControllerLazy.get()).thenReturn(mKeyguardStateController);
when(mKeyguardStateController.isShowing()).thenReturn(false);
mDependency.injectMockDependency(WindowManager.class);
// Needed to inflate top navigation bar.
@@ -119,4 +111,44 @@
verify(mButtonSelectionStateListener).onTaskStackChanged();
}
+
+ @Test
+ public void restartNavBars_newUserNotSetupWithKeyguardShowing_showsKeyguardButtons() {
+ ArgumentCaptor<CarDeviceProvisionedController.DeviceProvisionedListener>
+ deviceProvisionedCallbackCaptor = ArgumentCaptor.forClass(
+ CarDeviceProvisionedController.DeviceProvisionedListener.class);
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+ mCarNavigationBar.start();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ // switching the currentUserSetup value to force restart the navbars.
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+ verify(mDeviceProvisionedController).addCallback(deviceProvisionedCallbackCaptor.capture());
+
+ deviceProvisionedCallbackCaptor.getValue().onUserSwitched();
+ waitForIdleSync(mHandler);
+
+ verify(mCarNavigationBarController).showAllKeyguardButtons(false);
+ }
+
+ @Test
+ public void restartNavbars_newUserIsSetupWithKeyguardHidden_hidesKeyguardButtons() {
+ ArgumentCaptor<CarDeviceProvisionedController.DeviceProvisionedListener>
+ deviceProvisionedCallbackCaptor = ArgumentCaptor.forClass(
+ CarDeviceProvisionedController.DeviceProvisionedListener.class);
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+ mCarNavigationBar.start();
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ // switching the currentUserSetup value to force restart the navbars.
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+ verify(mDeviceProvisionedController).addCallback(deviceProvisionedCallbackCaptor.capture());
+ deviceProvisionedCallbackCaptor.getValue().onUserSwitched();
+ waitForIdleSync(mHandler);
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ deviceProvisionedCallbackCaptor.getValue().onUserSetupChanged();
+ waitForIdleSync(mHandler);
+
+ verify(mCarNavigationBarController).hideAllKeyguardButtons(true);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 18eb187..3c78560 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -218,7 +218,7 @@
}
public boolean supportsHighQualityAudio(BluetoothDevice device) {
- BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
if (bluetoothDevice == null) {
return false;
}
@@ -227,7 +227,7 @@
}
public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
- BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
if (bluetoothDevice == null) {
return false;
}
@@ -253,7 +253,7 @@
}
public void setHighQualityAudioEnabled(BluetoothDevice device, boolean enabled) {
- BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
if (bluetoothDevice == null) {
return;
}
@@ -272,7 +272,7 @@
}
public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
- BluetoothDevice bluetoothDevice = (device == null) ? device : mService.getActiveDevice();
+ BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
if (bluetoothDevice == null || !supportsHighQualityAudio(device)
|| getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 4a0d16c..44b481d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -49,11 +49,13 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({MediaDeviceState.STATE_CONNECTED,
MediaDeviceState.STATE_CONNECTING,
- MediaDeviceState.STATE_DISCONNECTED})
+ MediaDeviceState.STATE_DISCONNECTED,
+ MediaDeviceState.STATE_CONNECTING_FAILED})
public @interface MediaDeviceState {
- int STATE_CONNECTED = 1;
- int STATE_CONNECTING = 2;
- int STATE_DISCONNECTED = 3;
+ int STATE_CONNECTED = 0;
+ int STATE_CONNECTING = 1;
+ int STATE_DISCONNECTED = 2;
+ int STATE_CONNECTING_FAILED = 3;
}
private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
@@ -126,6 +128,7 @@
final CachedBluetoothDevice cachedDevice =
((BluetoothMediaDevice) device).getCachedDevice();
if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
+ device.setState(MediaDeviceState.STATE_CONNECTING);
cachedDevice.connect();
return;
}
@@ -140,6 +143,7 @@
mCurrentConnectedDevice.disconnect();
}
+ device.setState(MediaDeviceState.STATE_CONNECTING);
if (TextUtils.isEmpty(mPackageName)) {
mInfoMediaManager.connectDeviceWithoutPackageName(device);
} else {
@@ -421,6 +425,7 @@
MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
connectDevice = connectDevice != null
? connectDevice : updateCurrentConnectedDevice();
+ connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
if (connectDevice == mCurrentConnectedDevice) {
Log.d(TAG, "onConnectedDeviceChanged() this device all ready connected!");
@@ -438,6 +443,11 @@
@Override
public void onRequestFailed(int reason) {
+ for (MediaDevice device : mMediaDevices) {
+ if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
+ device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+ }
+ }
dispatchOnRequestFailed(reason);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 33c3d7e..39e6a12 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -51,6 +51,7 @@
int mType;
private int mConnectedRecord;
+ private int mState;
protected final Context mContext;
protected final MediaRoute2Info mRouteInfo;
@@ -200,6 +201,22 @@
}
/**
+ * Set current device's state
+ */
+ public void setState(@LocalMediaManager.MediaDeviceState int state) {
+ mState = state;
+ }
+
+ /**
+ * Get current device's state
+ *
+ * @return state of device
+ */
+ public @LocalMediaManager.MediaDeviceState int getState() {
+ return mState;
+ }
+
+ /**
* Rules:
* 1. If there is one of the connected devices identified as a carkit, this carkit will
* be always on the top of the device list. Rule 2 and Rule 3 can’t overrule this rule.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 18d8f14..f825ec5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -16,6 +16,8 @@
package com.android.settingslib.media;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -27,6 +29,8 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -53,10 +57,13 @@
@Config(shadows = {ShadowBluetoothAdapter.class})
public class LocalMediaManagerTest {
+ private static final String TEST_DEVICE_NAME_1 = "device_name_1";
+ private static final String TEST_DEVICE_NAME_2 = "device_name_2";
private static final String TEST_DEVICE_ID_1 = "device_id_1";
private static final String TEST_DEVICE_ID_2 = "device_id_2";
private static final String TEST_DEVICE_ID_3 = "device_id_3";
private static final String TEST_CURRENT_DEVICE_ID = "currentDevice_id";
+ private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
@Mock
private BluetoothMediaManager mBluetoothMediaManager;
@@ -72,10 +79,18 @@
private A2dpProfile mA2dpProfile;
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
+ @Mock
+ private MediaRouter2Manager mMediaRouter2Manager;
+ @Mock
+ private MediaRoute2Info mRouteInfo1;
+ @Mock
+ private MediaRoute2Info mRouteInfo2;
private Context mContext;
private LocalMediaManager mLocalMediaManager;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private InfoMediaDevice mInfoMediaDevice1;
+ private InfoMediaDevice mInfoMediaDevice2;
@Before
public void setUp() {
@@ -84,11 +99,18 @@
final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
-
+ when(mRouteInfo1.getName()).thenReturn(TEST_DEVICE_NAME_1);
+ when(mRouteInfo1.getId()).thenReturn(TEST_DEVICE_ID_1);
+ when(mRouteInfo2.getName()).thenReturn(TEST_DEVICE_NAME_2);
+ when(mRouteInfo2.getId()).thenReturn(TEST_DEVICE_ID_2);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ mInfoMediaDevice1 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo1,
+ TEST_PACKAGE_NAME);
+ mInfoMediaDevice2 = new InfoMediaDevice(mContext, mMediaRouter2Manager, mRouteInfo2,
+ TEST_PACKAGE_NAME);
mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
mInfoMediaManager, "com.test.packagename");
mLocalMediaManager.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -123,6 +145,32 @@
}
@Test
+ public void connectDevice_deviceNotEqualCurrentConnectedDevice_isConnectingState() {
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+ mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
+
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.connectDevice(mInfoMediaDevice2);
+
+ assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTING);
+ }
+
+ @Test
+ public void connectDevice_deviceEqualCurrentConnectedDevice_notConnectingState() {
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+ mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
+
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.connectDevice(mInfoMediaDevice1);
+
+ assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTING);
+ }
+
+ @Test
public void connectDevice_bluetoothDeviceNotConnected_connectBluetoothDevice() {
final MediaDevice device = mock(BluetoothMediaDevice.class);
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
@@ -388,6 +436,21 @@
}
@Test
+ public void onConnectedDeviceChanged_isConnectedState() {
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+ mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_DISCONNECTED);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
+
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTED);
+ }
+
+ @Test
public void onDeviceAttributesChanged_shouldDispatchDeviceListUpdate() {
mLocalMediaManager.registerCallback(mCallback);
@@ -397,6 +460,26 @@
}
@Test
+ public void onRequestFailed_checkDevicesState() {
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice1);
+ mLocalMediaManager.mMediaDevices.add(mInfoMediaDevice2);
+ mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+ mInfoMediaDevice2.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTING);
+ assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTED);
+ mLocalMediaManager.registerCallback(mCallback);
+ mLocalMediaManager.mMediaDeviceCallback.onRequestFailed(REASON_UNKNOWN_ERROR);
+
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTING_FAILED);
+ assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
+ .STATE_CONNECTED);
+ }
+
+ @Test
public void getActiveMediaDevice_checkList() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 3f29b72..4b08387 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -448,4 +448,23 @@
assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(
mContext.getResources().getString(R.string.unknown));
}
+
+ @Test
+ public void setState_verifyGetState() {
+ mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+ LocalMediaManager.MediaDeviceState.STATE_CONNECTED);
+
+ mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+ LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
+
+ mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+ LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+
+ mInfoMediaDevice1.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
+ assertThat(mInfoMediaDevice1.getState()).isEqualTo(
+ LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
+ }
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index c8b0d99..82224df 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -529,4 +529,7 @@
<!-- Flag to turn on the rendering of the above path or not -->
<bool name="config_enableDisplayCutoutProtection">false</bool>
+ <!-- Respect drawable/rounded.xml intrinsic size for multiple radius corner path customization -->
+ <bool name="config_roundedCornerMultipleRadius">false</bool>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 537a812..a8a3cae 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -50,6 +50,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.graphics.drawable.VectorDrawable;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.HandlerThread;
@@ -126,8 +127,9 @@
private WindowManager mWindowManager;
private int mRotation;
private SecureSetting mColorInversionSetting;
- private boolean mPendingRotationChange;
private Handler mHandler;
+ private boolean mPendingRotationChange;
+ private boolean mIsRoundedCornerMultipleRadius;
private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback =
new CameraAvailabilityListener.CameraTransitionCallback() {
@@ -194,6 +196,8 @@
mRotation = mContext.getDisplay().getRotation();
mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mIsRoundedCornerMultipleRadius = mContext.getResources().getBoolean(
+ R.bool.config_roundedCornerMultipleRadius);
updateRoundedCornerRadii();
setupDecorations();
setupCameraListener();
@@ -572,15 +576,22 @@
com.android.internal.R.dimen.rounded_corner_radius_top);
final int newRoundedDefaultBottom = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.rounded_corner_radius_bottom);
-
final boolean roundedCornersChanged = mRoundedDefault != newRoundedDefault
|| mRoundedDefaultBottom != newRoundedDefaultBottom
|| mRoundedDefaultTop != newRoundedDefaultTop;
if (roundedCornersChanged) {
- mRoundedDefault = newRoundedDefault;
- mRoundedDefaultTop = newRoundedDefaultTop;
- mRoundedDefaultBottom = newRoundedDefaultBottom;
+ // If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
+ // max(width, height) size of drawable/rounded.xml instead of rounded_corner_radius
+ if (mIsRoundedCornerMultipleRadius) {
+ final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
+ mRoundedDefault = Math.max(d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ mRoundedDefaultTop = mRoundedDefaultBottom = mRoundedDefault;
+ } else {
+ mRoundedDefault = newRoundedDefault;
+ mRoundedDefaultTop = newRoundedDefaultTop;
+ mRoundedDefaultBottom = newRoundedDefaultBottom;
+ }
onTuningChanged(SIZE, null);
}
}
@@ -631,7 +642,8 @@
}
private boolean hasRoundedCorners() {
- return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0;
+ return mRoundedDefault > 0 || mRoundedDefaultBottom > 0 || mRoundedDefaultTop > 0
+ || mIsRoundedCornerMultipleRadius;
}
private boolean shouldShowRoundedCorner(@BoundsPosition int pos) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 406e7ce..b39dd1a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -52,6 +52,7 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
@@ -146,13 +147,15 @@
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
private BubbleIconFactory mBubbleIconFactory;
- private int mMaxBubbles;
// Tracks the id of the current (foreground) user.
private int mCurrentUserId;
// Saves notification keys of active bubbles when users are switched.
private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+ // Used when ranking updates occur and we check if things should bubble / unbubble
+ private NotificationListenerService.Ranking mTmpRanking;
+
// Saves notification keys of user created "fake" bubbles so that we can allow notifications
// like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
private final HashSet<String> mUserCreatedBubbles;
@@ -338,7 +341,6 @@
configurationController.addCallback(this /* configurationListener */);
- mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
mBubbleData = data;
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(new NotificationSuppressionChangedListener() {
@@ -939,9 +941,29 @@
}
}
+ /**
+ * Called when NotificationListener has received adjusted notification rank and reapplied
+ * filtering and sorting. This is used to dismiss or create bubbles based on changes in
+ * permissions on the notification channel or the global setting.
+ *
+ * @param rankingMap the updated ranking map from NotificationListenerService
+ */
private void onRankingUpdated(RankingMap rankingMap) {
- // Forward to BubbleData to block any bubbles which should no longer be shown
- mBubbleData.notificationRankingUpdated(rankingMap);
+ if (mTmpRanking == null) {
+ mTmpRanking = new NotificationListenerService.Ranking();
+ }
+ String[] orderedKeys = rankingMap.getOrderedKeys();
+ for (int i = 0; i < orderedKeys.length; i++) {
+ String key = orderedKeys[i];
+ NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+ rankingMap.getRanking(key, mTmpRanking);
+ if (mBubbleData.hasBubbleWithKey(key) && !mTmpRanking.canBubble()) {
+ mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
+ } else if (entry != null && mTmpRanking.isBubble()) {
+ entry.setFlagBubble(true);
+ onEntryUpdated(entry);
+ }
+ }
}
@SuppressWarnings("FieldCanBeLocal")
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 54b83c0..ff5e13c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -26,7 +26,6 @@
import android.app.PendingIntent;
import android.content.Context;
import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.RankingMap;
import android.util.Log;
import android.util.Pair;
@@ -298,31 +297,6 @@
}
/**
- * Called when NotificationListener has received adjusted notification rank and reapplied
- * filtering and sorting. This is used to dismiss any bubbles which should no longer be shown
- * due to changes in permissions on the notification channel or the global setting.
- *
- * @param rankingMap the updated ranking map from NotificationListenerService
- */
- public void notificationRankingUpdated(RankingMap rankingMap) {
- if (mTmpRanking == null) {
- mTmpRanking = new NotificationListenerService.Ranking();
- }
-
- String[] orderedKeys = rankingMap.getOrderedKeys();
- for (int i = 0; i < orderedKeys.length; i++) {
- String key = orderedKeys[i];
- if (hasBubbleWithKey(key)) {
- rankingMap.getRanking(key, mTmpRanking);
- if (!mTmpRanking.canBubble()) {
- doRemove(key, BubbleController.DISMISS_BLOCKED);
- }
- }
- }
- dispatchPendingChanges();
- }
-
- /**
* Adds a group key indicating that the summary for this group should be suppressed.
*
* @param groupKey the group key of the group whose summary should be suppressed.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0778d5b..e666fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -471,7 +471,8 @@
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mStackAnimationController = new StackAnimationController(floatingContentCoordinator);
+ mStackAnimationController = new StackAnimationController(
+ floatingContentCoordinator, this::getBubbleCount);
mExpandedAnimationController = new ExpandedAnimationController(
mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 3eaa90c..9b5dc31 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -409,11 +409,8 @@
mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
// Includes overflow button.
- // TODO(b/148675523) this is a temporary work around; change back once we have proper fix.
-// float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2)
-// - (mBubblesMaxRendered + 1) * mBubbleSizePx;
- float totalGapWidth = getAvailableScreenWidth(true /* includeStableInsets */)
- - (mExpandedViewPadding * 2) - (mBubblesMaxRendered + 1) * mBubbleSizePx;
+ float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2)
+ - (mBubblesMaxRendered + 1) * mBubbleSizePx;
mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered;
// Ensure that all child views are at 1x scale, and visible, in case they were animating
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 86387f1..b63ba6f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -43,6 +43,7 @@
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Set;
+import java.util.function.IntSupplier;
/**
* Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop
@@ -241,9 +242,15 @@
}
};
+ /** Returns the number of 'real' bubbles (excluding the overflow bubble). */
+ private IntSupplier mBubbleCountSupplier;
+
public StackAnimationController(
- FloatingContentCoordinator floatingContentCoordinator) {
+ FloatingContentCoordinator floatingContentCoordinator,
+ IntSupplier bubbleCountSupplier) {
mFloatingContentCoordinator = floatingContentCoordinator;
+ mBubbleCountSupplier = bubbleCountSupplier;
+
}
/**
@@ -669,6 +676,8 @@
new SpringAnimation(this, firstBubbleProperty)
.setSpring(spring)
.addEndListener((dynamicAnimation, b, v, v1) -> {
+ mRestingStackPosition.set(mStackPosition);
+
if (after != null) {
for (Runnable callback : after) {
callback.run();
@@ -736,7 +745,7 @@
return;
}
- if (mLayout.getChildCount() == 1) {
+ if (getBubbleCount() == 1) {
// If this is the first child added, position the stack in its starting position.
moveStackToStartPosition();
} else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
@@ -758,7 +767,7 @@
.start();
// If there are other bubbles, pull them into the correct position.
- if (mLayout.getChildCount() > 0) {
+ if (getBubbleCount() > 0) {
animationForChildAtIndex(0).translationX(mStackPosition.x).start();
} else {
// When all children are removed ensure stack position is sane
@@ -979,6 +988,11 @@
return mMagnetizedStack;
}
+ /** Returns the number of 'real' bubbles (excluding overflow). */
+ private int getBubbleCount() {
+ return mBubbleCountSupplier.getAsInt();
+ }
+
/**
* FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
* translation and animate the rest of the stack with it. A DynamicAnimation can animate this
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 6d16253..b99d765 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -116,16 +116,17 @@
import javax.inject.Inject;
/**
- * Helper to show the global actions dialog. Each item is an {@link Action} that
- * may show depending on whether the keyguard is showing, and whether the device
- * is provisioned.
+ * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
+ * on whether the keyguard is showing, and whether the device is provisioned.
*/
public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
- DialogInterface.OnShowListener, ConfigurationController.ConfigurationListener {
+ DialogInterface.OnShowListener,
+ ConfigurationController.ConfigurationListener,
+ GlobalActionsPanelPlugin.Callbacks {
- static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
- static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
- static public final String SYSTEM_DIALOG_REASON_DREAM = "dream";
+ public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
+ public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+ public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
private static final String TAG = "GlobalActionsDialog";
@@ -271,8 +272,9 @@
@Override
public void onUnlockedChanged() {
if (mDialog != null && mDialog.mPanelController != null) {
- boolean locked = !keyguardStateController.canDismissLockScreen();
- mDialog.mPanelController.onDeviceLockStateChanged(locked);
+ boolean unlocked = keyguardStateController.isUnlocked()
+ || keyguardStateController.canDismissLockScreen();
+ mDialog.mPanelController.onDeviceLockStateChanged(unlocked);
}
}
});
@@ -386,7 +388,7 @@
mItems.add(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
+ Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
&& shouldDisplayLockdown()) {
mItems.add(getLockdownAction());
}
@@ -420,26 +422,7 @@
mAdapter = new MyAdapter();
- GlobalActionsPanelPlugin.PanelViewController panelViewController =
- mPanelPlugin != null
- ? mPanelPlugin.onPanelShown(
- new GlobalActionsPanelPlugin.Callbacks() {
- @Override
- public void dismissGlobalActionsMenu() {
- dismissDialog();
- }
-
- @Override
- public void startPendingIntentDismissingKeyguard(
- PendingIntent intent) {
- mActivityStarter
- .startPendingIntentDismissingKeyguard(intent);
- }
- },
- !mKeyguardStateController.isUnlocked())
- : null;
-
- ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController,
+ ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(),
mDepthController, mSysuiColorExtractor, mStatusBarService,
mNotificationShadeWindowController,
shouldShowControls() ? mControlsUiController : null, mBlurUtils);
@@ -477,6 +460,33 @@
mConfigurationController.removeCallback(this);
}
+ @Nullable
+ private GlobalActionsPanelPlugin.PanelViewController getWalletPanelViewController() {
+ if (mPanelPlugin == null) {
+ return null;
+ }
+ return mPanelPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
+ }
+
+ /**
+ * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
+ * called when the quick access wallet requests dismissal.
+ */
+ @Override
+ public void dismissGlobalActionsMenu() {
+ dismissDialog();
+ }
+
+ /**
+ * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
+ * called when the quick access wallet requests that an intent be started (with lock screen
+ * shown first if needed).
+ */
+ @Override
+ public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) {
+ mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
+ }
+
private final class PowerAction extends SinglePressAction implements LongPressAction {
private PowerAction() {
super(R.drawable.ic_lock_power_off,
@@ -919,7 +929,9 @@
}
}
- /** {@inheritDoc} */
+ /**
+ * {@inheritDoc}
+ */
public void onDismiss(DialogInterface dialog) {
if (mDialog == dialog) {
mDialog = null;
@@ -935,17 +947,19 @@
}
}
- /** {@inheritDoc} */
+ /**
+ * {@inheritDoc}
+ */
public void onShow(DialogInterface dialog) {
mMetricsLogger.visible(MetricsEvent.POWER_MENU);
}
/**
- * The adapter used for the list within the global actions dialog, taking
- * into account whether the keyguard is showing via
- * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether
- * the device is provisioned
- * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
+ * The adapter used for the list within the global actions dialog, taking into account whether
+ * the keyguard is showing via
+ * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing}
+ * and whether the device is provisioned via
+ * {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
*/
public class MyAdapter extends MultiListAdapter {
private int countItems(boolean separated) {
@@ -1076,8 +1090,7 @@
*/
public interface Action {
/**
- * @return Text that will be announced when dialog is created. null
- * for none.
+ * @return Text that will be announced when dialog is created. null for none.
*/
CharSequence getLabelForAccessibility(Context context);
@@ -1086,15 +1099,13 @@
void onPress();
/**
- * @return whether this action should appear in the dialog when the keygaurd
- * is showing.
+ * @return whether this action should appear in the dialog when the keygaurd is showing.
*/
boolean showDuringKeyguard();
/**
- * @return whether this action should appear in the dialog before the
- * device is provisioned.onlongpress
- *
+ * @return whether this action should appear in the dialog before the device is
+ * provisioned.onlongpress
*/
boolean showBeforeProvisioning();
@@ -1113,8 +1124,7 @@
}
/**
- * A single press action maintains no state, just responds to a press
- * and takes an action.
+ * A single press action maintains no state, just responds to a press and takes an action.
*/
private abstract class SinglePressAction implements Action {
@@ -1188,8 +1198,8 @@
}
/**
- * A toggle action knows whether it is on or off, and displays an icon
- * and status message accordingly.
+ * A toggle action knows whether it is on or off, and displays an icon and status message
+ * accordingly.
*/
private static abstract class ToggleAction implements Action {
@@ -1239,8 +1249,7 @@
}
/**
- * Override to make changes to resource IDs just before creating the
- * View.
+ * Override to make changes to resource IDs just before creating the View.
*/
void willCreate() {
@@ -1296,9 +1305,9 @@
}
/**
- * Implementations may override this if their state can be in on of the intermediate
- * states until some notification is received (e.g airplane mode is 'turning off' until
- * we know the wireless connections are back online
+ * Implementations may override this if their state can be in on of the intermediate states
+ * until some notification is received (e.g airplane mode is 'turning off' until we know the
+ * wireless connections are back online
*
* @param buttonOn Whether the button was turned on or off
*/
@@ -1316,12 +1325,13 @@
private class AirplaneModeAction extends ToggleAction {
AirplaneModeAction() {
super(
- R.drawable.ic_lock_airplane_mode,
- R.drawable.ic_lock_airplane_mode_off,
- R.string.global_actions_toggle_airplane_mode,
- R.string.global_actions_airplane_mode_on_status,
- R.string.global_actions_airplane_mode_off_status);
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status);
}
+
void onToggle(boolean on) {
if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
mIsWaitingForEcmExit = true;
@@ -1607,11 +1617,11 @@
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
window.getAttributes().setFitInsetsTypes(0 /* types */);
setTitle(R.string.global_actions);
@@ -1755,7 +1765,8 @@
/**
* Updates background and system bars according to current GradientColors.
- * @param colors Colors and hints to use.
+ *
+ * @param colors Colors and hints to use.
* @param animate Interpolates gradient if true, just sets otherwise.
*/
private void updateColors(GradientColors colors, boolean animate) {
@@ -1921,8 +1932,8 @@
}
/**
- * Determines whether or not the Global Actions menu should be forced to
- * use the newer grid-style layout.
+ * Determines whether or not the Global Actions menu should be forced to use the newer
+ * grid-style layout.
*/
private static boolean isForceGridEnabled(Context context) {
return isPanelDebugModeEnabled(context);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index fb348f4..88491b7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -67,7 +67,7 @@
private ComponentName mLastPipComponentName;
private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
- private Size mReentrySize = null;
+ private Size mReentrySize;
private float mDefaultAspectRatio;
private float mMinAspectRatio;
@@ -77,6 +77,7 @@
private int mDefaultMinSize;
private Point mScreenEdgeInsets;
private int mCurrentMinSize;
+ private Size mOverrideMinimalSize;
private boolean mIsImeShowing;
private int mImeHeight;
@@ -226,11 +227,14 @@
/**
* @return {@link Rect} of the destination PiP window bounds.
*/
- Rect getDestinationBounds(float aspectRatio, Rect bounds) {
+ Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) {
final Rect destinationBounds;
final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
if (bounds == null) {
destinationBounds = new Rect(defaultBounds);
+ if (mReentrySnapFraction == INVALID_SNAP_FRACTION && mReentrySize == null) {
+ mOverrideMinimalSize = minimalSize;
+ }
} else {
destinationBounds = new Rect(bounds);
}
@@ -335,7 +339,6 @@
*/
private void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
boolean useCurrentMinEdgeSize) {
-
// Save the snap fraction and adjust the size based on the new aspect ratio.
final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
getMovementBounds(stackBounds));
@@ -354,10 +357,38 @@
final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
+ // apply the override minimal size if applicable, this minimal size is specified by app
+ if (mOverrideMinimalSize != null) {
+ transformBoundsToMinimalSize(stackBounds, aspectRatio, mOverrideMinimalSize);
+ }
mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
}
/**
+ * Transforms a given bounds to meet the minimal size constraints.
+ * This function assumes the given {@param stackBounds} qualifies {@param aspectRatio}.
+ */
+ private void transformBoundsToMinimalSize(Rect stackBounds, float aspectRatio,
+ Size minimalSize) {
+ if (minimalSize == null) return;
+ final Size adjustedMinimalSize;
+ final float minimalSizeAspectRatio =
+ minimalSize.getWidth() / (float) minimalSize.getHeight();
+ if (minimalSizeAspectRatio > aspectRatio) {
+ // minimal size is wider, fixed the width and increase the height
+ adjustedMinimalSize = new Size(
+ minimalSize.getWidth(), (int) (minimalSize.getWidth() / aspectRatio));
+ } else {
+ adjustedMinimalSize = new Size(
+ (int) (minimalSize.getHeight() * aspectRatio), minimalSize.getHeight());
+ }
+ final Rect containerBounds = new Rect(stackBounds);
+ Gravity.apply(mDefaultStackGravity,
+ adjustedMinimalSize.getWidth(), adjustedMinimalSize.getHeight(),
+ containerBounds, stackBounds);
+ }
+
+ /**
* @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
* provided, then it will apply the default bounds to the provided snap fraction and size.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 4a4a638..dc1b5d7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -30,11 +30,14 @@
import android.app.ITaskOrganizerController;
import android.app.PictureInPictureParams;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Size;
import android.view.ITaskOrganizer;
import android.view.IWindowContainer;
import android.view.SurfaceControl;
@@ -45,7 +48,9 @@
import com.android.systemui.pip.phone.PipUpdateThread;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
@@ -77,6 +82,7 @@
private final Rect mLastReportedBounds = new Rect();
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+ private final Map<IBinder, Rect> mBoundsToRestore = new HashMap<>();
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -204,26 +210,12 @@
mOneShotAnimationType = animationType;
}
- /**
- * Callback to issue the final {@link WindowContainerTransaction} on end of movements.
- * @param destinationBounds the final bounds.
- */
- public void onMotionMovementEnd(Rect destinationBounds) {
- try {
- mLastReportedBounds.set(destinationBounds);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mToken, destinationBounds);
- mTaskOrganizerController.applyContainerTransaction(wct, null /* ITaskOrganizer */);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to apply window container transaction", e);
- }
- }
-
@Override
public void taskAppeared(ActivityManager.RunningTaskInfo info) {
Objects.requireNonNull(info, "Requires RunningTaskInfo");
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */);
+ getAspectRatioOrDefault(info.pictureInPictureParams),
+ null /* bounds */, getMinimalSize(info.topActivityInfo));
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
mTaskInfo = info;
mToken = mTaskInfo.token;
@@ -234,6 +226,7 @@
throw new RuntimeException("Unable to get leash", e);
}
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
+ mBoundsToRestore.put(mToken.asBinder(), currentBounds);
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
scheduleAnimateResizePip(currentBounds, destinationBounds,
TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null);
@@ -258,8 +251,8 @@
Log.wtf(TAG, "Unrecognized token: " + token);
return;
}
- scheduleAnimateResizePip(mLastReportedBounds,
- info.configuration.windowConfiguration.getBounds(),
+ final Rect boundsToRestore = mBoundsToRestore.remove(token.asBinder());
+ scheduleAnimateResizePip(mLastReportedBounds, boundsToRestore,
TRANSITION_DIRECTION_TO_FULLSCREEN, mEnterExitAnimationDuration, null);
mInPip = false;
}
@@ -276,7 +269,8 @@
return;
}
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- getAspectRatioOrDefault(newParams), null /* bounds */);
+ getAspectRatioOrDefault(newParams),
+ null /* bounds */, getMinimalSize(info.topActivityInfo));
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, null);
}
@@ -446,6 +440,14 @@
.start());
}
+ private Size getMinimalSize(ActivityInfo activityInfo) {
+ if (activityInfo == null || activityInfo.windowLayout == null) {
+ return null;
+ }
+ final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
+ return new Size(windowLayout.minWidth, windowLayout.minHeight);
+ }
+
private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
return params == null
? mPipBoundsHandler.getDefaultAspectRatio()
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 15a0088..449a2bc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -426,7 +426,7 @@
cancelAnimations();
mAnimatedBoundsPhysicsAnimator
- .withEndActions(() -> mPipTaskOrganizer.onMotionMovementEnd(mAnimatedBounds))
+ .withEndActions(() -> mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds))
.addUpdateListener(mResizePipUpdateListener)
.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index ea5ec05..d7eab3a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -31,7 +31,6 @@
import android.os.Handler;
import android.os.RemoteException;
import android.provider.Settings;
-import android.util.Log;
import android.util.Slog;
import android.view.IWindowContainer;
import android.view.LayoutInflater;
@@ -73,7 +72,7 @@
DisplayController.OnDisplaysChangedListener {
private static final String TAG = "Divider";
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
static final int DEFAULT_APP_TRANSITION_DURATION = 336;
static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
@@ -173,6 +172,9 @@
@Nullable
private ValueAnimator mAnimation = null;
+ private boolean mPaused = true;
+ private boolean mPausedTargetAdjusted = false;
+
private boolean getSecondaryHasFocus(int displayId) {
try {
IWindowContainer imeSplit = ActivityTaskManager.getTaskOrganizerController()
@@ -185,6 +187,14 @@
return false;
}
+ private void updateDimTargets() {
+ final boolean splitIsVisible = !mView.isHidden();
+ mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
+ ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
+ ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ }
+
@Override
public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
boolean imeShouldShow, SurfaceControl.Transaction t) {
@@ -193,18 +203,31 @@
}
final boolean splitIsVisible = !mView.isHidden();
mSecondaryHasFocus = getSecondaryHasFocus(displayId);
- mTargetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
+ final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
&& !mSplitLayout.mDisplayLayout.isLandscape();
mHiddenTop = hiddenTop;
mShownTop = shownTop;
mTargetShown = imeShouldShow;
if (mLastAdjustTop < 0) {
mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
+ } else {
+ // Check for an "interruption" of an existing animation. In this case, we need to
+ // fake-flip the last-known state direction so that the animation completes in the
+ // other direction.
+ if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
+ if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
+ mAdjusted = mTargetAdjusted;
+ }
+ }
}
- mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ if (mPaused) {
+ mPausedTargetAdjusted = targetAdjusted;
+ if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
+ return;
+ }
+ mTargetAdjusted = targetAdjusted;
+ updateDimTargets();
+ if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState());
if (mAnimation != null || (mImeWasShown && imeShouldShow
&& mTargetAdjusted != mAdjusted)) {
// We need to animate adjustment independently of the IME position, so
@@ -259,7 +282,7 @@
@Override
public void onImePositionChanged(int displayId, int imeTop,
SurfaceControl.Transaction t) {
- if (mAnimation != null || !inSplitMode()) {
+ if (mAnimation != null || !inSplitMode() || mPaused) {
// Not synchronized with IME anymore, so return.
return;
}
@@ -271,7 +294,7 @@
@Override
public void onImeEndPositioning(int displayId, boolean cancelled,
SurfaceControl.Transaction t) {
- if (mAnimation != null || !inSplitMode()) {
+ if (mAnimation != null || !inSplitMode() || mPaused) {
// Not synchronized with IME anymore, so return.
return;
}
@@ -279,7 +302,7 @@
}
private void onProgress(float progress, SurfaceControl.Transaction t) {
- if (mTargetAdjusted != mAdjusted) {
+ if (mTargetAdjusted != mAdjusted && !mPaused) {
final float fraction = mTargetAdjusted ? progress : 1.f - progress;
mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
@@ -342,6 +365,52 @@
});
mAnimation.start();
}
+
+ private String dumpState() {
+ return "top:" + mHiddenTop + "->" + mShownTop
+ + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
+ + " shw:" + mImeWasShown + "->" + mTargetShown
+ + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
+ + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
+ + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
+ + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
+ }
+
+ /** Completely aborts/resets adjustment state */
+ public void pause(int displayId) {
+ if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
+ mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
+ if (mPaused) {
+ return;
+ }
+ mPaused = true;
+ mPausedTargetAdjusted = mTargetAdjusted;
+ mTargetAdjusted = false;
+ mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
+ updateImeAdjustState();
+ startAsyncAnimation();
+ });
+ }
+
+ public void resume(int displayId) {
+ if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
+ mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
+ if (!mPaused) {
+ return;
+ }
+ mPaused = false;
+ mTargetAdjusted = mPausedTargetAdjusted;
+ updateDimTargets();
+ if ((mTargetAdjusted != mAdjusted) && !mMinimized && mView != null) {
+ // End unminimize animations since they conflict with adjustment animations.
+ mView.finishAnimations();
+ }
+ updateImeAdjustState();
+ startAsyncAnimation();
+ });
+ }
}
private final DividerImeController mImePositionProcessor = new DividerImeController();
@@ -493,6 +562,7 @@
}
void updateVisibility(final boolean visible) {
+ if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
if (mVisible != visible) {
mVisible = visible;
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
@@ -519,12 +589,21 @@
/** Switch to minimized state if appropriate */
public void setMinimized(final boolean minimized) {
+ if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
+ if (!mVisible) {
+ return;
+ }
setHomeMinimized(minimized, mHomeStackResizable);
});
}
private void setHomeMinimized(final boolean minimized, boolean homeStackResizable) {
+ if (DEBUG) {
+ Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
+ + mHomeStackResizable + "->" + homeStackResizable + " split:" + inSplitMode());
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
// Update minimized state
if (mMinimized != minimized) {
@@ -544,7 +623,17 @@
// Sync state to DividerView if it exists.
if (mView != null) {
+ final int displayId = mView.getDisplay() != null
+ ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY;
+ // pause ime here (before updateMinimizedDockedStack)
+ if (mMinimized) {
+ mImePositionProcessor.pause(displayId);
+ }
mView.setMinimizedDockStack(minimized, getAnimDuration(), homeStackResizable);
+ if (!mMinimized) {
+ // afterwards so it can end any animations started in view
+ mImePositionProcessor.resume(displayId);
+ }
}
updateTouchable();
WindowManagerProxy.applyContainerTransaction(wct);
@@ -649,7 +738,7 @@
if (!inSplitMode()) {
// Wasn't in split-mode yet, so enter now.
if (DEBUG) {
- Log.d(TAG, " entering split mode with minimized=true");
+ Slog.d(TAG, " entering split mode with minimized=true");
}
updateVisibility(true /* visible */);
}
@@ -660,7 +749,7 @@
if (!inSplitMode()) {
// Wasn't in split-mode, so enter now.
if (DEBUG) {
- Log.d(TAG, " enter split mode unminimized ");
+ Slog.d(TAG, " enter split mode unminimized ");
}
updateVisibility(true /* visible */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 4114bb9..131f4e1 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -34,6 +34,7 @@
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Slog;
import android.view.Choreographer;
import android.view.Display;
import android.view.InsetsState;
@@ -67,12 +68,15 @@
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import java.util.function.Consumer;
+
/**
* Docked stack divider.
*/
public class DividerView extends FrameLayout implements OnTouchListener,
OnComputeInternalInsetsListener {
private static final String TAG = "DividerView";
+ private static final boolean DEBUG = Divider.DEBUG;
public interface DividerCallbacks {
void onDraggingStart();
@@ -627,6 +631,7 @@
cancelFlingAnimation();
updateDockSide();
}
+ if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position);
final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
@@ -634,16 +639,21 @@
? TASK_POSITION_SAME
: snapTarget.taskPosition,
snapTarget));
- Runnable endAction = () -> {
+ Consumer<Boolean> endAction = cancelled -> {
+ if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction);
+ final boolean wasMinimizeInteraction = mIsInMinimizeInteraction;
+ // Reset minimized divider position after unminimized state animation finishes.
+ if (!cancelled && !mDockedStackMinimized && mIsInMinimizeInteraction) {
+ mIsInMinimizeInteraction = false;
+ }
boolean dismissed = commitSnapFlags(snapTarget);
mWindowManagerProxy.setResizing(false);
updateDockSide();
mCurrentAnimator = null;
mEntranceAnimationRunning = false;
mExitAnimationRunning = false;
- if (!dismissed) {
- WindowManagerProxy.applyResizeSplits((mIsInMinimizeInteraction
- ? mSnapTargetBeforeMinimized : snapTarget).position, mSplitLayout);
+ if (!dismissed && !wasMinimizeInteraction) {
+ WindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
}
if (mCallback != null) {
mCallback.onDraggingEnd();
@@ -667,12 +677,6 @@
}
}
};
- Runnable notCancelledEndAction = () -> {
- // Reset minimized divider position after unminimized state animation finishes
- if (!mDockedStackMinimized && mIsInMinimizeInteraction) {
- mIsInMinimizeInteraction = false;
- }
- };
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -694,15 +698,11 @@
delay = mSfChoreographer.getSurfaceFlingerOffsetMs();
}
if (delay == 0) {
- if (!mCancelled) {
- notCancelledEndAction.run();
- }
- endAction.run();
+ endAction.accept(mCancelled);
} else {
- if (!mCancelled) {
- mHandler.postDelayed(notCancelledEndAction, delay);
- }
- mHandler.postDelayed(endAction, delay);
+ final Boolean cancelled = mCancelled;
+ if (DEBUG) Slog.d(TAG, "Posting endFling " + cancelled + " d:" + delay + "ms");
+ mHandler.postDelayed(() -> endAction.accept(cancelled), delay);
}
}
});
@@ -899,6 +899,7 @@
public void setMinimizedDockStack(boolean minimized, long animDuration,
boolean isHomeStackResizable) {
+ if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized);
mHomeStackResizable = isHomeStackResizable;
updateDockSide();
if (!isHomeStackResizable) {
@@ -946,6 +947,15 @@
.start();
}
+ // Needed to end any currently playing animations when they might compete with other anims
+ // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe
+ // these can be unified, but not today.
+ void finishAnimations() {
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.end();
+ }
+ }
+
public void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
if (mAdjustedForIme == adjustedForIme) {
return;
@@ -1061,6 +1071,11 @@
mDividerPositionX = dockedRect.right;
mDividerPositionY = dockedRect.bottom;
+ if (DEBUG) {
+ Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect
+ + " " + otherRect + " " + otherTaskRect);
+ }
+
t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
Rect crop = new Rect(dockedRect);
crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index 4597b16..b33424c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -33,6 +33,7 @@
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;
import java.util.List;
@@ -159,20 +160,30 @@
}
public void setEntry(NotificationEntry entry) {
- if (entry != null) {
- mShowingEntry = entry;
+ if (mShowingEntry != null) {
+ mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener);
+ }
+ mShowingEntry = entry;
+
+ if (mShowingEntry != null) {
CharSequence text = entry.headsUpStatusBarText;
if (entry.isSensitive()) {
text = entry.headsUpStatusBarTextPublic;
}
mTextView.setText(text);
- mShowingEntry.setOnSensitiveChangedListener(() -> setEntry(entry));
- } else if (mShowingEntry != null){
- mShowingEntry.setOnSensitiveChangedListener(null);
- mShowingEntry = null;
+ mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
}
}
+ private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> {
+ if (entry != mShowingEntry) {
+ throw new IllegalStateException("Got a sensitivity change for " + entry
+ + " but mShowingEntry is " + mShowingEntry);
+ }
+ // Update the text
+ setEntry(entry);
+ };
+
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 047edd2..72d9d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -206,7 +206,8 @@
false,
false,
false,
- null
+ null,
+ false
);
}
return ranking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 87be739..d8fdf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -70,6 +70,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -262,11 +263,11 @@
synchronized (mEntryManager) {
NotificationEntry entry = mEntryManager
.getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry == null || entry.expandedIcon == null) {
+ if (entry == null || entry.getIcons().getShelfIcon() == null) {
return null;
}
- return entry.expandedIcon.getSourceIcon();
+ return entry.getIcons().getShelfIcon().getSourceIcon();
}
}
@@ -284,8 +285,7 @@
boolean metaDataChanged = false;
synchronized (mEntryManager) {
- Set<NotificationEntry> allNotifications =
- mEntryManager.getPendingAndActiveNotifications();
+ Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
// Promote the media notification with a controller in 'playing' state, if any.
NotificationEntry mediaNotification = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index eb8526d..8945f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -22,6 +22,7 @@
import android.app.WallpaperManager
import android.view.Choreographer
import android.view.View
+import androidx.annotation.VisibleForTesting
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
@@ -29,8 +30,10 @@
import com.android.systemui.Dumpable
import com.android.systemui.Interpolators
import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.PanelExpansionListener
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.FileDescriptor
@@ -44,17 +47,17 @@
*/
@Singleton
class NotificationShadeDepthController @Inject constructor(
- private val statusBarStateController: SysuiStatusBarStateController,
+ private val statusBarStateController: StatusBarStateController,
private val blurUtils: BlurUtils,
private val biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
private val choreographer: Choreographer,
private val wallpaperManager: WallpaperManager,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
dumpManager: DumpManager
) : PanelExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
- private const val SHADE_BLUR_ENABLED = true
}
lateinit var root: View
@@ -62,8 +65,9 @@
private var keyguardAnimator: Animator? = null
private var notificationAnimator: Animator? = null
private var updateScheduled: Boolean = false
- private var shadeExpansion = 1.0f
- private val shadeSpring = SpringAnimation(this, object :
+ private var shadeExpansion = 0f
+ @VisibleForTesting
+ var shadeSpring = SpringAnimation(this, object :
FloatPropertyCompat<NotificationShadeDepthController>("shadeBlurRadius") {
override fun setValue(rect: NotificationShadeDepthController?, value: Float) {
shadeBlurRadius = value.toInt()
@@ -74,12 +78,25 @@
}
})
private val zoomInterpolator = Interpolators.ACCELERATE_DECELERATE
+
+ /**
+ * Radius that we're animating to.
+ */
+ private var pendingShadeBlurRadius = -1
+
+ /**
+ * Shade blur radius on the current frame.
+ */
private var shadeBlurRadius = 0
set(value) {
if (field == value) return
field = value
scheduleUpdate()
}
+
+ /**
+ * Blur radius of the wake-up animation on this frame.
+ */
private var wakeAndUnlockBlurRadius = 0
set(value) {
if (field == value) return
@@ -100,6 +117,7 @@
val rawZoom = max(blurUtils.ratioOfBlurRadius(blur), globalDialogVisibility)
wallpaperManager.setWallpaperZoomOut(root.windowToken,
zoomInterpolator.getInterpolation(rawZoom))
+ notificationShadeWindowController.setBackgroundBlurRadius(blur)
}
/**
@@ -139,6 +157,18 @@
}
}
+ private val statusBarStateCallback = object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ updateShadeBlur()
+ }
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ if (isDozing && shadeSpring.isRunning) {
+ shadeSpring.skipToEnd()
+ }
+ }
+ }
+
init {
dumpManager.registerDumpable(javaClass.name, this)
if (WAKE_UP_ANIMATION_ENABLED) {
@@ -147,24 +177,31 @@
shadeSpring.spring = SpringForce(0.0f)
shadeSpring.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
shadeSpring.spring.stiffness = SpringForce.STIFFNESS_LOW
+ shadeSpring.addEndListener { _, _, _, _ -> pendingShadeBlurRadius = -1 }
+ statusBarStateController.addCallback(statusBarStateCallback)
}
/**
* Update blurs when pulling down the shade
*/
override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
- if (!SHADE_BLUR_ENABLED) {
+ if (expansion == shadeExpansion) {
return
}
+ shadeExpansion = expansion
+ updateShadeBlur()
+ }
+ private fun updateShadeBlur() {
var newBlur = 0
if (statusBarStateController.state == StatusBarState.SHADE) {
- newBlur = blurUtils.blurRadiusOfRatio(expansion)
+ newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
}
- if (shadeBlurRadius == newBlur) {
+ if (pendingShadeBlurRadius == newBlur) {
return
}
+ pendingShadeBlurRadius = newBlur
shadeSpring.animateToFinalPosition(newBlur.toFloat())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1a8454c..d7f2ae4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -329,7 +329,7 @@
expandableRow.setAboveShelf(false);
}
if (notGoneIndex == 0) {
- StatusBarIconView icon = expandableRow.getEntry().expandedIcon;
+ StatusBarIconView icon = expandableRow.getEntry().getIcons().getShelfIcon();
NotificationIconContainer.IconState iconState = getIconState(icon);
// The icon state might be null in rare cases where the notification is actually
// added to the layout, but not to the shelf. An example are replied messages,
@@ -432,7 +432,7 @@
// if the shelf is clipped, lets make sure we also clip the icon
maxTop = Math.max(maxTop, getTranslationY() + getClipTopAmount());
}
- StatusBarIconView icon = row.getEntry().expandedIcon;
+ StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
if (shelfIconPosition < maxTop && !mAmbientState.isFullyHidden()) {
int top = (int) (maxTop - shelfIconPosition);
@@ -444,7 +444,7 @@
}
private void updateContinuousClipping(final ExpandableNotificationRow row) {
- StatusBarIconView icon = row.getEntry().expandedIcon;
+ StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDozing();
boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
if (needsContinuousClipping && !isContinuousClipping) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 7f0479c..c6d84ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -29,6 +29,7 @@
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -56,9 +57,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -105,6 +106,10 @@
*/
public static final int UNDEFINED_DISMISS_REASON = 0;
+ private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
+ private final Set<NotificationEntry> mReadOnlyAllNotifications =
+ Collections.unmodifiableSet(mAllNotifications);
+
/** Pending notifications are ones awaiting inflation */
@VisibleForTesting
protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
@@ -468,6 +473,8 @@
entry.removeRow();
}
+ mAllNotifications.remove(entry);
+
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key);
removeVisibleNotification(key);
@@ -548,6 +555,7 @@
notification,
ranking,
mFgsFeatureController.isForegroundServiceDismissalEnabled());
+ mAllNotifications.add(entry);
mLeakDetector.trackInstance(entry);
@@ -709,15 +717,6 @@
}
/**
- * @return all notifications we're currently aware of (both pending and active notifications)
- */
- public Set<NotificationEntry> getPendingAndActiveNotifications() {
- Set<NotificationEntry> allNotifs = new HashSet<>(mPendingNotifications.values());
- allNotifs.addAll(mSortedAndFiltered);
- return allNotifs;
- }
-
- /**
* Use this method to retrieve a notification entry that has been prepared for presentation.
* Note that the notification may be filtered out and never shown to the user.
*
@@ -842,7 +841,7 @@
private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) {
pw.print(indent);
- pw.println(" [" + i + "] key=" + e.getKey() + " icon=" + e.icon);
+ pw.println(" [" + i + "] key=" + e.getKey() + " icon=" + e.getIcons().getStatusBarIcon());
StatusBarNotification n = e.getSbn();
pw.print(indent);
pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance="
@@ -861,6 +860,15 @@
return mReadOnlyNotifications;
}
+ /**
+ * Returns a collections containing ALL notifications we know about, including ones that are
+ * hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}.
+ */
+ @Override
+ public Collection<NotificationEntry> getAllNotifs() {
+ return mReadOnlyAllNotifications;
+ }
+
/** @return A count of the active notifications */
public int getActiveNotificationsCount() {
return mReadOnlyNotifications.size();
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 749e5e2..b90cfa8 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
@@ -64,24 +64,34 @@
import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CleanUpEntryEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.EntryAddedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.EntryRemovedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.EntryUpdatedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InitEntryEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.notifcollection.RankingAppliedEvent;
+import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
import com.android.systemui.util.Assert;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Queue;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -124,6 +134,8 @@
private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
+ private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
+
private boolean mAttached = false;
private boolean mAmDispatchingToOtherCode;
@@ -160,8 +172,8 @@
mBuildListener = buildListener;
}
- /** @see NotifPipeline#getActiveNotifs() */
- Collection<NotificationEntry> getActiveNotifs() {
+ /** @see NotifPipeline#getAllNotifs() */
+ Collection<NotificationEntry> getAllNotifs() {
Assert.isMainThread();
return mReadOnlyNotificationSet;
}
@@ -242,7 +254,7 @@
}
locallyDismissNotifications(entriesToLocallyDismiss);
- rebuildList();
+ dispatchEventsAndRebuildList();
}
/**
@@ -251,8 +263,7 @@
public void dismissNotification(
NotificationEntry entry,
@NonNull DismissedByUserStats stats) {
- dismissNotifications(List.of(
- new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+ dismissNotifications(List.of(new Pair<>(entry, stats)));
}
/**
@@ -268,7 +279,7 @@
// system process is dead if we're here.
}
- final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+ final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
for (int i = entries.size() - 1; i >= 0; i--) {
NotificationEntry entry = entries.get(i);
if (!shouldDismissOnClearAll(entry, userId)) {
@@ -283,7 +294,7 @@
}
locallyDismissNotifications(entries);
- rebuildList();
+ dispatchEventsAndRebuildList();
}
/**
@@ -326,8 +337,9 @@
private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
Assert.isMainThread();
- postNotification(sbn, requireRanking(rankingMap, sbn.getKey()), rankingMap);
- rebuildList();
+ postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
+ applyRanking(rankingMap);
+ dispatchEventsAndRebuildList();
}
private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
@@ -336,9 +348,9 @@
mLogger.logNotifGroupPosted(batch.get(0).getSbn().getGroupKey(), batch.size());
for (CoalescedEvent event : batch) {
- postNotification(event.getSbn(), event.getRanking(), null);
+ postNotification(event.getSbn(), event.getRanking());
}
- rebuildList();
+ dispatchEventsAndRebuildList();
}
private void onNotificationRemoved(
@@ -354,55 +366,49 @@
throw new IllegalStateException("No notification to remove with key " + sbn.getKey());
}
entry.mCancellationReason = reason;
- applyRanking(rankingMap);
tryRemoveNotification(entry);
- rebuildList();
+ applyRanking(rankingMap);
+ dispatchEventsAndRebuildList();
}
private void onNotificationRankingUpdate(RankingMap rankingMap) {
Assert.isMainThread();
+ mEventQueue.add(new RankingUpdatedEvent(rankingMap));
applyRanking(rankingMap);
- dispatchNotificationRankingUpdate(rankingMap);
- rebuildList();
+ dispatchEventsAndRebuildList();
}
private void postNotification(
StatusBarNotification sbn,
- Ranking ranking,
- @Nullable RankingMap rankingMap) {
+ Ranking ranking) {
NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
// A new notification!
- mLogger.logNotifPosted(sbn.getKey());
-
entry = new NotificationEntry(sbn, ranking);
mNotificationSet.put(sbn.getKey(), entry);
- dispatchOnEntryInit(entry);
- if (rankingMap != null) {
- applyRanking(rankingMap);
- }
-
- dispatchOnEntryAdded(entry);
+ mLogger.logNotifPosted(sbn.getKey());
+ mEventQueue.add(new InitEntryEvent(entry));
+ mEventQueue.add(new EntryAddedEvent(entry));
} else {
// Update to an existing entry
- mLogger.logNotifUpdated(sbn.getKey());
// Notification is updated so it is essentially re-added and thus alive again, so we
// can reset its state.
+ // TODO: If a coalesced event ever gets here, it's possible to lose track of children,
+ // since their rankings might have been updated earlier (and thus we may no longer
+ // think a child is associated with this locally-dismissed entry).
cancelLocalDismissal(entry);
cancelLifetimeExtension(entry);
cancelDismissInterception(entry);
entry.mCancellationReason = REASON_NOT_CANCELED;
entry.setSbn(sbn);
- if (rankingMap != null) {
- applyRanking(rankingMap);
- }
- dispatchOnEntryUpdated(entry);
+ mLogger.logNotifUpdated(sbn.getKey());
+ mEventQueue.add(new EntryUpdatedEvent(entry));
}
}
@@ -432,8 +438,8 @@
if (!isLifetimeExtended(entry)) {
mNotificationSet.remove(entry.getKey());
cancelDismissInterception(entry);
- dispatchOnEntryRemoved(entry, entry.mCancellationReason);
- dispatchOnEntryCleanUp(entry);
+ mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason));
+ mEventQueue.add(new CleanUpEntryEvent(entry));
return true;
} else {
return false;
@@ -466,9 +472,16 @@
}
}
}
+ mEventQueue.add(new RankingAppliedEvent());
}
- private void rebuildList() {
+ private void dispatchEventsAndRebuildList() {
+ mAmDispatchingToOtherCode = true;
+ while (!mEventQueue.isEmpty()) {
+ mEventQueue.remove().dispatchTo(mNotifCollectionListeners);
+ }
+ mAmDispatchingToOtherCode = false;
+
if (mBuildListener != null) {
mBuildListener.onBuildList(mReadOnlyNotificationSet);
}
@@ -491,7 +504,7 @@
if (!isLifetimeExtended(entry)) {
if (tryRemoveNotification(entry)) {
- rebuildList();
+ dispatchEventsAndRebuildList();
}
}
}
@@ -660,57 +673,9 @@
|| entry.getSbn().getUser().getIdentifier() == userId;
}
- private void dispatchOnEntryInit(NotificationEntry entry) {
- mAmDispatchingToOtherCode = true;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryInit(entry);
- }
- mAmDispatchingToOtherCode = false;
- }
-
- private void dispatchOnEntryAdded(NotificationEntry entry) {
- mAmDispatchingToOtherCode = true;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryAdded(entry);
- }
- mAmDispatchingToOtherCode = false;
- }
-
- private void dispatchOnEntryUpdated(NotificationEntry entry) {
- mAmDispatchingToOtherCode = true;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryUpdated(entry);
- }
- mAmDispatchingToOtherCode = false;
- }
-
- private void dispatchNotificationRankingUpdate(RankingMap map) {
- mAmDispatchingToOtherCode = true;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onRankingUpdate(map);
- }
- mAmDispatchingToOtherCode = false;
- }
-
- private void dispatchOnEntryRemoved(NotificationEntry entry, @CancellationReason int reason) {
- mAmDispatchingToOtherCode = true;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryRemoved(entry, reason);
- }
- mAmDispatchingToOtherCode = false;
- }
-
- private void dispatchOnEntryCleanUp(NotificationEntry entry) {
- mAmDispatchingToOtherCode = true;
- for (NotifCollectionListener listener : mNotifCollectionListeners) {
- listener.onEntryCleanUp(entry);
- }
- mAmDispatchingToOtherCode = false;
- }
-
@Override
public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
- final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+ final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
if (entries.size() == 0) {
@@ -754,6 +719,7 @@
private static final String TAG = "NotifCollection";
@IntDef(prefix = { "REASON_" }, value = {
+ REASON_NOT_CANCELED,
REASON_UNKNOWN,
REASON_CLICK,
REASON_CANCEL_ALL,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 14903cd..17899e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -82,14 +82,14 @@
}
/**
- * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+ * Returns the list of all known notifications, i.e. the notifications that are currently posted
* to the phone. In general, this tracks closely to the list maintained by NotificationManager,
* but it can diverge slightly due to lifetime extenders.
*
* The returned collection is read-only, unsorted, unfiltered, and ungrouped.
*/
- public Collection<NotificationEntry> getActiveNotifs() {
- return mNotifCollection.getActiveNotifs();
+ public Collection<NotificationEntry> getAllNotifs() {
+ return mNotifCollection.getAllNotifs();
}
@Override
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 3e9d8a4..7019b5b 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,8 +29,6 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
@@ -44,10 +42,7 @@
import android.app.Person;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
@@ -55,25 +50,20 @@
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ArrayUtils;
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.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -81,8 +71,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -102,15 +90,11 @@
* clean this up in the future.
*/
public final class NotificationEntry extends ListEntry {
- private static final String TAG = "NotificationEntry";
private final String mKey;
private StatusBarNotification mSbn;
private Ranking mRanking;
- private StatusBarIcon mSmallIcon;
- private StatusBarIcon mPeopleAvatar;
-
/*
* Bookkeeping members
*/
@@ -142,10 +126,7 @@
* TODO: Remove every member beneath this line if possible
*/
- public StatusBarIconView icon;
- public StatusBarIconView expandedIcon;
- public StatusBarIconView centeredIcon;
- public StatusBarIconView aodIcon;
+ private IconPack mIcons = IconPack.buildEmptyPack(null);
private boolean interruption;
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
@@ -191,7 +172,8 @@
private boolean hasSentReply;
private boolean mSensitive = true;
- private Runnable mOnSensitiveChangedListener;
+ private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>();
+
private boolean mAutoHeadsUp;
private boolean mPulseSupressed;
private boolean mAllowFgsDismissal;
@@ -347,6 +329,15 @@
* TODO: Remove as many of these as possible
*/
+ @NonNull
+ public IconPack getIcons() {
+ return mIcons;
+ }
+
+ public void setIcons(@NonNull IconPack icons) {
+ mIcons = icons;
+ }
+
public void setInterruption() {
interruption = true;
}
@@ -464,239 +455,6 @@
|| SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
}
- /**
- * Create the icons for a notification
- * @param context the context to create the icons with
- * @param sbn the notification
- * @throws InflationException Exception if required icons are not valid or specified
- */
- public void createIcons(Context context, StatusBarNotification sbn)
- throws InflationException {
- StatusBarIcon ic = getIcon(context, sbn, false /* redact */);
-
- // Construct the icon.
- icon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
- // Construct the expanded icon.
- expandedIcon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
- // Construct the expanded icon.
- aodIcon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- aodIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- aodIcon.setIncreasedSize(true);
-
- try {
- setIcons(ic, Collections.singletonList(icon));
- if (isSensitive()) {
- ic = getIcon(context, sbn, true /* redact */);
- }
- setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
- } catch (InflationException e) {
- icon = null;
- expandedIcon = null;
- centeredIcon = null;
- aodIcon = null;
- throw e;
- }
-
- expandedIcon.setVisibility(View.INVISIBLE);
- expandedIcon.setOnVisibilityChangedListener(
- newVisibility -> {
- if (row != null) {
- row.setIconsVisible(newVisibility != View.VISIBLE);
- }
- });
-
- // Construct the centered icon
- if (mSbn.getNotification().isMediaNotification()) {
- centeredIcon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- centeredIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- try {
- setIcons(ic, Collections.singletonList(centeredIcon));
- } catch (InflationException e) {
- centeredIcon = null;
- throw e;
- }
- }
- }
-
- /**
- * Determines if this icon should be tinted based on the sensitivity of the icon, its context
- * and the user's indicated sensitivity preference.
- *
- * @param ic The icon that should/should not be tinted.
- * @return
- */
- private boolean shouldTintIcon(StatusBarIconView ic) {
- boolean usedInSensitiveContext = (ic == expandedIcon || ic == aodIcon);
- return !isImportantConversation() || (usedInSensitiveContext && isSensitive());
- }
-
-
- private void setIcons(StatusBarIcon ic, List<StatusBarIconView> icons)
- throws InflationException {
- for (StatusBarIconView icon: icons) {
- if (icon == null) {
- continue;
- }
- icon.setTintIcons(shouldTintIcon(icon));
- if (!icon.set(ic)) {
- throw new InflationException("Couldn't create icon" + ic);
- }
- }
- }
-
- private StatusBarIcon getIcon(Context context, StatusBarNotification sbn, boolean redact)
- throws InflationException {
- Notification n = sbn.getNotification();
- final boolean showPeopleAvatar = isImportantConversation() && !redact;
-
- // If cached, return corresponding cached values
- if (showPeopleAvatar && mPeopleAvatar != null) {
- return mPeopleAvatar;
- } else if (!showPeopleAvatar && mSmallIcon != null) {
- return mSmallIcon;
- }
-
- Icon icon = showPeopleAvatar ? createPeopleAvatar(context) : n.getSmallIcon();
- if (icon == null) {
- throw new InflationException("No icon in notification from " + sbn.getPackageName());
- }
-
- StatusBarIcon ic = new StatusBarIcon(
- sbn.getUser(),
- sbn.getPackageName(),
- icon,
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(context, n));
-
- // Cache if important conversation.
- if (isImportantConversation()) {
- if (showPeopleAvatar) {
- mPeopleAvatar = ic;
- } else {
- mSmallIcon = ic;
- }
- }
- return ic;
- }
-
- private Icon createPeopleAvatar(Context context) throws InflationException {
- // Attempt to extract form shortcut.
- String conversationId = getChannel().getConversationId();
- ShortcutQuery query = new ShortcutQuery()
- .setPackage(mSbn.getPackageName())
- .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
- .setShortcutIds(Collections.singletonList(conversationId));
- List<ShortcutInfo> shortcuts = context.getSystemService(LauncherApps.class)
- .getShortcuts(query, mSbn.getUser());
- Icon ic = null;
- if (shortcuts != null && !shortcuts.isEmpty()) {
- ic = shortcuts.get(0).getIcon();
- }
-
- // Fall back to notification large icon if available
- if (ic == null) {
- ic = mSbn.getNotification().getLargeIcon();
- }
-
- // Fall back to extract from message
- if (ic == null) {
- Bundle extras = mSbn.getNotification().extras;
- List<Message> messages = Message.getMessagesFromBundleArray(
- extras.getParcelableArray(Notification.EXTRA_MESSAGES));
- Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
-
- for (int i = messages.size() - 1; i >= 0; i--) {
- Message message = messages.get(i);
- Person sender = message.getSenderPerson();
- if (sender != null && sender != user) {
- ic = message.getSenderPerson().getIcon();
- break;
- }
- }
- }
-
- // Revert to small icon if still not available
- if (ic == null) {
- ic = mSbn.getNotification().getSmallIcon();
- }
- if (ic == null) {
- throw new InflationException("No icon in notification from " + mSbn.getPackageName());
- }
- return ic;
- }
-
- private void updateSensitiveIconState() {
- try {
- StatusBarIcon ic = getIcon(getRow().getContext(), mSbn, isSensitive());
- setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
- } catch (InflationException e) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Unable to update icon", e);
- }
- }
- }
-
- public void setIconTag(int key, Object tag) {
- if (icon != null) {
- icon.setTag(key, tag);
- expandedIcon.setTag(key, tag);
- }
-
- if (centeredIcon != null) {
- centeredIcon.setTag(key, tag);
- }
-
- if (aodIcon != null) {
- aodIcon.setTag(key, tag);
- }
- }
-
- /**
- * Update the notification icons.
- *
- * @param context the context to create the icons with.
- * @param sbn the notification to read the icon from.
- * @throws InflationException Exception if required icons are not valid or specified
- */
- public void updateIcons(Context context, StatusBarNotification sbn)
- throws InflationException {
- if (icon != null) {
- // Update the icon
- mSmallIcon = null;
- mPeopleAvatar = null;
-
- StatusBarIcon ic = getIcon(context, sbn, false /* redact */);
-
- icon.setNotification(sbn);
- expandedIcon.setNotification(sbn);
- aodIcon.setNotification(sbn);
- setIcons(ic, Arrays.asList(icon, expandedIcon));
-
- if (isSensitive()) {
- ic = getIcon(context, sbn, true /* redact */);
- }
- setIcons(ic, Collections.singletonList(aodIcon));
-
- if (centeredIcon != null) {
- centeredIcon.setNotification(sbn);
- setIcons(ic, Collections.singletonList(centeredIcon));
- }
- }
- }
-
- private boolean isImportantConversation() {
- return getChannel() != null && getChannel().isImportantConversation();
- }
-
public int getContrastedColor(Context context, boolean isLowPriority,
int backgroundColor) {
int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
@@ -1125,9 +883,8 @@
getRow().setSensitive(sensitive, deviceSensitive);
if (sensitive != mSensitive) {
mSensitive = sensitive;
- updateSensitiveIconState();
- if (mOnSensitiveChangedListener != null) {
- mOnSensitiveChangedListener.run();
+ for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) {
+ mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this);
}
}
}
@@ -1136,8 +893,14 @@
return mSensitive;
}
- public void setOnSensitiveChangedListener(Runnable listener) {
- mOnSensitiveChangedListener = listener;
+ /** Add a listener to be notified when the entry's sensitivity changes. */
+ public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
+ mOnSensitivityChangedListeners.add(listener);
+ }
+
+ /** Remove a listener that was registered above. */
+ public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
+ mOnSensitivityChangedListeners.remove(listener);
}
public boolean isPulseSuppressed() {
@@ -1167,6 +930,12 @@
}
}
+ /** Listener interface for {@link #addOnSensitivityChangedListener} */
+ public interface OnSensitivityChangedListener {
+ /** Called when the sensitivity changes */
+ void onSensitivityChanged(@NonNull NotificationEntry entry);
+ }
+
/** @see #getDismissState() */
public enum DismissState {
/** User has not dismissed this notif or its parent */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 98c45ff..596235c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -260,11 +260,14 @@
private void applyRanking(RankingMap rankingMap) {
for (CoalescedEvent event : mCoalescedEvents.values()) {
Ranking ranking = new Ranking();
- if (!rankingMap.getRanking(event.getKey(), ranking)) {
- throw new IllegalStateException(
- "Ranking map doesn't contain key: " + event.getKey());
+ if (rankingMap.getRanking(event.getKey(), ranking)) {
+ event.setRanking(ranking);
+ } else {
+ // 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
+ // that means this might occur when SystemUI is starting up.
+ mLogger.logMissingRanking(event.getKey());
}
- event.setRanking(ranking);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 6e8788d..d4d5b64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -57,6 +57,14 @@
"Modification of notif $str1 triggered TIMEOUT emit of batched group $str2"
})
}
+
+ fun logMissingRanking(forKey: String) {
+ buffer.log(TAG, LogLevel.WARNING, {
+ str1 = forKey
+ }, {
+ "RankingMap is missing an entry for coalesced notification $str1"
+ })
+ }
}
private const val TAG = "GroupCoalescer"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 370de83..92426e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -126,7 +126,7 @@
mInterceptedDismissalEntries.remove(entry.getKey());
mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
createDismissedByUserStats(entry));
- } else if (mNotifPipeline.getActiveNotifs().contains(entry)) {
+ } else if (mNotifPipeline.getAllNotifs().contains(entry)) {
// Bubbles are hiding the notifications from the shade, but the bubble was
// deleted; therefore, the notification should be cancelled as if it were a user
// dismissal (this won't re-enter handleInterceptDimissal because Bubbles
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 854444f..b5b756d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -240,7 +240,7 @@
}
private NotificationEntry findNotificationEntryWithKey(String key) {
- for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) {
+ for (NotificationEntry entry : mNotifPipeline.getAllNotifs()) {
if (entry.getKey().equals(key)) {
return entry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 4beeede..7237284 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.collection.inflation;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import android.annotation.Nullable;
@@ -29,8 +28,6 @@
import android.view.ViewGroup;
import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -38,25 +35,22 @@
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.Objects;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
@@ -66,23 +60,23 @@
private static final String TAG = "NotificationViewManager";
- private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-
private final Context mContext;
- private final NotifBindPipeline mNotifBindPipeline;
- private final RowContentBindStage mRowContentBindStage;
private final NotificationMessagingUtil mMessagingUtil;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+ private final NotifBindPipeline mNotifBindPipeline;
+ private final RowContentBindStage mRowContentBindStage;
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+ private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
+ private final ExpandableNotificationRowComponent.Builder
+ mExpandableNotificationRowComponentBuilder;
+ private final IconManager mIconManager;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
private NotificationRowContentBinder.InflationCallback mInflationCallback;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
- private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
- private final ExpandableNotificationRowComponent.Builder
- mExpandableNotificationRowComponentBuilder;
@Inject
public NotificationRowBinderImpl(
@@ -92,14 +86,10 @@
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotifBindPipeline notifBindPipeline,
RowContentBindStage rowContentBindStage,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
- KeyguardBypassController keyguardBypassController,
- StatusBarStateController statusBarStateController,
- NotificationGroupManager notificationGroupManager,
- NotificationGutsManager notificationGutsManager,
NotificationInterruptStateProvider notificationInterruptionStateProvider,
Provider<RowInflaterTask> rowInflaterTaskProvider,
- ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) {
+ ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
+ IconManager iconManager) {
mContext = context;
mNotifBindPipeline = notifBindPipeline;
mRowContentBindStage = rowContentBindStage;
@@ -109,6 +99,7 @@
mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
mRowInflaterTaskProvider = rowInflaterTaskProvider;
mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
+ mIconManager = iconManager;
}
/**
@@ -120,6 +111,8 @@
mPresenter = presenter;
mListContainer = listContainer;
mBindRowCallback = bindRowCallback;
+
+ mIconManager.attach();
}
public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
@@ -142,12 +135,12 @@
final StatusBarNotification sbn = entry.getSbn();
if (entry.rowExists()) {
- entry.updateIcons(mContext, sbn);
+ mIconManager.updateIcons(entry);
entry.reset();
updateNotification(entry, pmUser, sbn, entry.getRow());
entry.getRowController().setOnDismissRunnable(onDismissRunnable);
} else {
- entry.createIcons(mContext, sbn);
+ mIconManager.createIcons(entry);
mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
row -> {
// Setup the controller for the view.
@@ -227,8 +220,8 @@
row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
&& entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
- // TODO: should updates to the entry be happening somewhere else?
- entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
+ // TODO: should this be happening somewhere else?
+ mIconManager.updateIconTags(entry, entry.targetSdk);
row.setOnActivatedListener(mPresenter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index 171816f..b4c2bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -20,6 +20,8 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import java.util.Collection;
+
/**
* A notification collection that manages the list of {@link NotificationEntry}s that will be
* rendered.
@@ -34,4 +36,13 @@
* or deleted.
*/
void addCollectionListener(NotifCollectionListener listener);
+
+ /**
+ * Returns the list of all known notifications, i.e. the notifications that are currently posted
+ * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+ * but it can diverge slightly due to lifetime extenders.
+ *
+ * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
+ */
+ Collection<NotificationEntry> getAllNotifs();
}
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 b2c53da..0c0cded 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
@@ -70,8 +70,24 @@
}
/**
- * Called whenever the RankingMap is updated by system server. By the time this listener is
- * called, the Rankings of all entries will have been updated.
+ * Called whenever a ranking update is applied. During a ranking update, all active,
+ * non-lifetime-extended notification entries will have their ranking object updated.
+ *
+ * Ranking updates occur whenever a notification is added, updated, or removed, or when a
+ * standalone ranking is sent from the server.
+ */
+ default void onRankingApplied() {
+ }
+
+ /**
+ * Called whenever system server sends a standalone ranking update (i.e. one that isn't
+ * associated with a notification being added or removed).
+ *
+ * In general it is unsafe to depend on this method as rankings can change for other reasons.
+ * Instead, listen for {@link #onRankingApplied()}, which is called whenever ANY ranking update
+ * is applied, regardless of source.
+ *
+ * @deprecated Use {@link #onRankingApplied()} instead.
*/
default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
new file mode 100644
index 0000000..2ef0368
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.notifcollection
+
+import android.service.notification.NotificationListenerService.RankingMap
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Set of classes that represent the various events that [NotifCollection] can dispatch to
+ * [NotifCollectionListener]s.
+ *
+ * These events build up in a queue and are periodically emitted in chunks by the collection.
+ */
+
+sealed class NotifEvent {
+ fun dispatchTo(listeners: List<NotifCollectionListener>) {
+ for (i in listeners.indices) {
+ dispatchToListener(listeners[i])
+ }
+ }
+
+ abstract fun dispatchToListener(listener: NotifCollectionListener)
+}
+
+data class InitEntryEvent(
+ val entry: NotificationEntry
+) : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onEntryInit(entry)
+ }
+}
+
+data class EntryAddedEvent(
+ val entry: NotificationEntry
+) : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onEntryAdded(entry)
+ }
+}
+
+data class EntryUpdatedEvent(
+ val entry: NotificationEntry
+) : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onEntryUpdated(entry)
+ }
+}
+
+data class EntryRemovedEvent(
+ val entry: NotificationEntry,
+ val reason: Int
+) : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onEntryRemoved(entry, reason)
+ }
+}
+
+data class CleanUpEntryEvent(
+ val entry: NotificationEntry
+) : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onEntryCleanUp(entry)
+ }
+}
+
+data class RankingUpdatedEvent(
+ val rankingMap: RankingMap
+) : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onRankingUpdate(rankingMap)
+ }
+}
+
+class RankingAppliedEvent() : NotifEvent() {
+ override fun dispatchToListener(listener: NotifCollectionListener) {
+ listener.onRankingApplied()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
new file mode 100644
index 0000000..afc123f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -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.icon
+
+import android.app.Notification
+import android.content.Context
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * Testable wrapper around Context.
+ */
+class IconBuilder @Inject constructor(
+ private val context: Context
+) {
+ fun createIconView(entry: NotificationEntry): StatusBarIconView {
+ return StatusBarIconView(
+ context,
+ "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
+ entry.sbn)
+ }
+
+ fun getIconContentDescription(n: Notification): CharSequence {
+ return StatusBarIconView.contentDescForNotification(context, n)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
new file mode 100644
index 0000000..bb0fcaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -0,0 +1,338 @@
+/*
+ * 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.icon
+
+import android.app.Notification
+import android.app.Person
+import android.content.pm.LauncherApps
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.InflationException
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import javax.inject.Inject
+
+/**
+ * Inflates and updates icons associated with notifications
+ *
+ * Notifications are represented by icons in a few different places -- in the status bar, in the
+ * notification shelf, in AOD, etc. This class is in charge of inflating the views that hold these
+ * icons and keeping the icon assets themselves up to date as notifications change.
+ *
+ * TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
+ * Long-term, it should probably live somewhere in the content inflation pipeline.
+ */
+class IconManager @Inject constructor(
+ private val notifCollection: CommonNotifCollection,
+ private val launcherApps: LauncherApps,
+ private val iconBuilder: IconBuilder
+) {
+ fun attach() {
+ notifCollection.addCollectionListener(entryListener)
+ }
+
+ private val entryListener = object : NotifCollectionListener {
+ override fun onEntryInit(entry: NotificationEntry) {
+ entry.addOnSensitivityChangedListener(sensitivityListener)
+ }
+
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ entry.removeOnSensitivityChangedListener(sensitivityListener)
+ }
+
+ override fun onRankingApplied() {
+ // When the sensitivity changes OR when the isImportantConversation status changes,
+ // we need to update the icons
+ for (entry in notifCollection.allNotifs) {
+ val isImportant = isImportantConversation(entry)
+ if (entry.icons.areIconsAvailable &&
+ isImportant != entry.icons.isImportantConversation) {
+ updateIconsSafe(entry)
+ }
+ entry.icons.isImportantConversation = isImportant
+ }
+ }
+ }
+
+ private val sensitivityListener = NotificationEntry.OnSensitivityChangedListener {
+ entry -> updateIconsSafe(entry)
+ }
+
+ /**
+ * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
+ * result in [NotificationEntry.getIcons].
+ *
+ * @throws InflationException Exception if required icons are not valid or specified
+ */
+ @Throws(InflationException::class)
+ fun createIcons(entry: NotificationEntry) {
+ // Construct the status bar icon view.
+ val sbIcon = iconBuilder.createIconView(entry)
+ sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+
+ // Construct the shelf icon view.
+ val shelfIcon = iconBuilder.createIconView(entry)
+ shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+
+ shelfIcon.visibility = View.INVISIBLE
+ // TODO: This doesn't belong here
+ shelfIcon.setOnVisibilityChangedListener { newVisibility: Int ->
+ if (entry.row != null) {
+ entry.row.setIconsVisible(newVisibility != View.VISIBLE)
+ }
+ }
+
+ // Construct the aod icon view.
+ val aodIcon = iconBuilder.createIconView(entry)
+ aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ aodIcon.setIncreasedSize(true)
+
+ // Construct the centered icon view.
+ val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
+ iconBuilder.createIconView(entry).apply {
+ scaleType = ImageView.ScaleType.CENTER_INSIDE
+ }
+ } else {
+ null
+ }
+
+ // Set the icon views' icons
+ val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+
+ try {
+ setIcon(entry, normalIconDescriptor, sbIcon)
+ setIcon(entry, sensitiveIconDescriptor, shelfIcon)
+ setIcon(entry, sensitiveIconDescriptor, aodIcon)
+ if (centeredIcon != null) {
+ setIcon(entry, normalIconDescriptor, centeredIcon)
+ }
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
+ } catch (e: InflationException) {
+ entry.icons = IconPack.buildEmptyPack(entry.icons)
+ throw e
+ }
+ }
+
+ /**
+ * Update the notification icons.
+ *
+ * @param entry the notification to read the icon from.
+ * @throws InflationException Exception if required icons are not valid or specified
+ */
+ @Throws(InflationException::class)
+ fun updateIcons(entry: NotificationEntry) {
+ if (!entry.icons.areIconsAvailable) {
+ return
+ }
+ entry.icons.smallIconDescriptor = null
+ entry.icons.peopleAvatarDescriptor = null
+
+ val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+
+ entry.icons.statusBarIcon?.let {
+ it.notification = entry.sbn
+ setIcon(entry, normalIconDescriptor, it)
+ }
+
+ entry.icons.shelfIcon?.let {
+ it.notification = entry.sbn
+ setIcon(entry, normalIconDescriptor, it)
+ }
+
+ entry.icons.aodIcon?.let {
+ it.notification = entry.sbn
+ setIcon(entry, sensitiveIconDescriptor, it)
+ }
+
+ entry.icons.centeredIcon?.let {
+ it.notification = entry.sbn
+ setIcon(entry, sensitiveIconDescriptor, it)
+ }
+ }
+
+ /**
+ * Updates tags on the icon views to match the posting app's target SDK level
+ *
+ * Note that this method MUST be called after both [createIcons] and [updateIcons].
+ */
+ fun updateIconTags(entry: NotificationEntry, targetSdk: Int) {
+ setTagOnIconViews(
+ entry.icons,
+ R.id.icon_is_pre_L,
+ targetSdk < Build.VERSION_CODES.LOLLIPOP)
+ }
+
+ private fun updateIconsSafe(entry: NotificationEntry) {
+ try {
+ updateIcons(entry)
+ } catch (e: InflationException) {
+ // TODO This should mark the entire row as involved in an inflation error
+ Log.e(TAG, "Unable to update icon", e)
+ }
+ }
+
+ @Throws(InflationException::class)
+ private fun getIconDescriptors(
+ entry: NotificationEntry
+ ): Pair<StatusBarIcon, StatusBarIcon> {
+ val iconDescriptor = getIconDescriptor(entry, false /* redact */)
+ val sensitiveDescriptor = if (entry.isSensitive) {
+ getIconDescriptor(entry, true /* redact */)
+ } else {
+ iconDescriptor
+ }
+ return Pair(iconDescriptor, sensitiveDescriptor)
+ }
+
+ @Throws(InflationException::class)
+ private fun getIconDescriptor(
+ entry: NotificationEntry,
+ redact: Boolean
+ ): StatusBarIcon {
+ val n = entry.sbn.notification
+ val showPeopleAvatar = isImportantConversation(entry) && !redact
+
+ val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
+ val smallIconDescriptor = entry.icons.smallIconDescriptor
+
+ // If cached, return corresponding cached values
+ if (showPeopleAvatar && peopleAvatarDescriptor != null) {
+ return peopleAvatarDescriptor
+ } else if (!showPeopleAvatar && smallIconDescriptor != null) {
+ return smallIconDescriptor
+ }
+
+ val icon =
+ (if (showPeopleAvatar) {
+ createPeopleAvatar(entry)
+ } else {
+ n.smallIcon
+ }) ?: throw InflationException(
+ "No icon in notification from " + entry.sbn.packageName)
+
+ val ic = StatusBarIcon(
+ entry.sbn.user,
+ entry.sbn.packageName,
+ icon,
+ n.iconLevel,
+ n.number,
+ iconBuilder.getIconContentDescription(n))
+
+ // Cache if important conversation.
+ if (isImportantConversation(entry)) {
+ if (showPeopleAvatar) {
+ entry.icons.peopleAvatarDescriptor = ic
+ } else {
+ entry.icons.smallIconDescriptor = ic
+ }
+ }
+
+ return ic
+ }
+
+ @Throws(InflationException::class)
+ private fun setIcon(
+ entry: NotificationEntry,
+ iconDescriptor: StatusBarIcon,
+ iconView: StatusBarIconView
+ ) {
+ iconView.setTintIcons(shouldTintIconView(entry, iconView))
+ if (!iconView.set(iconDescriptor)) {
+ throw InflationException("Couldn't create icon $iconDescriptor")
+ }
+ }
+
+ @Throws(InflationException::class)
+ private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
+ // Attempt to extract form shortcut.
+ val conversationId = entry.ranking.channel.conversationId
+ val query = LauncherApps.ShortcutQuery()
+ .setPackage(entry.sbn.packageName)
+ .setQueryFlags(
+ LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
+ or LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
+ .setShortcutIds(listOf(conversationId))
+ val shortcuts = launcherApps.getShortcuts(query, entry.sbn.user)
+ var ic: Icon? = null
+ if (shortcuts != null && shortcuts.isNotEmpty()) {
+ ic = shortcuts[0].icon
+ }
+
+ // Fall back to notification large icon if available
+ if (ic == null) {
+ ic = entry.sbn.notification.getLargeIcon()
+ }
+
+ // Fall back to extract from message
+ if (ic == null) {
+ val extras: Bundle = entry.sbn.notification.extras
+ val messages = Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ extras.getParcelableArray(Notification.EXTRA_MESSAGES))
+ val user = extras.getParcelable<Person>(Notification.EXTRA_MESSAGING_PERSON)
+ for (i in messages.indices.reversed()) {
+ val message = messages[i]
+ val sender = message.senderPerson
+ if (sender != null && sender !== user) {
+ ic = message.senderPerson!!.icon
+ break
+ }
+ }
+ }
+
+ // Revert to small icon if still not available
+ if (ic == null) {
+ ic = entry.sbn.notification.smallIcon
+ }
+ if (ic == null) {
+ throw InflationException("No icon in notification from " + entry.sbn.packageName)
+ }
+ return ic
+ }
+
+ /**
+ * Determines if this icon should be tinted based on the sensitivity of the icon, its context
+ * and the user's indicated sensitivity preference.
+ *
+ * @param iconView The icon that should/should not be tinted.
+ */
+ private fun shouldTintIconView(entry: NotificationEntry, iconView: StatusBarIconView): Boolean {
+ val usedInSensitiveContext =
+ iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
+ return !isImportantConversation(entry) || usedInSensitiveContext && entry.isSensitive
+ }
+
+ private fun isImportantConversation(entry: NotificationEntry): Boolean {
+ return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation
+ }
+
+ private fun setTagOnIconViews(icons: IconPack, key: Int, tag: Any) {
+ icons.statusBarIcon?.setTag(key, tag)
+ icons.shelfIcon?.setTag(key, tag)
+ icons.aodIcon?.setTag(key, tag)
+ icons.centeredIcon?.setTag(key, tag)
+ }
+}
+
+private const val TAG = "IconManager"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
new file mode 100644
index 0000000..054e381
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -0,0 +1,134 @@
+/*
+ * 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.icon;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.StatusBarIconView;
+
+/**
+ * Data class for storing icons associated with a notification
+ */
+public final class IconPack {
+
+ private final boolean mAreIconsAvailable;
+ @Nullable private final StatusBarIconView mStatusBarIcon;
+ @Nullable private final StatusBarIconView mShelfIcon;
+ @Nullable private final StatusBarIconView mAodIcon;
+ @Nullable private final StatusBarIconView mCenteredIcon;
+
+ @Nullable private StatusBarIcon mSmallIconDescriptor;
+ @Nullable private StatusBarIcon mPeopleAvatarDescriptor;
+
+ private boolean mIsImportantConversation;
+
+ /**
+ * Builds an empty instance of IconPack that doesn't have any icons (because either they
+ * haven't been inflated yet or there was an error while inflating them).
+ */
+ public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
+ return new IconPack(false, null, null, null, null, fromSource);
+ }
+
+ /**
+ * Builds an instance of an IconPack that contains successfully-inflated icons
+ */
+ public static IconPack buildPack(
+ @NonNull StatusBarIconView statusBarIcon,
+ @NonNull StatusBarIconView shelfIcon,
+ @NonNull StatusBarIconView aodIcon,
+ @Nullable StatusBarIconView centeredIcon,
+ @Nullable IconPack source) {
+ return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
+ }
+
+ private IconPack(
+ boolean areIconsAvailable,
+ @Nullable StatusBarIconView statusBarIcon,
+ @Nullable StatusBarIconView shelfIcon,
+ @Nullable StatusBarIconView aodIcon,
+ @Nullable StatusBarIconView centeredIcon,
+ @Nullable IconPack source) {
+ mAreIconsAvailable = areIconsAvailable;
+ mStatusBarIcon = statusBarIcon;
+ mShelfIcon = shelfIcon;
+ mCenteredIcon = centeredIcon;
+ mAodIcon = aodIcon;
+ if (source != null) {
+ mIsImportantConversation = source.mIsImportantConversation;
+ }
+ }
+
+ /** The version of the notification icon that appears in the status bar. */
+ @Nullable
+ public StatusBarIconView getStatusBarIcon() {
+ return mStatusBarIcon;
+ }
+
+ /**
+ * The version of the icon that appears in the "shelf" at the bottom of the notification shade.
+ * In general, this icon also appears somewhere on the notification and is "sucked" into the
+ * shelf as the scrolls beyond it.
+ */
+ @Nullable
+ public StatusBarIconView getShelfIcon() {
+ return mShelfIcon;
+ }
+
+ @Nullable
+ public StatusBarIconView getCenteredIcon() {
+ return mCenteredIcon;
+ }
+
+ /** The version of the icon that's shown when pulsing (in AOD). */
+ @Nullable
+ public StatusBarIconView getAodIcon() {
+ return mAodIcon;
+ }
+
+ @Nullable
+ StatusBarIcon getSmallIconDescriptor() {
+ return mSmallIconDescriptor;
+ }
+
+ void setSmallIconDescriptor(@Nullable StatusBarIcon smallIconDescriptor) {
+ mSmallIconDescriptor = smallIconDescriptor;
+ }
+
+ @Nullable
+ StatusBarIcon getPeopleAvatarDescriptor() {
+ return mPeopleAvatarDescriptor;
+ }
+
+ void setPeopleAvatarDescriptor(@Nullable StatusBarIcon peopleAvatarDescriptor) {
+ mPeopleAvatarDescriptor = peopleAvatarDescriptor;
+ }
+
+ boolean isImportantConversation() {
+ return mIsImportantConversation;
+ }
+
+ void setImportantConversation(boolean importantConversation) {
+ mIsImportantConversation = importantConversation;
+ }
+
+ public boolean getAreIconsAvailable() {
+ return mAreIconsAvailable;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
index 373457d..dbfa27f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/DungeonRow.kt
@@ -37,7 +37,7 @@
}
(findViewById(R.id.icon) as StatusBarIconView).apply {
- set(entry?.icon?.statusBarIcon)
+ set(entry?.icons?.statusBarIcon?.statusBarIcon)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index f61fe98..b1db5b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -579,7 +579,7 @@
@VisibleForTesting
void updateShelfIconColor() {
- StatusBarIconView expandedIcon = mEntry.expandedIcon;
+ StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
ContrastColorUtil.getInstance(mContext));
@@ -1290,7 +1290,7 @@
setLongPressListener(null);
mGroupParentWhenDismissed = mNotificationParent;
mChildAfterViewWhenDismissed = null;
- mEntry.icon.setDismissed();
+ mEntry.getIcons().getStatusBarIcon().setDismissed();
if (isChildInGroup()) {
List<ExpandableNotificationRow> notificationChildren =
mNotificationParent.getNotificationChildren();
@@ -1832,7 +1832,7 @@
mTranslateableViews.get(i).setTranslationX(0);
}
invalidateOutline();
- getEntry().expandedIcon.setScrollX(0);
+ getEntry().getIcons().getShelfIcon().setScrollX(0);
}
if (mMenuRow != null) {
@@ -1912,7 +1912,7 @@
// In order to keep the shelf in sync with this swiping, we're simply translating
// it's icon by the same amount. The translation is already being used for the normal
// positioning, so we can use the scrollX instead.
- getEntry().expandedIcon.setScrollX((int) -translationX);
+ getEntry().getIcons().getShelfIcon().setScrollX((int) -translationX);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -2111,7 +2111,7 @@
@Override
public StatusBarIconView getShelfIcon() {
- return getEntry().expandedIcon;
+ return getEntry().getIcons().getShelfIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 0996ff2..14442e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -453,9 +453,10 @@
needsAnimation = false;
}
NotificationEntry entry = row.getEntry();
- StatusBarIconView icon = entry.icon;
- if (entry.centeredIcon != null && entry.centeredIcon.getParent() != null) {
- icon = entry.centeredIcon;
+ StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
+ final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
+ if (centeredIcon != null && centeredIcon.getParent() != null) {
+ icon = centeredIcon;
}
if (icon.getParent() != null) {
icon.getLocationOnScreen(mTmpLocation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c39ee3a..51c02c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -269,7 +269,7 @@
}
updateIsolatedIconLocation(false /* requireUpdate */);
mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
- : newEntry.icon, animateIsolation);
+ : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index b09ccff..f58cce5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -253,8 +253,8 @@
boolean hidePulsing, boolean onlyShowCenteredIcon) {
final boolean isCenteredNotificationIcon = mCenteredIconView != null
- && entry.centeredIcon != null
- && Objects.equals(entry.centeredIcon, mCenteredIconView);
+ && entry.getIcons().getCenteredIcon() != null
+ && Objects.equals(entry.getIcons().getCenteredIcon(), mCenteredIconView);
if (onlyShowCenteredIcon) {
return isCenteredNotificationIcon;
}
@@ -307,7 +307,7 @@
}
private void updateShelfIcons() {
- updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
+ updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
true /* showAmbient */,
true /* showLowPriority */,
false /* hideDismissed */,
@@ -319,7 +319,7 @@
}
public void updateStatusBarIcons() {
- updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
+ updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
false /* showAmbient */,
mShowLowPriority,
true /* hideDismissed */,
@@ -331,7 +331,7 @@
}
private void updateCenterIcon() {
- updateIconsForLayout(entry -> entry.centeredIcon, mCenteredIcon,
+ updateIconsForLayout(entry -> entry.getIcons().getCenteredIcon(), mCenteredIcon,
false /* showAmbient */,
true /* showLowPriority */,
false /* hideDismissed */,
@@ -343,7 +343,7 @@
}
public void updateAodNotificationIcons() {
- updateIconsForLayout(entry -> entry.aodIcon, mAodIcons,
+ updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
false /* showAmbient */,
true /* showLowPriority */,
true /* hideDismissed */,
@@ -517,7 +517,7 @@
* Shows the icon view given in the center.
*/
public void showIconCentered(NotificationEntry entry) {
- StatusBarIconView icon = entry == null ? null : entry.centeredIcon;
+ StatusBarIconView icon = entry == null ? null : entry.getIcons().getCenteredIcon();
if (!Objects.equals(mCenteredIconView, icon)) {
mCenteredIconView = icon;
updateNotificationIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 75f5023..e1e679f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -309,7 +309,8 @@
return !state.mForceCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
|| state.mHeadsUpShowing || state.mBubblesShowing
- || state.mScrimsVisibility != ScrimController.TRANSPARENT);
+ || state.mScrimsVisibility != ScrimController.TRANSPARENT)
+ || state.mBackgroundBlurRadius > 0;
}
private void applyFitsSystemWindows(State state) {
@@ -478,6 +479,19 @@
apply(mCurrentState);
}
+ /**
+ * Current blur level, controller by
+ * {@link com.android.systemui.statusbar.NotificationShadeDepthController}.
+ * @param backgroundBlurRadius Radius in pixels.
+ */
+ public void setBackgroundBlurRadius(int backgroundBlurRadius) {
+ if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
+ return;
+ }
+ mCurrentState.mBackgroundBlurRadius = backgroundBlurRadius;
+ apply(mCurrentState);
+ }
+
public void setHeadsUpShowing(boolean showing) {
mCurrentState.mHeadsUpShowing = showing;
apply(mCurrentState);
@@ -665,6 +679,7 @@
boolean mForcePluginOpen;
boolean mDozing;
int mScrimsVisibility;
+ int mBackgroundBlurRadius;
private boolean isKeyguardShowingAndNotOccluded() {
return mKeyguardShowing && !mKeyguardOccluded;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 1359f74..e25c14c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -47,7 +48,6 @@
private static final String TAG = "PhoneStatusBarView";
private static final boolean DEBUG = StatusBar.DEBUG;
private static final boolean DEBUG_GESTURES = false;
- private static final int NO_VALUE = Integer.MIN_VALUE;
private final CommandQueue mCommandQueue;
StatusBar mBar;
@@ -64,13 +64,14 @@
}
};
private DarkReceiver mBattery;
- private int mLastOrientation;
+ private int mRotationOrientation;
@Nullable
private View mCenterIconSpace;
@Nullable
private View mCutoutSpace;
@Nullable
private DisplayCutout mDisplayCutout;
+ private int mStatusBarHeight;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -107,7 +108,7 @@
super.onAttachedToWindow();
// Always have Battery meters in the status bar observe the dark/light modes.
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
- if (updateOrientationAndCutout(getResources().getConfiguration().orientation)) {
+ if (updateOrientationAndCutout()) {
updateLayoutForCutout();
}
}
@@ -124,7 +125,7 @@
super.onConfigurationChanged(newConfig);
// May trigger cutout space layout-ing
- if (updateOrientationAndCutout(newConfig.orientation)) {
+ if (updateOrientationAndCutout()) {
updateLayoutForCutout();
requestLayout();
}
@@ -132,7 +133,7 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (updateOrientationAndCutout(mLastOrientation)) {
+ if (updateOrientationAndCutout()) {
updateLayoutForCutout();
requestLayout();
}
@@ -140,17 +141,14 @@
}
/**
- *
- * @param newOrientation may pass NO_VALUE for no change
* @return boolean indicating if we need to update the cutout location / margins
*/
- private boolean updateOrientationAndCutout(int newOrientation) {
+ private boolean updateOrientationAndCutout() {
boolean changed = false;
- if (newOrientation != NO_VALUE) {
- if (mLastOrientation != newOrientation) {
- changed = true;
- mLastOrientation = newOrientation;
- }
+ int newRotation = RotationUtils.getExactRotation(mContext);
+ if (newRotation != mRotationOrientation) {
+ changed = true;
+ mRotationOrientation = newRotation;
}
if (!Objects.equals(getRootWindowInsets().getDisplayCutout(), mDisplayCutout)) {
@@ -293,17 +291,16 @@
final int waterfallTopInset =
mDisplayCutout == null ? 0 : mDisplayCutout.getWaterfallInsets().top;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height =
- getResources().getDimensionPixelSize(R.dimen.status_bar_height) - waterfallTopInset;
+ mStatusBarHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
+ layoutParams.height = mStatusBarHeight - waterfallTopInset;
setLayoutParams(layoutParams);
}
private void updateLayoutForCutout() {
updateStatusBarHeight();
- Pair<Integer, Integer> cornerCutoutMargins =
- StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay());
- updateCutoutLocation(cornerCutoutMargins);
- updateSafeInsets(cornerCutoutMargins);
+ updateCutoutLocation(StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay()));
+ updateSafeInsets(StatusBarWindowView.statusBarCornerCutoutMargins(mDisplayCutout,
+ getDisplay(), mRotationOrientation, mStatusBarHeight));
}
private void updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 22bf513..760a6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -33,6 +33,8 @@
import android.view.WindowInsets;
import android.widget.FrameLayout;
+import com.android.systemui.util.leak.RotationUtils;
+
/**
* Status bar view.
*/
@@ -52,17 +54,13 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
- mLeftInset = 0;
- mRightInset = 0;
+ mLeftInset = insets.left;
+ mRightInset = insets.right;
mTopInset = 0;
DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
if (displayCutout != null) {
mTopInset = displayCutout.getWaterfallInsets().top;
- mLeftInset = displayCutout.getSafeInsetLeft();
- mRightInset = displayCutout.getSafeInsetRight();
}
- mLeftInset = Math.max(insets.left, mLeftInset);
- mRightInset = Math.max(insets.right, mRightInset);
applyMargins();
return windowInsets;
}
@@ -100,44 +98,33 @@
return new Pair<>(roundedCornerContentPadding, roundedCornerContentPadding);
}
- // compute the padding needed for corner cutout.
- final int leftMargin = cutout.getSafeInsetLeft();
- final int rightMargin = cutout.getSafeInsetRight();
+ // padding needed for corner cutout.
int leftCornerCutoutPadding = 0;
int rightCornerCutoutPadding = 0;
if (cornerCutoutPadding != null) {
- if (cornerCutoutPadding.first > leftMargin) {
- leftCornerCutoutPadding = cornerCutoutPadding.first - leftMargin;
- }
- if (cornerCutoutPadding.second > rightMargin) {
- rightCornerCutoutPadding = cornerCutoutPadding.second - rightMargin;
- }
- }
-
- // compute the padding needed for rounded corner
- int leftRoundedCornerPadding = 0;
- int rightRoundedCornerPadding = 0;
- if (roundedCornerContentPadding > leftMargin) {
- leftRoundedCornerPadding = roundedCornerContentPadding - leftMargin;
- }
- if (roundedCornerContentPadding > rightMargin) {
- rightRoundedCornerPadding = roundedCornerContentPadding - rightMargin;
+ leftCornerCutoutPadding = cornerCutoutPadding.first;
+ rightCornerCutoutPadding = cornerCutoutPadding.second;
}
return new Pair<>(
- Math.max(leftCornerCutoutPadding, leftRoundedCornerPadding),
- Math.max(rightCornerCutoutPadding, rightRoundedCornerPadding));
+ Math.max(leftCornerCutoutPadding, roundedCornerContentPadding),
+ Math.max(rightCornerCutoutPadding, roundedCornerContentPadding));
}
+
/**
- * Compute the corner cutout margins
- *
- * @param cutout
- * @param display
- * @return
+ * Compute the corner cutout margins in portrait mode
*/
public static Pair<Integer, Integer> cornerCutoutMargins(DisplayCutout cutout,
Display display) {
+ return statusBarCornerCutoutMargins(cutout, display, RotationUtils.ROTATION_NONE, 0);
+ }
+
+ /**
+ * Compute the corner cutout margins in the given orientation (exactRotation)
+ */
+ public static Pair<Integer, Integer> statusBarCornerCutoutMargins(DisplayCutout cutout,
+ Display display, int exactRotation, int statusBarHeight) {
if (cutout == null) {
return null;
}
@@ -145,14 +132,33 @@
display.getRealSize(size);
Rect bounds = new Rect();
- boundsFromDirection(cutout, Gravity.TOP, bounds);
+ switch (exactRotation) {
+ case RotationUtils.ROTATION_LANDSCAPE:
+ boundsFromDirection(cutout, Gravity.LEFT, bounds);
+ break;
+ case RotationUtils.ROTATION_SEASCAPE:
+ boundsFromDirection(cutout, Gravity.RIGHT, bounds);
+ break;
+ case RotationUtils.ROTATION_NONE:
+ boundsFromDirection(cutout, Gravity.TOP, bounds);
+ break;
+ case RotationUtils.ROTATION_UPSIDE_DOWN:
+ // we assume the cutout is always on top in portrait mode
+ return null;
+ }
+
+ if (statusBarHeight >= 0 && bounds.top > statusBarHeight) {
+ return null;
+ }
if (bounds.left <= 0) {
return new Pair<>(bounds.right, 0);
}
+
if (bounds.right >= size.x) {
return new Pair<>(0, size.x - bounds.left);
}
+
return null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 35954d8..496bf68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -99,7 +99,6 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
- filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
filter.addAction(ACTION_LEVEL_TEST);
mBroadcastDispatcher.registerReceiver(this, filter);
}
@@ -155,8 +154,6 @@
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
- } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
- setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
} else if (action.equals(ACTION_LEVEL_TEST)) {
mTestmode = true;
mMainHandler.post(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 2f1b160..5411839 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -49,6 +49,8 @@
public class DisplayImeController implements DisplayController.OnDisplaysChangedListener {
private static final String TAG = "DisplayImeController";
+ private static final boolean DEBUG = false;
+
public static final int ANIMATION_DURATION_SHOW_MS = 275;
public static final int ANIMATION_DURATION_HIDE_MS = 340;
public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
@@ -209,6 +211,7 @@
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
+ if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
startAnimation(true /* show */);
}
@@ -217,6 +220,7 @@
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
+ if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
startAnimation(false /* show */);
}
@@ -241,6 +245,11 @@
return;
}
mHandler.post(() -> {
+ if (DEBUG) {
+ Slog.d(TAG, "Run startAnim show:" + show + " was:"
+ + (mAnimationDirection == DIRECTION_SHOW ? "SHOW"
+ : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE")));
+ }
if ((mAnimationDirection == DIRECTION_SHOW && show)
|| (mAnimationDirection == DIRECTION_HIDE && !show)) {
return;
@@ -289,6 +298,11 @@
public void onAnimationStart(Animator animation) {
SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setPosition(mImeSourceControl.getLeash(), x, startY);
+ if (DEBUG) {
+ Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
+ + imeTop(imeSource, hiddenY) + "->" + imeTop(imeSource, shownY)
+ + " showing:" + (mAnimationDirection == DIRECTION_SHOW));
+ }
dispatchStartPositioning(mDisplayId, imeTop(imeSource, hiddenY),
imeTop(imeSource, shownY), mAnimationDirection == DIRECTION_SHOW,
t);
@@ -304,6 +318,7 @@
}
@Override
public void onAnimationEnd(Animator animation) {
+ if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled);
SurfaceControl.Transaction t = mTransactionPool.acquire();
if (!mCancelled) {
t.setPosition(mImeSourceControl.getLeash(), x, endY);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 1b34b3d..b6537bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -23,6 +23,8 @@
import static com.android.systemui.ScreenDecorations.rectsToRegion;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -42,6 +44,7 @@
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.graphics.drawable.VectorDrawable;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
@@ -142,6 +145,8 @@
com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 0);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
@@ -161,6 +166,8 @@
com.android.internal.R.dimen.rounded_corner_radius, 20);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 20);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// no cutout
doReturn(null).when(mScreenDecorations).getCutout();
@@ -182,6 +189,67 @@
}
@Test
+ public void testRoundingRadius_NoCutout() {
+ final int testRadius = 1;
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius, testRadius);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius_top, testRadius);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius_bottom, testRadius);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
+
+ // no cutout
+ doReturn(null).when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Size of corner view should same as rounded_corner_radius{_top|_bottom}
+ assertThat(mScreenDecorations.mRoundedDefault).isEqualTo(testRadius);
+ assertThat(mScreenDecorations.mRoundedDefaultTop).isEqualTo(testRadius);
+ assertThat(mScreenDecorations.mRoundedDefaultBottom).isEqualTo(testRadius);
+ }
+
+ @Test
+ public void testRoundingMultipleRadius_NoCutout() {
+ final VectorDrawable d = (VectorDrawable) mContext.getDrawable(R.drawable.rounded);
+ final int multipleRadiusSize = Math.max(d.getIntrinsicWidth(), d.getIntrinsicHeight());
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.dimen.rounded_corner_radius, 9999);
+ mContext.getOrCreateTestableResources()
+ .addOverride(dimen.rounded_corner_content_padding, 9999);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, true);
+
+ // no cutout
+ doReturn(null).when(mScreenDecorations).getCutout();
+
+ mScreenDecorations.start();
+ // Top and bottom windows are created for rounded corners.
+ verify(mWindowManager, times(1))
+ .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any());
+ verify(mWindowManager, times(1))
+ .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any());
+
+ // Left and right window should be null.
+ assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]);
+ assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]);
+
+ // One tunable.
+ verify(mTunerService, times(1)).addTunable(any(), any());
+
+ // Size of corner view should exactly match max(width, height) of R.drawable.rounded
+ assertThat(mScreenDecorations.mRoundedDefault).isEqualTo(multipleRadiusSize);
+ assertThat(mScreenDecorations.mRoundedDefaultTop).isEqualTo(multipleRadiusSize);
+ assertThat(mScreenDecorations.mRoundedDefaultBottom).isEqualTo(multipleRadiusSize);
+ }
+
+ @Test
public void testNoRounding_CutoutShortEdge() {
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true);
@@ -193,6 +261,8 @@
com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 0);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// top cutout
doReturn(new DisplayCutout(
@@ -227,6 +297,8 @@
com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 0);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// left cutout
doReturn(new DisplayCutout(
@@ -257,6 +329,8 @@
com.android.internal.R.dimen.rounded_corner_radius, 20);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 20);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// top cutout
doReturn(new DisplayCutout(
@@ -288,6 +362,8 @@
com.android.internal.R.dimen.rounded_corner_radius, 20);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 20);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// left cutout
doReturn(new DisplayCutout(
@@ -319,6 +395,8 @@
com.android.internal.R.dimen.rounded_corner_radius, 20);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 20);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// top and left cutout
doReturn(new DisplayCutout(
@@ -355,6 +433,8 @@
com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 0);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// Set to short edge cutout(top).
doReturn(new DisplayCutout(
@@ -402,6 +482,8 @@
com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
mContext.getOrCreateTestableResources()
.addOverride(dimen.rounded_corner_content_padding, 0);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
// top cutout
doReturn(new DisplayCutout(
@@ -440,6 +522,8 @@
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.dimen.rounded_corner_radius, 20);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_roundedCornerMultipleRadius, false);
mScreenDecorations.start();
assertEquals(mScreenDecorations.mRoundedDefault, 20);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index e3187cb9..b1ac022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -43,6 +43,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.IntSupplier;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -59,7 +60,13 @@
@Before
public void setUp() throws Exception {
super.setUp();
- mStackController = spy(new TestableStackController(mFloatingContentCoordinator));
+ mStackController = spy(new TestableStackController(
+ mFloatingContentCoordinator, new IntSupplier() {
+ @Override
+ public int getAsInt() {
+ return mLayout.getChildCount();
+ }
+ }));
mLayout.setActiveController(mStackController);
addOneMoreThanBubbleLimitBubbles();
mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
@@ -295,8 +302,9 @@
*/
private class TestableStackController extends StackAnimationController {
TestableStackController(
- FloatingContentCoordinator floatingContentCoordinator) {
- super(floatingContentCoordinator);
+ FloatingContentCoordinator floatingContentCoordinator,
+ IntSupplier bubbleCountSupplier) {
+ super(floatingContentCoordinator, bubbleCountSupplier);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index b12db2b..0bf0f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -24,6 +24,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import android.util.Size;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -51,6 +52,7 @@
private static final float MIN_ASPECT_RATIO = 0.5f;
private static final float MAX_ASPECT_RATIO = 2f;
private static final Rect EMPTY_CURRENT_BOUNDS = null;
+ private static final Size EMPTY_MINIMAL_SIZE = null;
private PipBoundsHandler mPipBoundsHandler;
private DisplayInfo mDefaultDisplayInfo;
@@ -119,7 +121,7 @@
};
for (float aspectRatio : aspectRatios) {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS);
+ aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
assertEquals("Destination bounds matches the given aspect ratio",
@@ -135,7 +137,7 @@
};
for (float aspectRatio : invalidAspectRatios) {
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, EMPTY_CURRENT_BOUNDS);
+ aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
assertEquals("Destination bounds fallbacks to default aspect ratio",
@@ -151,7 +153,7 @@
currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
- aspectRatio, currentBounds);
+ aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
final float actualAspectRatio =
destinationBounds.width() / (destinationBounds.height() * 1f);
@@ -160,14 +162,58 @@
}
@Test
+ public void getDestinationBounds_withMinSize_returnMinBounds() {
+ final float[] aspectRatios = new float[] {
+ (MIN_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2,
+ DEFAULT_ASPECT_RATIO,
+ (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
+ };
+ final Size[] minimalSizes = new Size[] {
+ new Size((int) (100 * aspectRatios[0]), 100),
+ new Size((int) (100 * aspectRatios[1]), 100),
+ new Size((int) (100 * aspectRatios[2]), 100)
+ };
+ for (int i = 0; i < aspectRatios.length; i++) {
+ final float aspectRatio = aspectRatios[i];
+ final Size minimalSize = minimalSizes[i];
+ final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
+ aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
+ assertTrue("Destination bounds is no smaller than minimal requirement",
+ (destinationBounds.width() == minimalSize.getWidth()
+ && destinationBounds.height() >= minimalSize.getHeight())
+ || (destinationBounds.height() == minimalSize.getHeight()
+ && destinationBounds.width() >= minimalSize.getWidth()));
+ final float actualAspectRatio =
+ destinationBounds.width() / (destinationBounds.height() * 1f);
+ assertEquals("Destination bounds matches the given aspect ratio",
+ aspectRatio, actualAspectRatio, ASPECT_RATIO_ERROR_MARGIN);
+ }
+ }
+
+ @Test
+ public void getDestinationBounds_withCurrentBounds_ignoreMinBounds() {
+ final float aspectRatio = (DEFAULT_ASPECT_RATIO + MAX_ASPECT_RATIO) / 2;
+ final Rect currentBounds = new Rect(0, 0, 0, 100);
+ currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
+ final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
+
+ final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
+ aspectRatio, currentBounds, minSize);
+
+ assertTrue("Destination bounds ignores minimal size",
+ destinationBounds.width() > minSize.getWidth()
+ && destinationBounds.height() > minSize.getHeight());
+ }
+
+ @Test
public void setShelfHeight_offsetBounds() {
final int shelfHeight = 100;
final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
mPipBoundsHandler.setShelfHeight(true, shelfHeight);
final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -shelfHeight);
assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition);
@@ -177,11 +223,11 @@
public void onImeVisibilityChanged_offsetBounds() {
final int imeHeight = 100;
final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight);
final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -imeHeight);
assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition);
@@ -191,13 +237,13 @@
public void onSaveReentryBounds_restoreLastPosition() {
final ComponentName componentName = new ComponentName(mContext, "component1");
final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
oldPosition.offset(0, -100);
mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition);
final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition);
}
@@ -206,14 +252,14 @@
public void onResetReentryBounds_useDefaultBounds() {
final ComponentName componentName = new ComponentName(mContext, "component1");
final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final Rect newBounds = new Rect(defaultBounds);
newBounds.offset(0, -100);
mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
mPipBoundsHandler.onResetReentryBounds(componentName);
final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds);
}
@@ -222,14 +268,14 @@
public void onResetReentryBounds_componentMismatch_restoreLastPosition() {
final ComponentName componentName = new ComponentName(mContext, "component1");
final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
final Rect newBounds = new Rect(defaultBounds);
newBounds.offset(0, -100);
mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2"));
final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
- DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS);
+ DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
new file mode 100644
index 0000000..f061f34
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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
+
+import android.app.WallpaperManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Choreographer
+import android.view.View
+import android.view.ViewRootImpl
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.*
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class NotificationShadeDepthControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var blurUtils: BlurUtils
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var choreographer: Choreographer
+ @Mock private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var root: View
+ @Mock private lateinit var viewRootImpl: ViewRootImpl
+ @Mock private lateinit var shadeSpring: SpringAnimation
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
+
+ private lateinit var statusBarStateListener: StatusBarStateController.StateListener
+ private var statusBarState = StatusBarState.SHADE
+ private val maxBlur = 150
+ private lateinit var notificationShadeDepthController: NotificationShadeDepthController
+
+ @Before
+ fun setup() {
+ `when`(root.viewRootImpl).thenReturn(viewRootImpl)
+ `when`(statusBarStateController.state).then { statusBarState }
+ `when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
+ (answer.arguments[0] as Float * maxBlur).toInt()
+ }
+ notificationShadeDepthController = NotificationShadeDepthController(
+ statusBarStateController, blurUtils, biometricUnlockController,
+ keyguardStateController, choreographer, wallpaperManager,
+ notificationShadeWindowController, dumpManager)
+ notificationShadeDepthController.shadeSpring = shadeSpring
+ notificationShadeDepthController.root = root
+
+ val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java)
+ verify(statusBarStateController).addCallback(captor.capture())
+ statusBarStateListener = captor.value
+ }
+
+ @Test
+ fun setupListeners() {
+ verify(dumpManager).registerDumpable(anyString(), safeEq(notificationShadeDepthController))
+ }
+
+ @Test
+ fun onPanelExpansionChanged_apliesBlur_ifShade() {
+ notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
+ false /* tracking */)
+ verify(shadeSpring).animateToFinalPosition(eq(maxBlur.toFloat()))
+ }
+
+ @Test
+ fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
+ onPanelExpansionChanged_apliesBlur_ifShade()
+ clearInvocations(shadeSpring)
+
+ statusBarState = StatusBarState.KEYGUARD
+ statusBarStateListener.onStateChanged(statusBarState)
+ verify(shadeSpring).animateToFinalPosition(eq(0f))
+ }
+
+ @Test
+ fun updateGlobalDialogVisibility_schedulesUpdate() {
+ notificationShadeDepthController.updateGlobalDialogVisibility(0.5f, root)
+ verify(choreographer).postFrameCallback(any())
+ }
+
+ private fun <T : Any> safeEq(value: T): T {
+ return eq(value) ?: value
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index fe8b89f..a58000d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -55,6 +55,7 @@
private boolean mIsVisuallyInterruptive = false;
private boolean mIsConversation = false;
private ShortcutInfo mShortcutInfo = null;
+ private boolean mIsBubble = false;
public RankingBuilder() {
}
@@ -82,6 +83,7 @@
mIsVisuallyInterruptive = ranking.visuallyInterruptive();
mIsConversation = ranking.isConversation();
mShortcutInfo = ranking.getShortcutInfo();
+ mIsBubble = ranking.isBubble();
}
public Ranking build() {
@@ -108,7 +110,8 @@
mCanBubble,
mIsVisuallyInterruptive,
mIsConversation,
- mShortcutInfo);
+ mShortcutInfo,
+ mIsBubble);
return ranking;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 312bb7f..972357e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -143,7 +143,7 @@
IMPORTANCE_DEFAULT,
null, null,
null, null, null, true, sentiment, false, -1, false, null, null, false, false,
- false, null);
+ false, null, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
}
@@ -162,7 +162,7 @@
null, null,
null, null, null, true,
Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
- false, smartActions, null, false, false, false, null);
+ false, smartActions, null, false, false, false, null, false);
return true;
}).when(mRankingMap).getRanking(eq(key), any(Ranking.class));
}
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 b7184be..82de4a3 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
@@ -34,16 +34,17 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import android.annotation.Nullable;
@@ -83,13 +84,13 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -123,6 +124,8 @@
private NotifCollection mCollection;
private BatchableNotificationHandler mNotifHandler;
+ private InOrder mListenerInOrder;
+
private NoManSimulator mNoMan;
@Before
@@ -133,6 +136,8 @@
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true);
+ mListenerInOrder = inOrder(mCollectionListener);
+
mCollection = new NotifCollection(
mStatusBarService,
mock(DumpManager.class),
@@ -159,10 +164,12 @@
.setRank(4747));
// THEN the listener is notified
- verify(mCollectionListener).onEntryInit(mEntryCaptor.capture());
- NotificationEntry entry = mEntryCaptor.getValue();
+ final NotificationEntry entry = mCollectionListener.getEntry(notif1.key);
- verify(mCollectionListener).onEntryAdded(entry);
+ mListenerInOrder.verify(mCollectionListener).onEntryInit(entry);
+ mListenerInOrder.verify(mCollectionListener).onEntryAdded(entry);
+ mListenerInOrder.verify(mCollectionListener).onRankingApplied();
+
assertEquals(notif1.key, entry.getKey());
assertEquals(notif1.sbn, entry.getSbn());
assertEquals(notif1.ranking, entry.getRanking());
@@ -215,12 +222,11 @@
assertEquals(entry2.getRanking(), capturedUpdate.getRanking());
// THEN onBuildList is called only once
- verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
- assertEquals(new ArraySet<>(Arrays.asList(
- capturedAdds.get(0),
- capturedAdds.get(1),
- capturedUpdate
- )), new ArraySet<>(mBuildListCaptor.getValue()));
+ verifyBuiltList(
+ List.of(
+ capturedAdds.get(0),
+ capturedAdds.get(1),
+ capturedUpdate));
}
@Test
@@ -234,9 +240,11 @@
.setRank(89));
// THEN the listener is notified
- verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture());
+ final NotificationEntry entry = mCollectionListener.getEntry(notif2.key);
- NotificationEntry entry = mEntryCaptor.getValue();
+ mListenerInOrder.verify(mCollectionListener).onEntryUpdated(entry);
+ mListenerInOrder.verify(mCollectionListener).onRankingApplied();
+
assertEquals(notif2.key, entry.getKey());
assertEquals(notif2.sbn, entry.getSbn());
assertEquals(notif2.ranking, entry.getRanking());
@@ -256,8 +264,10 @@
mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL);
// THEN the listener is notified
- verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
- verify(mCollectionListener).onEntryCleanUp(entry);
+ mListenerInOrder.verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL);
+ mListenerInOrder.verify(mCollectionListener).onEntryCleanUp(entry);
+ mListenerInOrder.verify(mCollectionListener).onRankingApplied();
+
assertEquals(notif.sbn, entry.getSbn());
assertEquals(notif.ranking, entry.getRanking());
}
@@ -415,8 +425,8 @@
// THEN the dismissed entry still appears in the notification set
assertEquals(
- new ArraySet<>(Collections.singletonList(entry1)),
- new ArraySet<>(mCollection.getActiveNotifs()));
+ new ArraySet<>(singletonList(entry1)),
+ new ArraySet<>(mCollection.getAllNotifs()));
}
@Test
@@ -444,7 +454,7 @@
mNoMan.retractNotif(notif2.sbn, REASON_CANCEL);
assertEquals(
new ArraySet<>(List.of(entry1, entry2, entry3)),
- new ArraySet<>(mCollection.getActiveNotifs()));
+ new ArraySet<>(mCollection.getAllNotifs()));
// WHEN the summary is dismissed by the user
mCollection.dismissNotification(entry1, defaultStats(entry1));
@@ -452,7 +462,7 @@
// THEN the summary is removed, but both children stick around
assertEquals(
new ArraySet<>(List.of(entry2, entry3)),
- new ArraySet<>(mCollection.getActiveNotifs()));
+ new ArraySet<>(mCollection.getAllNotifs()));
assertEquals(NOT_DISMISSED, entry2.getDismissState());
assertEquals(NOT_DISMISSED, entry3.getDismissState());
}
@@ -561,7 +571,7 @@
}
@Test
- public void testEndDismissInterceptionUpdatesDismissInterceptors() throws RemoteException {
+ public void testEndDismissInterceptionUpdatesDismissInterceptors() {
// GIVEN a collection with notifications with multiple dismiss interceptors
mInterceptor1.shouldInterceptDismissal = true;
mInterceptor2.shouldInterceptDismissal = true;
@@ -592,7 +602,7 @@
@Test(expected = IllegalStateException.class)
- public void testEndingDismissalOfNonInterceptedThrows() throws RemoteException {
+ public void testEndingDismissalOfNonInterceptedThrows() {
// GIVEN a collection with notifications with a dismiss interceptor that hasn't been called
mInterceptor1.shouldInterceptDismissal = false;
mCollection.addNotificationDismissInterceptor(mInterceptor1);
@@ -820,7 +830,7 @@
verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK);
// THEN the entry is not removed
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
@@ -841,7 +851,7 @@
// GIVEN a notification gets lifetime-extended by one of them
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the last active extender expires (but new ones become active)
@@ -856,7 +866,7 @@
verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
// THEN the entry is not removed
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
@@ -878,7 +888,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN one (but not all) of the extenders expires
@@ -886,7 +896,7 @@
mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
// THEN the entry is not removed
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
// THEN we don't re-query the extenders
verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
@@ -894,7 +904,7 @@
verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL);
// THEN the entry properly records all extenders that returned true
- assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders);
+ assertEquals(singletonList(mExtender1), entry2.mLifetimeExtenders);
}
@Test
@@ -913,7 +923,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN all of the active extenders expire
@@ -923,7 +933,7 @@
mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
// THEN the entry removed
- assertFalse(mCollection.getActiveNotifs().contains(entry2));
+ assertFalse(mCollection.getAllNotifs().contains(entry2));
verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN);
}
@@ -943,7 +953,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the notification is reposted
@@ -954,7 +964,7 @@
verify(mExtender2).cancelLifetimeExtension(entry2);
// THEN the notification is still present
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
}
@Test(expected = IllegalStateException.class)
@@ -973,7 +983,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
@@ -1002,7 +1012,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getActiveNotifs().contains(entry2));
+ assertTrue(mCollection.getAllNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the notification is reposted
@@ -1055,11 +1065,11 @@
// WHEN both notifications are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair(entry1, defaultStats(entry1)),
- new Pair(entry2, defaultStats(entry2))));
+ List.of(new Pair<>(entry1, defaultStats(entry1)),
+ new Pair<>(entry2, defaultStats(entry2))));
// THEN build list is only called one time
- verify(mBuildListener).onBuildList(any(Collection.class));
+ verifyBuiltList(List.of(entry1, entry2));
}
@Test
@@ -1074,8 +1084,8 @@
DismissedByUserStats stats1 = defaultStats(entry1);
DismissedByUserStats stats2 = defaultStats(entry2);
mCollection.dismissNotifications(
- List.of(new Pair(entry1, defaultStats(entry1)),
- new Pair(entry2, defaultStats(entry2))));
+ List.of(new Pair<>(entry1, defaultStats(entry1)),
+ new Pair<>(entry2, defaultStats(entry2))));
// THEN we send the dismissals to system server
verify(mStatusBarService).onNotificationClear(
@@ -1109,8 +1119,8 @@
// WHEN both notifications are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair(entry1, defaultStats(entry1)),
- new Pair(entry2, defaultStats(entry2))));
+ List.of(new Pair<>(entry1, defaultStats(entry1)),
+ new Pair<>(entry2, defaultStats(entry2))));
// THEN the entries are marked as dismissed
assertEquals(DISMISSED, entry1.getDismissState());
@@ -1134,8 +1144,8 @@
// WHEN both notifications are manually dismissed together
mCollection.dismissNotifications(
- List.of(new Pair(entry1, defaultStats(entry1)),
- new Pair(entry2, defaultStats(entry2))));
+ List.of(new Pair<>(entry1, defaultStats(entry1)),
+ new Pair<>(entry2, defaultStats(entry2))));
// THEN all interceptors get checked
verify(mInterceptor1).shouldInterceptDismissal(entry1);
@@ -1162,7 +1172,7 @@
mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
// THEN build list is only called one time
- verify(mBuildListener).onBuildList(any(Collection.class));
+ verifyBuiltList(List.of(entry1, entry2));
}
@Test
@@ -1271,13 +1281,18 @@
NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
}
- public CollectionEvent postNotif(NotificationEntryBuilder builder) {
+ private CollectionEvent postNotif(NotificationEntryBuilder builder) {
clearInvocations(mCollectionListener);
NotifEvent rawEvent = mNoMan.postNotif(builder);
verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture());
return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
}
+ private void verifyBuiltList(Collection<NotificationEntry> list) {
+ verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
+ assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
+ }
+
private static class RecordingCollectionListener implements NotifCollectionListener {
private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>();
@@ -1303,6 +1318,14 @@
public void onEntryCleanUp(NotificationEntry entry) {
}
+ @Override
+ public void onRankingApplied() {
+ }
+
+ @Override
+ public void onRankingUpdate(RankingMap rankingMap) {
+ }
+
public NotificationEntry getEntry(String key) {
if (!mLastSeenEntries.containsKey(key)) {
throw new RuntimeException("Key not found: " + key);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index 67b1aad..407e1e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -223,7 +223,7 @@
.setPkg(TEST_PKG)
.setId(2)
.build();
- when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
+ when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry1, entry2, entry2Other));
// GIVEN that entry2 is currently associated with a foreground service
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
@@ -253,7 +253,7 @@
.setPkg(TEST_PKG)
.setId(2)
.build();
- when(mNotifPipeline.getActiveNotifs()).thenReturn(List.of(entry));
+ when(mNotifPipeline.getAllNotifs()).thenReturn(List.of(entry));
when(mForegroundServiceController.getStandardLayoutKey(0, TEST_PKG))
.thenReturn(entry.getKey());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index e960185..c356e0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -329,10 +329,10 @@
@Test
public void testIconScrollXAfterTranslationAndReset() throws Exception {
mGroupRow.setTranslation(50);
- assertEquals(50, -mGroupRow.getEntry().expandedIcon.getScrollX());
+ assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
mGroupRow.resetTranslation();
- assertEquals(0, mGroupRow.getEntry().expandedIcon.getScrollX());
+ assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index a21a047..2e787ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.when;
import android.app.Notification;
+import android.content.pm.LauncherApps;
import android.os.Handler;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
@@ -61,6 +62,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.icon.IconBuilder;
+import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -245,14 +248,13 @@
mLockscreenUserManager,
pipeline,
stage,
- true, /* allowLongPress */
- mock(KeyguardBypassController.class),
- mock(StatusBarStateController.class),
- mGroupManager,
- mGutsManager,
mNotificationInterruptionStateProvider,
RowInflaterTask::new,
- mExpandableNotificationRowComponentBuilder);
+ mExpandableNotificationRowComponentBuilder,
+ new IconManager(
+ mEntryManager,
+ mock(LauncherApps.class),
+ new IconBuilder(mContext)));
mEntryManager.setUpWithPresenter(mPresenter);
mEntryManager.addNotificationEntryListener(mEntryListener);
@@ -284,7 +286,8 @@
false,
false,
false,
- null);
+ null,
+ false);
mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 5a89fc4..dc3374b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -34,6 +34,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -53,6 +54,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.icon.IconBuilder;
+import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -93,6 +96,7 @@
private final NotifBindPipeline mBindPipeline;
private final NotifCollectionListener mBindPipelineEntryListener;
private final RowContentBindStage mBindStage;
+ private final IconManager mIconManager;
private StatusBarStateController mStatusBarStateController;
public NotificationTestHelper(Context context, TestableDependency dependency) {
@@ -106,6 +110,10 @@
mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
mock(ConfigurationControllerImpl.class));
mGroupManager.setHeadsUpManager(mHeadsUpManager);
+ mIconManager = new IconManager(
+ mock(CommonNotifCollection.class),
+ mock(LauncherApps.class),
+ new IconBuilder(mContext));
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
@@ -377,7 +385,7 @@
.build();
entry.setRow(row);
- entry.createIcons(mContext, entry.getSbn());
+ mIconManager.createIcons(entry);
row.setEntry(entry);
mBindPipelineEntryListener.onEntryInit(entry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
index 83e89bd..5320ecd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,6 +28,7 @@
import android.app.IActivityManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -110,4 +112,13 @@
public void testSetForcePluginOpen_beforeStatusBarInitialization() {
mNotificationShadeWindowController.setForcePluginOpen(true);
}
+
+ @Test
+ public void setBackgroundBlurRadius_expandedWithBlurs() {
+ mNotificationShadeWindowController.setBackgroundBlurRadius(10);
+ verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE));
+
+ mNotificationShadeWindowController.setBackgroundBlurRadius(0);
+ verify(mNotificationShadeWindowView).setVisibility(eq(View.INVISIBLE));
+ }
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
index 8b8b9e5..48be0d9 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
@@ -64,16 +64,26 @@
dest.writeInt(mTetheringType);
}
+ /**
+ * Get the MAC address used to identify the client.
+ */
@NonNull
public MacAddress getMacAddress() {
return mMacAddress;
}
+ /**
+ * Get information on the list of addresses that are associated with the client.
+ */
@NonNull
public List<AddressInfo> getAddresses() {
return new ArrayList<>(mAddresses);
}
+ /**
+ * Get the type of tethering used by the client.
+ * @return one of the {@code TetheringManager#TETHERING_*} constants.
+ */
public int getTetheringType() {
return mTetheringType;
}
@@ -115,45 +125,47 @@
private final LinkAddress mAddress;
@Nullable
private final String mHostname;
- // TODO: use LinkAddress expiration time once it is supported
- private final long mExpirationTime;
/** @hide */
public AddressInfo(@NonNull LinkAddress address, @Nullable String hostname) {
- this(address, hostname, 0);
- }
-
- /** @hide */
- public AddressInfo(@NonNull LinkAddress address, String hostname, long expirationTime) {
this.mAddress = address;
this.mHostname = hostname;
- this.mExpirationTime = expirationTime;
}
private AddressInfo(Parcel in) {
- this(in.readParcelable(null), in.readString(), in.readLong());
+ this(in.readParcelable(null), in.readString());
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mAddress, flags);
dest.writeString(mHostname);
- dest.writeLong(mExpirationTime);
}
+ /**
+ * Get the link address (including prefix length and lifetime) used by the client.
+ *
+ * This may be an IPv4 or IPv6 address.
+ */
@NonNull
public LinkAddress getAddress() {
return mAddress;
}
+ /**
+ * Get the hostname that was advertised by the client when obtaining its address, if any.
+ */
@Nullable
public String getHostname() {
return mHostname;
}
- /** @hide TODO: use expiration time in LinkAddress */
+ /**
+ * Get the expiration time of the address assigned to the client.
+ * @hide
+ */
public long getExpirationTime() {
- return mExpirationTime;
+ return mAddress.getExpirationTime();
}
@Override
@@ -163,7 +175,7 @@
@Override
public int hashCode() {
- return Objects.hash(mAddress, mHostname, mExpirationTime);
+ return Objects.hash(mAddress, mHostname);
}
@Override
@@ -173,8 +185,7 @@
// Use .equals() for addresses as all changes, including address expiry changes,
// should be included.
return other.mAddress.equals(mAddress)
- && Objects.equals(mHostname, other.mHostname)
- && mExpirationTime == other.mExpirationTime;
+ && Objects.equals(mHostname, other.mHostname);
}
@NonNull
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 4af5c53..04d6215 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -157,4 +157,49 @@
<!-- ComponentName of the service used to run no ui tether provisioning. -->
<string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
+
+ <!-- Enable tethering notification -->
+ <!-- Icons for showing tether enable notification.
+ Each item should have two elements and be separated with ";".
+
+ The first element is downstream types which is one of tethering. This element has to be
+ made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
+ types and use "," to separate each combinations. Such as
+
+ USB|BT,WIFI|USB|BT
+
+ The second element is icon for the item. This element has to be composed by
+ <package name>:drawable/<resource name>. Such as
+
+ 1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
+ 2. android:drawable/xxx
+
+ So the entire string of each item would be
+
+ USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
+
+ NOTE: One config can be separated into two or more for readability. Such as
+
+ WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
+
+ can be separated into
+
+ WIFI|USB;android:drawable/xxx
+ WIFI|BT;android:drawable/xxx
+ USB|BT;android:drawable/xxx
+ WIFI|USB|BT;android:drawable/xxx
+
+ Notification will not show if the downstream type isn't listed in array.
+ Empty array means disable notifications. -->
+ <!-- In AOSP, hotspot is configured to no notification by default. Because status bar has showed
+ an icon on the right side already -->
+ <string-array translatable="false" name="tethering_notification_icons">
+ <item>USB;com.android.networkstack.tethering:drawable/stat_sys_tether_usb</item>
+ <item>BT;com.android.networkstack.tethering:drawable/stat_sys_tether_bluetooth</item>
+ <item>WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general</item>
+ </string-array>
+ <!-- String for tether enable notification title. -->
+ <string name="tethering_notification_title">@string/tethered_notification_title</string>
+ <!-- String for tether enable notification message. -->
+ <string name="tethering_notification_message">@string/tethered_notification_message</string>
</resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index fe025c7..bbba3f3 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -16,6 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<overlayable name="TetheringConfig">
<policy type="product|system|vendor">
+ <!-- Params from config.xml that can be overlaid -->
<item type="array" name="config_tether_usb_regexs"/>
<item type="array" name="config_tether_ncm_regexs" />
<item type="array" name="config_tether_wifi_regexs"/>
@@ -31,6 +32,45 @@
<item type="string" name="config_mobile_hotspot_provision_response"/>
<item type="integer" name="config_mobile_hotspot_provision_check_period"/>
<item type="string" name="config_wifi_tether_enable"/>
+ <!-- Configuration values for TetheringNotificationUpdater -->
+ <!-- Icons for showing tether enable notification.
+ Each item should have two elements and be separated with ";".
+
+ The first element is downstream types which is one of tethering. This element has to be
+ made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
+ types and use "," to separate each combinations. Such as
+
+ USB|BT,WIFI|USB|BT
+
+ The second element is icon for the item. This element has to be composed by
+ <package name>:drawable/<resource name>. Such as
+
+ 1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
+ 2. android:drawable/xxx
+
+ So the entire string of each item would be
+
+ USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
+
+ NOTE: One config can be separated into two or more for readability. Such as
+
+ WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
+
+ can be separated into
+
+ WIFI|USB;android:drawable/xxx
+ WIFI|BT;android:drawable/xxx
+ USB|BT;android:drawable/xxx
+ WIFI|USB|BT;android:drawable/xxx
+
+ Notification will not show if the downstream type isn't listed in array.
+ Empty array means disable notifications. -->
+ <item type="array" name="tethering_notification_icons"/>
+ <!-- String for tether enable notification title. -->
+ <item type="string" name="tethering_notification_title"/>
+ <!-- String for tether enable notification message. -->
+ <item type="string" name="tethering_notification_message"/>
+ <!-- Params from config.xml that can be overlaid -->
</policy>
</overlayable>
</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index 792bce9..ba98a66 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -15,19 +15,21 @@
-->
<resources>
<!-- Shown when the device is tethered -->
- <!-- Strings for tethered notification title [CHAR LIMIT=200] -->
+ <!-- String for tethered notification title [CHAR LIMIT=200] -->
<string name="tethered_notification_title">Tethering or hotspot active</string>
- <!-- Strings for tethered notification message [CHAR LIMIT=200] -->
+ <!-- String for tethered notification message [CHAR LIMIT=200] -->
<string name="tethered_notification_message">Tap to set up.</string>
<!-- This notification is shown when tethering has been disabled on a user's device.
The device is managed by the user's employer. Tethering can't be turned on unless the
IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
- <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
+ <!-- String for tether disabling notification title [CHAR LIMIT=200] -->
<string name="disable_tether_notification_title">Tethering is disabled</string>
- <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
+ <!-- String for tether disabling notification message [CHAR LIMIT=200] -->
<string name="disable_tether_notification_message">Contact your admin for details</string>
- <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+ <!-- This string should be consistent with the "Hotspot & tethering" text in the "Network and
+ Internet" settings page. That is currently the tether_settings_title_all string. -->
+ <!-- String for tether notification channel name [CHAR LIMIT=200] -->
<string name="notification_channel_tethering_status">Hotspot & tethering status</string>
</resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 38f8609..6c0c432 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -24,6 +24,7 @@
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
import static android.net.util.NetworkConstants.asByte;
import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import android.net.INetd;
import android.net.INetworkStackStatusCallback;
@@ -448,7 +449,9 @@
final ArrayList<TetheredClient> leases = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
- intToInet4AddressHTH(lease.netAddr), lease.prefixLength);
+ intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
+ 0 /* flags */, RT_SCOPE_UNIVERSE /* as per RFC6724#3.2 */,
+ lease.expTime /* deprecationTime */, lease.expTime /* expirationTime */);
final MacAddress macAddress;
try {
@@ -460,7 +463,7 @@
}
final TetheredClient.AddressInfo addressInfo = new TetheredClient.AddressInfo(
- address, lease.hostname, lease.expTime);
+ address, lease.hostname);
leases.add(new TetheredClient(
macAddress,
Collections.singletonList(addressInfo),
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index f89da84..3d8dbab 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -59,10 +59,8 @@
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
+import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
+
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
@@ -72,7 +70,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.EthernetManager;
@@ -128,7 +125,6 @@
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.networkstack.tethering.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -224,14 +220,13 @@
private final ActiveDataSubIdListener mActiveDataSubIdListener;
private final ConnectedClientsTracker mConnectedClientsTracker;
private final TetheringThreadExecutor mExecutor;
+ private final TetheringNotificationUpdater mNotificationUpdater;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
// All the usage of mTetheringEventCallback should run in the same thread.
private ITetheringEventCallback mTetheringEventCallback = null;
private volatile TetheringConfiguration mConfig;
private InterfaceSet mCurrentUpstreamIfaceSet;
- private Notification.Builder mTetheredNotificationBuilder;
- private int mLastNotificationId;
private boolean mRndisEnabled; // track the RNDIS function enabled state
// True iff. WiFi tethering should be started when soft AP is ready.
@@ -255,6 +250,7 @@
mContext = mDeps.getContext();
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
+ mNotificationUpdater = mDeps.getNotificationUpdater(mContext);
mPublicSync = new Object();
@@ -738,13 +734,10 @@
final ArrayList<String> erroredList = new ArrayList<>();
final ArrayList<Integer> lastErrorList = new ArrayList<>();
- boolean wifiTethered = false;
- boolean usbTethered = false;
- boolean bluetoothTethered = false;
-
final TetheringConfiguration cfg = mConfig;
mTetherStatesParcel = new TetherStatesParcel();
+ int downstreamTypesMask = DOWNSTREAM_NONE;
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
TetherState tetherState = mTetherStates.valueAt(i);
@@ -758,11 +751,11 @@
localOnlyList.add(iface);
} else if (tetherState.lastState == IpServer.STATE_TETHERED) {
if (cfg.isUsb(iface)) {
- usbTethered = true;
+ downstreamTypesMask |= (1 << TETHERING_USB);
} else if (cfg.isWifi(iface)) {
- wifiTethered = true;
+ downstreamTypesMask |= (1 << TETHERING_WIFI);
} else if (cfg.isBluetooth(iface)) {
- bluetoothTethered = true;
+ downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
}
tetherList.add(iface);
}
@@ -796,98 +789,7 @@
"error", TextUtils.join(",", erroredList)));
}
- if (usbTethered) {
- if (wifiTethered || bluetoothTethered) {
- showTetheredNotification(R.drawable.stat_sys_tether_general);
- } else {
- showTetheredNotification(R.drawable.stat_sys_tether_usb);
- }
- } else if (wifiTethered) {
- if (bluetoothTethered) {
- showTetheredNotification(R.drawable.stat_sys_tether_general);
- } else {
- /* We now have a status bar icon for WifiTethering, so drop the notification */
- clearTetheredNotification();
- }
- } else if (bluetoothTethered) {
- showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
- } else {
- clearTetheredNotification();
- }
- }
-
- private void showTetheredNotification(int id) {
- showTetheredNotification(id, true);
- }
-
- @VisibleForTesting
- protected void showTetheredNotification(int id, boolean tetheringOn) {
- NotificationManager notificationManager =
- (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (notificationManager == null) {
- return;
- }
- final NotificationChannel channel = new NotificationChannel(
- "TETHERING_STATUS",
- mContext.getResources().getString(R.string.notification_channel_tethering_status),
- NotificationManager.IMPORTANCE_LOW);
- notificationManager.createNotificationChannel(channel);
-
- if (mLastNotificationId != 0) {
- if (mLastNotificationId == id) {
- return;
- }
- notificationManager.cancel(null, mLastNotificationId);
- mLastNotificationId = 0;
- }
-
- Intent intent = new Intent();
- intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-
- PendingIntent pi = PendingIntent.getActivity(
- mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
-
- Resources r = mContext.getResources();
- final CharSequence title;
- final CharSequence message;
-
- if (tetheringOn) {
- title = r.getText(R.string.tethered_notification_title);
- message = r.getText(R.string.tethered_notification_message);
- } else {
- title = r.getText(R.string.disable_tether_notification_title);
- message = r.getText(R.string.disable_tether_notification_message);
- }
-
- if (mTetheredNotificationBuilder == null) {
- mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
- mTetheredNotificationBuilder.setWhen(0)
- .setOngoing(true)
- .setColor(mContext.getColor(
- android.R.color.system_notification_accent_color))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setCategory(Notification.CATEGORY_STATUS);
- }
- mTetheredNotificationBuilder.setSmallIcon(id)
- .setContentTitle(title)
- .setContentText(message)
- .setContentIntent(pi);
- mLastNotificationId = id;
-
- notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
- }
-
- @VisibleForTesting
- protected void clearTetheredNotification() {
- NotificationManager notificationManager =
- (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
- .getSystemService(Context.NOTIFICATION_SERVICE);
- if (notificationManager != null && mLastNotificationId != 0) {
- notificationManager.cancel(null, mLastNotificationId);
- mLastNotificationId = 0;
- }
+ mNotificationUpdater.onDownstreamChanged(downstreamTypesMask);
}
private class StateReceiver extends BroadcastReceiver {
@@ -1081,12 +983,10 @@
return;
}
- mWrapper.clearTetheredNotification();
+ // TODO: Add user restrictions notification.
final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0);
if (newlyDisallowed && isTetheringActiveOnDevice) {
- mWrapper.showTetheredNotification(
- R.drawable.stat_sys_tether_general, false);
mWrapper.untetherAll();
// TODO(b/148139325): send tetheringSupported on restriction change
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index e019c3a..0330dad 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -26,6 +26,8 @@
import android.os.IBinder;
import android.os.Looper;
+import androidx.annotation.NonNull;
+
import com.android.internal.util.StateMachine;
import java.util.ArrayList;
@@ -102,6 +104,13 @@
}
/**
+ * Get a reference to the TetheringNotificationUpdater to be used by tethering.
+ */
+ public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx) {
+ return new TetheringNotificationUpdater(ctx);
+ }
+
+ /**
* Get tethering thread looper.
*/
public abstract Looper getTetheringLooper();
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
new file mode 100644
index 0000000..b97f752
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.ArrayRes;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
+
+/**
+ * A class to display tethering-related notifications.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the tethering handler
+ * thread. However the constructor is an exception, as it is called on another thread ;
+ * therefore for thread safety all members of this class MUST either be final or initialized
+ * to their default value (0, false or null).
+ *
+ * @hide
+ */
+public class TetheringNotificationUpdater {
+ private static final String TAG = TetheringNotificationUpdater.class.getSimpleName();
+ private static final String CHANNEL_ID = "TETHERING_STATUS";
+ private static final boolean NOTIFY_DONE = true;
+ private static final boolean NO_NOTIFY = false;
+ // Id to update and cancel tethering notification. Must be unique within the tethering app.
+ private static final int NOTIFY_ID = 20191115;
+ @VisibleForTesting
+ static final int NO_ICON_ID = 0;
+ @VisibleForTesting
+ static final int DOWNSTREAM_NONE = 0;
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private final NotificationChannel mChannel;
+ // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2.
+ // This value has to be made 1 2 and 4, and OR'd with the others.
+ // WARNING : the constructor is called on a different thread. Thread safety therefore
+ // relies on this value being initialized to 0, and not any other value. If you need
+ // to change this, you will need to change the thread where the constructor is invoked,
+ // or to introduce synchronization.
+ private int mDownstreamTypesMask = DOWNSTREAM_NONE;
+
+ public TetheringNotificationUpdater(@NonNull final Context context) {
+ mContext = context;
+ mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ mChannel = new NotificationChannel(
+ CHANNEL_ID,
+ context.getResources().getString(R.string.notification_channel_tethering_status),
+ NotificationManager.IMPORTANCE_LOW);
+ mNotificationManager.createNotificationChannel(mChannel);
+ }
+
+ /** Called when downstream has changed */
+ public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) {
+ if (mDownstreamTypesMask == downstreamTypesMask) return;
+ mDownstreamTypesMask = downstreamTypesMask;
+ updateNotification();
+ }
+
+ private void updateNotification() {
+ final boolean tetheringInactive = mDownstreamTypesMask <= DOWNSTREAM_NONE;
+
+ if (tetheringInactive || setupNotification() == NO_NOTIFY) {
+ clearNotification();
+ }
+ }
+
+ private void clearNotification() {
+ mNotificationManager.cancel(null /* tag */, NOTIFY_ID);
+ }
+
+ /**
+ * Returns the downstream types mask which convert from given string.
+ *
+ * @param types This string has to be made by "WIFI", "USB", "BT", and OR'd with the others.
+ *
+ * @return downstream types mask value.
+ */
+ @IntRange(from = 0, to = 7)
+ private int getDownstreamTypesMask(@NonNull final String types) {
+ int downstreamTypesMask = DOWNSTREAM_NONE;
+ final String[] downstreams = types.split("\\|");
+ for (String downstream : downstreams) {
+ if ("USB".equals(downstream.trim())) {
+ downstreamTypesMask |= (1 << TETHERING_USB);
+ } else if ("WIFI".equals(downstream.trim())) {
+ downstreamTypesMask |= (1 << TETHERING_WIFI);
+ } else if ("BT".equals(downstream.trim())) {
+ downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
+ }
+ }
+ return downstreamTypesMask;
+ }
+
+ /**
+ * Returns the icons {@link android.util.SparseArray} which get from given string-array resource
+ * id.
+ *
+ * @param id String-array resource id
+ *
+ * @return {@link android.util.SparseArray} with downstream types and icon id info.
+ */
+ @NonNull
+ private SparseArray<Integer> getIcons(@ArrayRes int id) {
+ final Resources res = mContext.getResources();
+ final String[] array = res.getStringArray(id);
+ final SparseArray<Integer> icons = new SparseArray<>();
+ for (String config : array) {
+ if (TextUtils.isEmpty(config)) continue;
+
+ final String[] elements = config.split(";");
+ if (elements.length != 2) {
+ Log.wtf(TAG,
+ "Unexpected format in Tethering notification configuration : " + config);
+ continue;
+ }
+
+ final String[] types = elements[0].split(",");
+ for (String type : types) {
+ int mask = getDownstreamTypesMask(type);
+ if (mask == DOWNSTREAM_NONE) continue;
+ icons.put(mask, res.getIdentifier(
+ elements[1].trim(), null /* defType */, null /* defPackage */));
+ }
+ }
+ return icons;
+ }
+
+ private boolean setupNotification() {
+ final Resources res = mContext.getResources();
+ final SparseArray<Integer> downstreamIcons = getIcons(R.array.tethering_notification_icons);
+
+ final int iconId = downstreamIcons.get(mDownstreamTypesMask, NO_ICON_ID);
+ if (iconId == NO_ICON_ID) return NO_NOTIFY;
+
+ final String title = res.getString(R.string.tethering_notification_title);
+ final String message = res.getString(R.string.tethering_notification_message);
+
+ showNotification(iconId, title, message);
+ return NOTIFY_DONE;
+ }
+
+ private void showNotification(@DrawableRes final int iconId, @NonNull final String title,
+ @NonNull final String message) {
+ final Intent intent = new Intent(Settings.ACTION_TETHER_SETTINGS);
+ final PendingIntent pi = PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0),
+ 0 /* requestCode */, intent, 0 /* flags */, null /* options */);
+ final Notification notification =
+ new Notification.Builder(mContext, mChannel.getId())
+ .setSmallIcon(iconId)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setOngoing(true)
+ .setColor(mContext.getColor(
+ android.R.color.system_notification_accent_color))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setCategory(Notification.CATEGORY_STATUS)
+ .setContentIntent(pi)
+ .build();
+
+ mNotificationManager.notify(null /* tag */, NOTIFY_ID, notification);
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt b/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
index d85389a..a20a0df 100644
--- a/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
+++ b/packages/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
@@ -20,6 +20,7 @@
import android.net.TetheredClient.AddressInfo
import android.net.TetheringManager.TETHERING_BLUETOOTH
import android.net.TetheringManager.TETHERING_USB
+import android.system.OsConstants.RT_SCOPE_UNIVERSE
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertParcelSane
@@ -30,11 +31,19 @@
private val TEST_MACADDR = MacAddress.fromBytes(byteArrayOf(12, 23, 34, 45, 56, 67))
private val TEST_OTHER_MACADDR = MacAddress.fromBytes(byteArrayOf(23, 34, 45, 56, 67, 78))
-private val TEST_ADDR1 = LinkAddress(parseNumericAddress("192.168.113.3"), 24)
-private val TEST_ADDR2 = LinkAddress(parseNumericAddress("fe80::1:2:3"), 64)
+private val TEST_ADDR1 = makeLinkAddress("192.168.113.3", prefixLength = 24, expTime = 123L)
+private val TEST_ADDR2 = makeLinkAddress("fe80::1:2:3", prefixLength = 64, expTime = 456L)
private val TEST_ADDRINFO1 = AddressInfo(TEST_ADDR1, "test_hostname")
private val TEST_ADDRINFO2 = AddressInfo(TEST_ADDR2, null)
+private fun makeLinkAddress(addr: String, prefixLength: Int, expTime: Long) = LinkAddress(
+ parseNumericAddress(addr),
+ prefixLength,
+ 0 /* flags */,
+ RT_SCOPE_UNIVERSE,
+ expTime /* deprecationTime */,
+ expTime /* expirationTime */)
+
@RunWith(AndroidJUnit4::class)
@SmallTest
class TetheredClientTest {
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
index 56f3e21..1cdc3bb 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/ConnectedClientsTrackerTest.kt
@@ -46,23 +46,28 @@
private val client1Addr = MacAddress.fromString("01:23:45:67:89:0A")
private val client1 = TetheredClient(client1Addr, listOf(
- AddressInfo(LinkAddress("192.168.43.44/32"), null /* hostname */, clock.time + 20)),
+ makeAddrInfo("192.168.43.44/32", null /* hostname */, clock.time + 20)),
TETHERING_WIFI)
private val wifiClient1 = makeWifiClient(client1Addr)
private val client2Addr = MacAddress.fromString("02:34:56:78:90:AB")
- private val client2Exp30AddrInfo = AddressInfo(
- LinkAddress("192.168.43.45/32"), "my_hostname", clock.time + 30)
+ private val client2Exp30AddrInfo = makeAddrInfo(
+ "192.168.43.45/32", "my_hostname", clock.time + 30)
private val client2 = TetheredClient(client2Addr, listOf(
client2Exp30AddrInfo,
- AddressInfo(LinkAddress("2001:db8:12::34/72"), "other_hostname", clock.time + 10)),
+ makeAddrInfo("2001:db8:12::34/72", "other_hostname", clock.time + 10)),
TETHERING_WIFI)
private val wifiClient2 = makeWifiClient(client2Addr)
private val client3Addr = MacAddress.fromString("03:45:67:89:0A:BC")
private val client3 = TetheredClient(client3Addr,
- listOf(AddressInfo(LinkAddress("2001:db8:34::34/72"), "other_other_hostname",
- clock.time + 10)),
+ listOf(makeAddrInfo("2001:db8:34::34/72", "other_other_hostname", clock.time + 10)),
TETHERING_USB)
+ private fun makeAddrInfo(addr: String, hostname: String?, expTime: Long) =
+ LinkAddress(addr).let {
+ AddressInfo(LinkAddress(it.address, it.prefixLength, it.flags, it.scope,
+ expTime /* deprecationTime */, expTime /* expirationTime */), hostname)
+ }
+
@Test
fun testUpdateConnectedClients() {
doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index af7ad66..820c852 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -46,6 +46,8 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -53,7 +55,6 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
@@ -188,6 +189,7 @@
@Mock private NetworkRequest mNetworkRequest;
@Mock private ConnectivityManager mCm;
@Mock private EthernetManager mEm;
+ @Mock private TetheringNotificationUpdater mNotificationUpdater;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -207,6 +209,7 @@
private PhoneStateListener mPhoneStateListener;
private InterfaceConfigurationParcel mInterfaceConfiguration;
+
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
super(base);
@@ -249,11 +252,6 @@
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
return super.getSystemServiceName(serviceClass);
}
-
- @Override
- public Context createContextAsUser(UserHandle user, int flags) {
- return mContext;
- }
}
public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -315,12 +313,10 @@
public class MockTetheringDependencies extends TetheringDependencies {
StateMachine mUpstreamNetworkMonitorMasterSM;
ArrayList<IpServer> mIpv6CoordinatorNotifyList;
- int mIsTetheringSupportedCalls;
public void reset() {
mUpstreamNetworkMonitorMasterSM = null;
mIpv6CoordinatorNotifyList = null;
- mIsTetheringSupportedCalls = 0;
}
@Override
@@ -354,7 +350,6 @@
@Override
public boolean isTetheringSupported() {
- mIsTetheringSupportedCalls++;
return true;
}
@@ -384,6 +379,11 @@
// TODO: add test for bluetooth tethering.
return null;
}
+
+ @Override
+ public TetheringNotificationUpdater getNotificationUpdater(Context ctx) {
+ return mNotificationUpdater;
+ }
}
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -472,7 +472,6 @@
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
mServiceContext = new TestContext(mContext);
- when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mIntents = new Vector<>();
@@ -605,7 +604,8 @@
// it creates a IpServer and sends out a broadcast indicating that the
// interface is "available".
if (emulateInterfaceStatusChanged) {
- assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
+ // There is 1 IpServer state change event: STATE_AVAILABLE
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -689,9 +689,8 @@
verifyNoMoreInteractions(mWifiManager);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
- // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
- // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
- assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
+ // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
+ verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
// Emulate externally-visible WifiManager effects, when hotspot mode
// is being torn down.
@@ -917,7 +916,8 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
mLooper.dispatchAll();
- assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls);
+ // There is 1 IpServer state change event: STATE_AVAILABLE
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
@@ -961,9 +961,9 @@
// In tethering mode, in the default configuration, an explicit request
// for a mobile network is also made.
verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
- // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
- // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
- assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
+ // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_TETHERED
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
/////
// We do not currently emulate any upstream being found.
@@ -1034,9 +1034,10 @@
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
- // There are 3 state change event:
- // AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
- assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
+ // There are 3 IpServer state change event:
+ // STATE_AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
+ verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
+ verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
// This is called, but will throw.
verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
@@ -1071,9 +1072,6 @@
ural.onUserRestrictionsChanged();
verify(mockTethering, times(expectedInteractionsWithShowNotification))
- .showTetheredNotification(anyInt(), eq(false));
-
- verify(mockTethering, times(expectedInteractionsWithShowNotification))
.untetherAll();
}
@@ -1429,9 +1427,8 @@
verifyNoMoreInteractions(mNetd);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
- // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
- // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
- assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
+ // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
+ verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 5d699c0..5d97d21 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -1232,6 +1232,7 @@
*/
public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
@NonNull Region outRegion) {
+ windowId = resolveParentWindowIdLocked(windowId);
final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
if (observer != null) {
return observer.computePartialInteractiveRegionForWindowLocked(windowId, outRegion);
@@ -1436,6 +1437,7 @@
*/
@Nullable
public WindowInfo findWindowInfoByIdLocked(int windowId) {
+ windowId = resolveParentWindowIdLocked(windowId);
final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
if (observer != null) {
return observer.findWindowInfoByIdLocked(windowId);
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
index 3376f2b..5de8171 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
@@ -62,7 +62,7 @@
* side flow.
*
* <p>
- * This class is thread safe.
+ * This class should hold the same lock as {@link Session} as they call into each other.
*/
final class InlineSuggestionSession {
@@ -105,12 +105,12 @@
private boolean mImeInputViewStarted = false;
InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
- int userId, ComponentName componentName, Handler handler) {
+ int userId, ComponentName componentName, Handler handler, Object lock) {
mInputMethodManagerInternal = inputMethodManagerInternal;
mUserId = userId;
mComponentName = componentName;
mHandler = handler;
- mLock = new Object();
+ mLock = lock;
mImeStatusListener = new ImeStatusListener() {
@Override
public void onInputMethodStartInputView(AutofillId imeFieldId) {
@@ -261,29 +261,27 @@
mHandler = handler;
mTimeoutCallback = () -> {
Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
- synchronized (mLock) {
- completeIfNotLocked(null);
- }
+ completeIfNot(null);
};
mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
}
- private void completeIfNotLocked(@Nullable ImeResponse response) {
- if (mResponse.isDone()) {
- return;
+ private void completeIfNot(@Nullable ImeResponse response) {
+ synchronized (mLock) {
+ if (mResponse.isDone()) {
+ return;
+ }
+ mResponse.complete(response);
+ mRequestConsumer.accept(response == null ? null : response.mRequest);
+ mHandler.removeCallbacks(mTimeoutCallback);
}
- mResponse.complete(response);
- mRequestConsumer.accept(response == null ? null : response.mRequest);
- mHandler.removeCallbacks(mTimeoutCallback);
}
@BinderThread
@Override
public void onInlineSuggestionsUnsupported() throws RemoteException {
if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
- synchronized (mLock) {
- completeIfNotLocked(null);
- }
+ completeIfNot(null);
}
@BinderThread
@@ -302,13 +300,9 @@
mImeStatusListener.onInputMethodFinishInputView(imeFieldId);
}
if (request != null && callback != null) {
- synchronized (mLock) {
- completeIfNotLocked(new ImeResponse(request, callback));
- }
+ completeIfNot(new ImeResponse(request, callback));
} else {
- synchronized (mLock) {
- completeIfNotLocked(null);
- }
+ completeIfNot(null);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 5064663..de31118 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -756,7 +756,7 @@
setClientLocked(client);
mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
- componentName, handler);
+ componentName, handler, mLock);
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dadcd4e..9fddafb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -991,4 +991,9 @@
* @param enabled true if visibility blocks should be logged
*/
public abstract void setVisibilityLogging(String packageName, boolean enabled);
+
+ /**
+ * Returns if a package name is a valid system package.
+ */
+ public abstract boolean isSystemPackage(@NonNull String packageName);
}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index b464422..52a1b5a 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -354,6 +354,10 @@
*/
public void onPackageFailure(List<VersionedPackage> packages,
@FailureReasons int failureReason) {
+ if (packages == null) {
+ Slog.w(TAG, "Could not resolve a list of failing packages");
+ return;
+ }
mLongTaskHandler.post(() -> {
synchronized (mLock) {
if (mAllObservers.isEmpty()) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b7d050a..2c220d3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -154,7 +154,6 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.SystemService.TargetUser;
import com.android.server.pm.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
@@ -233,6 +232,8 @@
private static final String FUSE_ENABLED = "fuse_enabled";
private static final boolean DEFAULT_FUSE_ENABLED = true;
+ private final Set<Integer> mFuseMountedUser = new ArraySet<>();
+
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -1497,6 +1498,9 @@
@GuardedBy("mLock")
private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+ if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
+ mFuseMountedUser.remove(vol.getMountUserId());
+ }
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
if (!TextUtils.isEmpty(vol.fsUuid)) {
@@ -2075,39 +2079,86 @@
mount(vol);
}
+ private void remountAppStorageDirs(Map<Integer, String> pidPkgMap, int userId) {
+ for (Entry<Integer, String> entry : pidPkgMap.entrySet()) {
+ final int pid = entry.getKey();
+ final String packageName = entry.getValue();
+ Slog.i(TAG, "Remounting storage for pid: " + pid);
+ final String[] sharedPackages =
+ mPmInternal.getSharedUserPackagesForPackage(packageName, userId);
+ final int uid = mPmInternal.getPackageUidInternal(packageName, 0, userId);
+ final String[] packages =
+ sharedPackages.length != 0 ? sharedPackages : new String[]{packageName};
+ try {
+ mVold.remountAppStorageDirs(uid, pid, packages);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
+
private void mount(VolumeInfo vol) {
try {
// TODO(b/135341433): Remove paranoid logging when FUSE is stable
Slog.i(TAG, "Mounting volume " + vol);
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
- @Override
- public boolean onVolumeChecking(FileDescriptor fd, String path,
- String internalPath) {
- vol.path = path;
- vol.internalPath = internalPath;
- ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
- try {
- mStorageSessionController.onVolumeMount(pfd, vol);
- return true;
- } catch (ExternalStorageServiceException e) {
- Slog.e(TAG, "Failed to mount volume " + vol, e);
+ @Override
+ public boolean onVolumeChecking(FileDescriptor fd, String path,
+ String internalPath) {
+ vol.path = path;
+ vol.internalPath = internalPath;
+ ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
+ try {
+ mStorageSessionController.onVolumeMount(pfd, vol);
+ return true;
+ } catch (ExternalStorageServiceException e) {
+ Slog.e(TAG, "Failed to mount volume " + vol, e);
- int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2;
- Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
- mHandler.removeMessages(H_RESET);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
- TimeUnit.SECONDS.toMillis(nextResetSeconds));
- return false;
- } finally {
- try {
- pfd.close();
- } catch (Exception e) {
- Slog.e(TAG, "Failed to close FUSE device fd", e);
- }
+ int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2;
+ Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s");
+ mHandler.removeMessages(H_RESET);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
+ TimeUnit.SECONDS.toMillis(nextResetSeconds));
+ return false;
+ } finally {
+ try {
+ pfd.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to close FUSE device fd", e);
}
}
- });
+ }
+ });
Slog.i(TAG, "Mounted volume " + vol);
+ if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ final int userId = vol.getMountUserId();
+ mFuseMountedUser.add(userId);
+ // Async remount app storage so it won't block the main thread.
+ new Thread(() -> {
+ Map<Integer, String> pidPkgMap = null;
+ // getProcessesWithPendingBindMounts() could fail when a new app process is
+ // starting and it's not planning to mount storage dirs in zygote, but it's
+ // rare, so we retry 5 times and hope we can get the result successfully.
+ for (int i = 0; i < 5; i++) {
+ try {
+ pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
+ .getProcessesWithPendingBindMounts(vol.getMountUserId());
+ break;
+ } catch (IllegalStateException e) {
+ Slog.i(TAG, "Some processes are starting, retry");
+ // Wait 100ms and retry so hope the pending process is started.
+ SystemClock.sleep(100);
+ }
+ }
+ if (pidPkgMap != null) {
+ remountAppStorageDirs(pidPkgMap, userId);
+ } else {
+ Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
+ + " 5 retries");
+ }
+
+ }).start();
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -4309,7 +4360,8 @@
pw.println();
pw.println("mObbPathToStateMap:");
pw.increaseIndent();
- final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
+ final Iterator<Entry<String, ObbState>> maps =
+ mObbPathToStateMap.entrySet().iterator();
while (maps.hasNext()) {
final Entry<String, ObbState> e = maps.next();
pw.print(e.getKey());
@@ -4350,45 +4402,41 @@
}
/**
- * Check if fuse is running in target user, if it's running then setup its obb directories.
- * TODO: System server should store a list of active pids that obb is not mounted and use it.
+ * Check if fuse is running in target user, if it's running then setup its storage dirs.
+ * Return true if storage dirs are mounted.
*/
@Override
- public void prepareObbDirs(int userId, Set<String> packageList, String processName) {
- String fuseRunningUsersList = SystemProperties.get("vold.fuse_running_users", "");
- String[] fuseRunningUsers = fuseRunningUsersList.split(",");
- boolean fuseReady = false;
- String targetUserId = String.valueOf(userId);
- for (String user : fuseRunningUsers) {
- if (targetUserId.equals(user)) {
- fuseReady = true;
- }
+ public boolean prepareStorageDirs(int userId, Set<String> packageList,
+ String processName) {
+ if (!mFuseMountedUser.contains(userId)) {
+ Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
+ return false;
}
- if (fuseReady) {
- try {
- final IVold vold = IVold.Stub.asInterface(
- ServiceManager.getServiceOrThrow("vold"));
- for (String pkg : packageList) {
- final String packageObbDir =
- String.format("/storage/emulated/%d/Android/obb/%s/", userId, pkg);
- final String packageDataDir =
- String.format("/storage/emulated/%d/Android/data/%s/",
- userId, pkg);
+ try {
+ final IVold vold = IVold.Stub.asInterface(
+ ServiceManager.getServiceOrThrow("vold"));
+ for (String pkg : packageList) {
+ final String packageObbDir =
+ String.format("/storage/emulated/%d/Android/obb/%s/", userId, pkg);
+ final String packageDataDir =
+ String.format("/storage/emulated/%d/Android/data/%s/",
+ userId, pkg);
- // Create package obb and data dir if it doesn't exist.
- File file = new File(packageObbDir);
- if (!file.exists()) {
- vold.setupAppDir(packageObbDir, mPmInternal.getPackage(pkg).getUid());
- }
- file = new File(packageDataDir);
- if (!file.exists()) {
- vold.setupAppDir(packageDataDir, mPmInternal.getPackage(pkg).getUid());
- }
+ // Create package obb and data dir if it doesn't exist.
+ File file = new File(packageObbDir);
+ if (!file.exists()) {
+ vold.setupAppDir(packageObbDir, mPmInternal.getPackage(pkg).getUid());
}
- } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
- Slog.e(TAG, "Unable to create obb and data directories for " + processName, e);
+ file = new File(packageDataDir);
+ if (!file.exists()) {
+ vold.setupAppDir(packageDataDir, mPmInternal.getPackage(pkg).getUid());
+ }
}
+ } catch (ServiceManager.ServiceNotFoundException | RemoteException e) {
+ Slog.e(TAG, "Unable to create obb and data directories for " + processName,e);
+ return false;
}
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4c1740f..0ebb5bb 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -137,6 +137,10 @@
private static final boolean SHOW_DUNGEON_NOTIFICATION = false;
+ //TODO: remove this when development is done.
+ private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0;
+ private static final int DEBUG_FGS_ENFORCE_TYPE = 1;
+
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
@@ -4915,10 +4919,20 @@
if (!r.isForeground) {
continue;
}
- if (!r.mAllowWhileInUsePermissionInFgs
- && r.mInfoDenyWhileInUsePermissionInFgs != null) {
- final String msg = r.mInfoDenyWhileInUsePermissionInFgs
- + " affected while-in-use permission:"
+ if (mode == DEBUG_FGS_ALLOW_WHILE_IN_USE) {
+ if (!r.mAllowWhileInUsePermissionInFgs
+ && r.mInfoDenyWhileInUsePermissionInFgs != null) {
+ final String msg = r.mInfoDenyWhileInUsePermissionInFgs
+ + " affected while-in-use permission:"
+ + AppOpsManager.opToPublicName(op);
+ Slog.wtf(TAG, msg);
+ }
+ } else if (mode == DEBUG_FGS_ENFORCE_TYPE) {
+ final String msg =
+ "FGS Missing foregroundServiceType in manifest file [callingPackage: "
+ + r.mRecentCallingPackage
+ + "; intent:" + r.intent.getIntent()
+ + "] affected while-in-use permission:"
+ AppOpsManager.opToPublicName(op);
Slog.wtf(TAG, msg);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f64272b..4485af1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17538,8 +17538,11 @@
final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
if (proc.thread != null && proc.baseProcessTracker != null) {
- proc.baseProcessTracker.setState(
- proc.getReportedProcState(), memFactor, now, proc.pkgList.mPkgList);
+ final int procState = proc.getReportedProcState();
+ if (procState != PROCESS_STATE_NONEXISTENT) {
+ proc.baseProcessTracker.setState(
+ procState, memFactor, now, proc.pkgList.mPkgList);
+ }
}
}
@@ -18714,6 +18717,11 @@
}
@Override
+ public Map<Integer, String> getProcessesWithPendingBindMounts(int userId) {
+ return mProcessList.getProcessesWithPendingBindMounts(userId);
+ }
+
+ @Override
public boolean isSystemReady() {
// no need to synchronize(this) just to read & return the value
return mSystemReady;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 48ceba9..8527ae9 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -115,6 +115,10 @@
DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE,
WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE, boolean.class,
WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT));
+ sDeviceConfigEntries.add(new DeviceConfigEntry<Integer>(
+ DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.FINGER_TO_CURSOR_DISTANCE,
+ WidgetFlags.KEY_FINGER_TO_CURSOR_DISTANCE, int.class,
+ WidgetFlags.FINGER_TO_CURSOR_DISTANCE_DEFAULT));
sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
DeviceConfig.NAMESPACE_WIDGET, WidgetFlags.ENABLE_INSERTION_HANDLE_GESTURES,
WidgetFlags.KEY_ENABLE_INSERTION_HANDLE_GESTURES, boolean.class,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b584ea5..2b1534b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -75,7 +75,6 @@
import android.app.ApplicationExitInfo;
import android.app.usage.UsageEvents;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ServiceInfo;
@@ -149,12 +148,13 @@
* capabilities.
*/
@ChangeId
- //TODO: change to @EnabledAfter when enforcing the feature.
- @Disabled
+ @EnabledAfter(targetSdkVersion=android.os.Build.VERSION_CODES.Q)
static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L;
//TODO: remove this when development is done.
private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
+ private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
+ private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
/**
* For some direct access we need to power manager.
@@ -1513,10 +1513,12 @@
if (enabled) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
- != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0;
+ != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA
+ : TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA;
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE)
- != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0;
+ != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
+ : TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
} else {
capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0e9970e..57bd42b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -99,6 +99,7 @@
import android.system.Os;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.EventLog;
import android.util.LongSparseArray;
import android.util.Pair;
@@ -136,8 +137,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Activity manager code dealing with processes.
@@ -154,7 +158,8 @@
"persist.sys.vold_app_data_isolation_enabled";
// A device config to control the minimum target SDK to enable app data isolation
- static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk";
+ static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK =
+ "android_app_data_isolation_min_sdk";
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
@@ -799,6 +804,31 @@
}
}
+ /**
+ * Get a map of pid and package name that process of that pid Android/data and Android/obb
+ * directory is not mounted to lowerfs to speed up access.
+ */
+ Map<Integer, String> getProcessesWithPendingBindMounts(int userId) {
+ final Map<Integer, String> pidPackageMap = new HashMap<>();
+ synchronized (mService) {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+ final ProcessRecord record = mLruProcesses.get(i);
+ if (record.userId != userId || !record.bindMountPending) {
+ continue;
+ }
+ final int pid = record.pid;
+ // It can happen when app process is starting, but zygote work is not done yet so
+ // system does not this pid record yet.
+ if (pid == 0) {
+ throw new IllegalStateException("Pending process is not started yet,"
+ + "retry later");
+ }
+ pidPackageMap.put(pid, record.info.packageName);
+ }
+ return pidPackageMap;
+ }
+ }
+
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
// Scale buckets from avail memory: at 300MB we use the lowest values to
// 700MB or more for the top values.
@@ -1680,6 +1710,7 @@
if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
checkSlow(startTime, "startProcess: removing from pids map");
mService.removePidLocked(app);
+ app.bindMountPending = false;
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
checkSlow(startTime, "startProcess: done removing from pids map");
app.setPid(0);
@@ -2162,8 +2193,10 @@
}
final Map<String, Pair<String, Long>> pkgDataInfoMap;
+ boolean bindMountAppStorageDirs = false;
if (shouldIsolateAppData(app)) {
+ bindMountAppStorageDirs = mVoldAppDataIsolationEnabled;
// Get all packages belongs to the same shared uid. sharedPackages is empty array
// if it doesn't have shared uid.
final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
@@ -2172,11 +2205,17 @@
pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
? new String[]{app.info.packageName} : sharedPackages, uid);
+ int userId = UserHandle.getUserId(uid);
if (mVoldAppDataIsolationEnabled) {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
- StorageManagerInternal.class);
- storageManagerInternal.prepareObbDirs(UserHandle.getUserId(uid),
- pkgDataInfoMap.keySet(), app.processName);
+ StorageManagerInternal.class);
+ if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
+ app.processName)) {
+ // Cannot prepare Android/app and Android/obb directory,
+ // so we won't mount it in zygote.
+ app.bindMountPending = true;
+ bindMountAppStorageDirs = false;
+ }
}
} else {
pkgDataInfoMap = null;
@@ -2197,7 +2236,7 @@
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
- app.mDisabledCompatChanges, pkgDataInfoMap,
+ app.mDisabledCompatChanges, pkgDataInfoMap, bindMountAppStorageDirs,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
@@ -2205,6 +2244,7 @@
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
+ bindMountAppStorageDirs,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
@@ -2663,6 +2703,7 @@
int pid = app.pid;
if (pid > 0) {
mService.removePidLocked(app);
+ app.bindMountPending = false;
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
if (app.isolated) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a78caac..e7f66bb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -251,6 +251,7 @@
int adjSourceProcState; // Debugging: proc state of adjSource's process.
Object adjTarget; // Debugging: target component impacting oom_adj.
Runnable crashHandler; // Optional local handler to be invoked in the process crash.
+ boolean bindMountPending; // True if Android/obb and Android/data need to be bind mount .
// Cache of last retrieve memory info and uptime, to throttle how frequently
// apps can requyest it.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 268bf33..2e2241f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -249,6 +249,11 @@
//TODO: remove this when development is done.
private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
+ private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30;
+ private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29;
+ private static final int DEBUG_FGS_ALLOW_WHILE_IN_USE = 0;
+ private static final int DEBUG_FGS_ENFORCE_TYPE = 1;
+
final Context mContext;
final AtomicFile mFile;
@@ -551,7 +556,7 @@
// The FGS has the location capability, but due to FGS BG start
// restriction it lost the capability, use temp location capability
// to mark this case.
- maybeShowWhileInUseDebugToast(op, mode);
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
return MODE_IGNORED;
} else {
return MODE_IGNORED;
@@ -559,15 +564,25 @@
case OP_CAMERA:
if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
return MODE_ALLOWED;
+ } else if ((capability & TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA)
+ != 0) {
+ // CHANGE TO MODE_IGNORED when enforce this feature.
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+ return MODE_ALLOWED;
} else {
- maybeShowWhileInUseDebugToast(op, mode);
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
return MODE_IGNORED;
}
case OP_RECORD_AUDIO:
if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
return MODE_ALLOWED;
+ } else if ((capability & TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE)
+ != 0) {
+ // CHANGE TO MODE_IGNORED when enforce this feature.
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+ return MODE_ALLOWED;
} else {
- maybeShowWhileInUseDebugToast(op, mode);
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
return MODE_IGNORED;
}
default:
@@ -582,15 +597,24 @@
case OP_CAMERA:
if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
return MODE_ALLOWED;
+ } else if ((capability & TEMP_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
+ // CHANGE TO MODE_IGNORED when enforce this feature.
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+ return MODE_ALLOWED;
} else {
- maybeShowWhileInUseDebugToast(op, mode);
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
return MODE_IGNORED;
}
case OP_RECORD_AUDIO:
if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
return MODE_ALLOWED;
+ } else if ((capability & TEMP_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE)
+ != 0) {
+ // CHANGE TO MODE_IGNORED when enforce this feature.
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE);
+ return MODE_ALLOWED;
} else {
- maybeShowWhileInUseDebugToast(op, mode);
+ maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE);
return MODE_IGNORED;
}
default:
diff --git a/services/core/java/com/android/server/hdmi/TEST_MAPPING b/services/core/java/com/android/server/hdmi/TEST_MAPPING
new file mode 100644
index 0000000..7245ec4
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/TEST_MAPPING
@@ -0,0 +1,34 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.hdmi"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.hdmi"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index aa12bbd..44ab438 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -50,6 +50,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -244,6 +245,11 @@
return new ParceledListSlice<>(rules);
}
+ @Override
+ public List<String> getWhitelistedRuleProviders() throws RemoteException {
+ return getAllowedRuleProviders();
+ }
+
private void handleIntegrityVerification(Intent intent) {
int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index aad7203..5c97e90 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -127,8 +127,10 @@
@Override
public void requestCreateSession(long requestId, String packageName, String routeId,
Bundle sessionHints) {
- // Handle it as an internal transfer.
+
transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
+ mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
+ //TODO: We should call after the session info is changed.
}
@Override
@@ -240,7 +242,6 @@
builder.addTransferableRoute(route.getId());
}
-
RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
if (Objects.equals(oldSessionInfo, newSessionInfo)) {
return false;
@@ -261,6 +262,7 @@
synchronized (mLock) {
sessionInfo = mSessionInfos.get(0);
}
+
mCallback.onSessionUpdated(this, sessionInfo);
}
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index c9c8042..c96880c 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -15,9 +15,27 @@
*/
package com.android.server.notification;
+import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+
+import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
+import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+
/**
* Determines whether a bubble can be shown for this notification
*/
@@ -25,10 +43,15 @@
private static final String TAG = "BubbleExtractor";
private static final boolean DBG = false;
+ private BubbleChecker mBubbleChecker;
private RankingConfig mConfig;
+ private ActivityManager mActivityManager;
+ private Context mContext;
- public void initialize(Context ctx, NotificationUsageStats usageStats) {
+ public void initialize(Context context, NotificationUsageStats usageStats) {
if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ mContext = context;
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
public RankingReconsideration process(NotificationRecord record) {
@@ -41,6 +64,12 @@
if (DBG) Slog.d(TAG, "missing config");
return null;
}
+
+ if (mBubbleChecker == null) {
+ if (DBG) Slog.d(TAG, "missing bubble checker");
+ return null;
+ }
+
boolean appCanShowBubble =
mConfig.areBubblesAllowed(record.getSbn().getPackageName(), record.getSbn().getUid());
if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
@@ -52,7 +81,12 @@
record.setAllowBubble(appCanShowBubble);
}
}
-
+ final boolean applyFlag = mBubbleChecker.isNotificationAppropriateToBubble(record);
+ if (applyFlag) {
+ record.getNotification().flags |= FLAG_BUBBLE;
+ } else {
+ record.getNotification().flags &= ~FLAG_BUBBLE;
+ }
return null;
}
@@ -64,4 +98,185 @@
@Override
public void setZenHelper(ZenModeHelper helper) {
}
+
+ /**
+ * Expected to be called after {@link #setConfig(RankingConfig)} has occurred.
+ */
+ void setShortcutHelper(ShortcutHelper helper) {
+ if (mConfig == null) {
+ if (DBG) Slog.d(TAG, "setting shortcut helper prior to setConfig");
+ return;
+ }
+ mBubbleChecker = new BubbleChecker(mContext, helper, mConfig, mActivityManager);
+ }
+
+ @VisibleForTesting
+ void setBubbleChecker(BubbleChecker checker) {
+ mBubbleChecker = checker;
+ }
+
+ /**
+ * Encapsulates special checks to see if a notification can be flagged as a bubble. This
+ * makes testing a bit easier.
+ */
+ public static class BubbleChecker {
+
+ private ActivityManager mActivityManager;
+ private RankingConfig mRankingConfig;
+ private Context mContext;
+ private ShortcutHelper mShortcutHelper;
+
+ BubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
+ ActivityManager activityManager) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mShortcutHelper = helper;
+ mRankingConfig = config;
+ }
+
+ /**
+ * @return whether the provided notification record is allowed to be represented as a
+ * bubble, accounting for user choice & policy.
+ */
+ public boolean isNotificationAppropriateToBubble(NotificationRecord r) {
+ final String pkg = r.getSbn().getPackageName();
+ final int userId = r.getSbn().getUser().getIdentifier();
+ Notification notification = r.getNotification();
+ if (!canBubble(r, pkg, userId)) {
+ // no log: canBubble has its own
+ return false;
+ }
+
+ if (mActivityManager.isLowRamDevice()) {
+ logBubbleError(r.getKey(), "low ram device");
+ return false;
+ }
+
+ // At this point the bubble must fulfill communication policy
+
+ // Communication always needs a person
+ ArrayList<Person> peopleList = notification.extras != null
+ ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
+ : null;
+ // Message style requires a person & it's not included in the list
+ boolean isMessageStyle = Notification.MessagingStyle.class.equals(
+ notification.getNotificationStyle());
+ if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
+ logBubbleError(r.getKey(), "Must have a person and be "
+ + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+ return false;
+ }
+
+ // Communication is a message or a call
+ boolean isCall = CATEGORY_CALL.equals(notification.category);
+ boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+ if (hasForegroundService && !isCall) {
+ logBubbleError(r.getKey(),
+ "foreground services must be Notification.CATEGORY_CALL to bubble");
+ return false;
+ }
+ if (isMessageStyle) {
+ return true;
+ } else if (isCall) {
+ if (hasForegroundService) {
+ return true;
+ }
+ logBubbleError(r.getKey(), "calls require foreground service");
+ return false;
+ }
+ logBubbleError(r.getKey(), "Must be "
+ + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+ return false;
+ }
+
+ /**
+ * @return whether the user has enabled the provided notification to bubble, does not
+ * account for policy.
+ */
+ @VisibleForTesting
+ boolean canBubble(NotificationRecord r, String pkg, int userId) {
+ Notification notification = r.getNotification();
+ Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+ if (metadata == null) {
+ // no log: no need to inform dev if they didn't attach bubble metadata
+ return false;
+ }
+ if (!mRankingConfig.bubblesEnabled()) {
+ logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
+ return false;
+ }
+ if (!mRankingConfig.areBubblesAllowed(pkg, userId)) {
+ logBubbleError(r.getKey(),
+ "bubbles for package: " + pkg + " disabled for user: " + userId);
+ return false;
+ }
+ if (!r.getChannel().canBubble()) {
+ logBubbleError(r.getKey(),
+ "bubbles for channel " + r.getChannel().getId() + " disabled");
+ return false;
+ }
+
+ String shortcutId = metadata.getShortcutId();
+ boolean shortcutValid = shortcutId != null
+ && mShortcutHelper.hasValidShortcutInfo(shortcutId, pkg, r.getUser());
+ if (metadata.getBubbleIntent() == null && !shortcutValid) {
+ // Should have a shortcut if intent is null
+ logBubbleError(r.getKey(),
+ "couldn't find valid shortcut for bubble with shortcutId: " + shortcutId);
+ return false;
+ }
+ if (shortcutValid) {
+ return true;
+ }
+ // no log: canLaunch method has the failure log
+ return canLaunchInActivityView(mContext, metadata.getBubbleIntent(), pkg);
+ }
+
+ /**
+ * Whether an intent is properly configured to display in an {@link
+ * android.app.ActivityView}.
+ *
+ * @param context the context to use.
+ * @param pendingIntent the pending intent of the bubble.
+ * @param packageName the notification package name for this bubble.
+ */
+ // Keep checks in sync with BubbleController#canLaunchInActivityView.
+ @VisibleForTesting
+ protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
+ String packageName) {
+ if (pendingIntent == null) {
+ Slog.w(TAG, "Unable to create bubble -- no intent");
+ return false;
+ }
+
+ Intent intent = pendingIntent.getIntent();
+
+ ActivityInfo info = intent != null
+ ? intent.resolveActivityInfo(context.getPackageManager(), 0)
+ : null;
+ if (info == null) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
+ packageName,
+ BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
+ Slog.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
+ + intent);
+ return false;
+ }
+ if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED,
+ packageName,
+ BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
+ Slog.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
+ + intent);
+ return false;
+ }
+ return true;
+ }
+
+ private void logBubbleError(String key, String failureMessage) {
+ if (DBG) {
+ Slog.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 69a5b35..20ad87a 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.notification;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
-import static android.app.Notification.CATEGORY_CALL;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -51,9 +50,6 @@
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_NOT_PERCEPTIBLE;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.MATCH_ALL;
@@ -93,8 +89,6 @@
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
-import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -131,8 +125,6 @@
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
-import android.app.Person;
-import android.app.RemoteInput;
import android.app.StatsManager;
import android.app.StatusBarManager;
import android.app.UriGrantsManager;
@@ -152,7 +144,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
@@ -160,7 +151,6 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
-import android.content.pm.ShortcutInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -251,7 +241,6 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
@@ -296,7 +285,6 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
@@ -421,7 +409,7 @@
private RoleObserver mRoleObserver;
private UserManager mUm;
private IPlatformCompat mPlatformCompat;
- private LauncherApps mLauncherAppsService;
+ private ShortcutHelper mShortcutHelper;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -497,7 +485,8 @@
"allow-secure-notifications-on-lockscreen";
private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
- private RankingHelper mRankingHelper;
+ @VisibleForTesting
+ RankingHelper mRankingHelper;
@VisibleForTesting
PreferencesHelper mPreferencesHelper;
@@ -931,6 +920,8 @@
.setType(MetricsEvent.TYPE_ACTION)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, nv.rank)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, nv.count));
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED, r);
EventLogTags.writeNotificationClicked(key,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
nv.rank, nv.count);
@@ -970,7 +961,8 @@
generatedByAssistant ? 1 : 0)
.addTaggedData(MetricsEvent.NOTIFICATION_LOCATION,
nv.location.toMetricsEventEnum()));
-
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_ACTION_CLICKED, r);
EventLogTags.writeNotificationActionClicked(key, actionIndex,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
nv.rank, nv.count);
@@ -1004,6 +996,8 @@
public void onPanelRevealed(boolean clearEffects, int items) {
MetricsLogger.visible(getContext(), MetricsEvent.NOTIFICATION_PANEL);
MetricsLogger.histogram(getContext(), "note_load", items);
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_OPEN);
EventLogTags.writeNotificationPanelRevealed(items);
if (clearEffects) {
clearEffects();
@@ -1014,6 +1008,8 @@
@Override
public void onPanelHidden() {
MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_CLOSE);
EventLogTags.writeNotificationPanelHidden();
mAssistants.onPanelHidden();
}
@@ -1103,6 +1099,10 @@
MetricsLogger.action(r.getItemLogMaker()
.setType(expanded ? MetricsEvent.TYPE_DETAIL
: MetricsEvent.TYPE_COLLAPSE));
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.fromExpanded(expanded,
+ userAction),
+ r);
}
if (expanded && userAction) {
r.recordExpanded();
@@ -1124,6 +1124,9 @@
mMetricsLogger.write(r.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_DIRECT_REPLY_ACTION)
.setType(MetricsEvent.TYPE_ACTION));
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_DIRECT_REPLIED,
+ r);
reportUserInteraction(r);
mAssistants.notifyAssistantNotificationDirectReplyLocked(r.getSbn());
}
@@ -1166,6 +1169,9 @@
MetricsEvent.NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING,
modifiedBeforeSending ? 1 : 0);
mMetricsLogger.write(logMaker);
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED,
+ r);
// Treat clicking on a smart reply as a user interaction.
reportUserInteraction(r);
mAssistants.notifyAssistantSuggestedReplySent(
@@ -1186,13 +1192,30 @@
@Override
public void onNotificationBubbleChanged(String key, boolean isBubble) {
+ String pkg;
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ pkg = r != null && r.getSbn() != null ? r.getSbn().getPackageName() : null;
+ }
+ boolean isAppForeground = pkg != null
+ && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
- final StatusBarNotification n = r.getSbn();
- final int callingUid = n.getUid();
- final String pkg = n.getPackageName();
- applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isBubble);
+ if (!isBubble) {
+ // This happens if the user has dismissed the bubble but the notification
+ // is still active in the shade, enqueuing would create a bubble since
+ // the notification is technically allowed. Flip the flag so that
+ // apps querying noMan will know that their notification is not showing
+ // as a bubble.
+ r.getNotification().flags &= ~FLAG_BUBBLE;
+ } else {
+ // Enqueue will trigger resort & if the flag is allowed to be true it'll
+ // be applied there.
+ r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+ mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
+ r, isAppForeground));
+ }
}
}
}
@@ -1219,6 +1242,7 @@
flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
}
data.setFlags(flags);
+ r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
true /* isAppForeground */));
}
@@ -1310,6 +1334,9 @@
MetricsEvent.NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING,
r.getEditChoicesBeforeSending() ? 1 : 0);
mMetricsLogger.write(logMaker);
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLY_VISIBLE,
+ r);
}
}
@@ -1595,80 +1622,6 @@
}
};
- // Key: packageName Value: <shortcutId, notifId>
- private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
-
- private boolean mLauncherAppsCallbackRegistered;
-
- // Bubbles can be created based on a shortcut, we need to listen for changes to
- // that shortcut so that we may update the bubble appropriately.
- private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
- @Override
- public void onPackageRemoved(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackageAdded(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackageChanged(String packageName, UserHandle user) {
- }
-
- @Override
- public void onPackagesAvailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- }
-
- @Override
- public void onPackagesUnavailable(String[] packageNames, UserHandle user,
- boolean replacing) {
- }
-
- @Override
- public void onShortcutsChanged(@NonNull String packageName,
- @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
- HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
- boolean isAppForeground = packageName != null
- && mActivityManager.getPackageImportance(packageName) == IMPORTANCE_FOREGROUND;
- ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
- if (shortcutBubbles != null) {
- // If we can't find one of our bubbles in the shortcut list, that bubble needs
- // to be removed.
- for (String shortcutId : shortcutBubbles.keySet()) {
- boolean foundShortcut = false;
- for (int i = 0; i < shortcuts.size(); i++) {
- if (shortcuts.get(i).getId().equals(shortcutId)) {
- foundShortcut = true;
- break;
- }
- }
- if (!foundShortcut) {
- bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
- }
- }
- }
-
- // Do the removals
- for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
- // update flag bubble
- String bubbleKey = bubbleKeysToRemove.get(i);
- synchronized (mNotificationLock) {
- NotificationRecord r = mNotificationsByKey.get(bubbleKey);
- if (r != null) {
- final StatusBarNotification n = r.getSbn();
- final int callingUid = n.getUid();
- final String pkg = n.getPackageName();
- applyFlagBubble(r, pkg, callingUid, null /* oldEntry */, isAppForeground);
- mHandler.post(new EnqueueNotificationRunnable(user.getIdentifier(), r,
- false /* isAppForeground */));
- }
- }
- }
- }
- };
-
-
private final class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_BADGING_URI
= Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
@@ -1763,8 +1716,8 @@
}
@VisibleForTesting
- void setLauncherApps(LauncherApps launcherApps) {
- mLauncherAppsService = launcherApps;
+ ShortcutHelper getShortcutHelper() {
+ return mShortcutHelper;
}
@VisibleForTesting
@@ -2314,8 +2267,13 @@
mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
mPackageManager, getContext().getMainExecutor());
mRoleObserver.init();
- mLauncherAppsService =
+ LauncherApps launcherApps =
(LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener);
+ BubbleExtractor bubbsExtractor = mRankingHelper.findExtractor(BubbleExtractor.class);
+ if (bubbsExtractor != null) {
+ bubbsExtractor.setShortcutHelper(mShortcutHelper);
+ }
registerNotificationPreferencesPullers();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
@@ -3458,7 +3416,7 @@
ArrayList<ConversationChannelWrapper> conversations =
mPreferencesHelper.getConversations(onlyImportant);
for (ConversationChannelWrapper conversation : conversations) {
- conversation.setShortcutInfo(getShortcutInfo(
+ conversation.setShortcutInfo(mShortcutHelper.getShortcutInfo(
conversation.getNotificationChannel().getConversationId(),
conversation.getPkg(),
UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
@@ -3481,7 +3439,7 @@
ArrayList<ConversationChannelWrapper> conversations =
mPreferencesHelper.getConversations(pkg, uid);
for (ConversationChannelWrapper conversation : conversations) {
- conversation.setShortcutInfo(getShortcutInfo(
+ conversation.setShortcutInfo(mShortcutHelper.getShortcutInfo(
conversation.getNotificationChannel().getConversationId(),
pkg,
UserHandle.of(UserHandle.getUserId(uid))));
@@ -5652,7 +5610,7 @@
}
}
- r.setShortcutInfo(getShortcutInfo(notification.getShortcutId(), pkg, user));
+ r.setShortcutInfo(mShortcutHelper.getShortcutInfo(notification.getShortcutId(), pkg, user));
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.getSbn().getOverrideGroupKey() != null)) {
@@ -5780,16 +5738,12 @@
}
/**
- * Updates the flags for this notification to reflect whether it is a bubble or not. Some
- * bubble specific flags only work if the app is foreground, this will strip those flags
+ * Some bubble specific flags only work if the app is foreground, this will strip those flags
* if the app wasn't foreground.
*/
- private void updateNotificationBubbleFlags(NotificationRecord r, String pkg, int userId,
- NotificationRecord oldRecord, boolean isAppForeground) {
- Notification notification = r.getNotification();
- applyFlagBubble(r, pkg, userId, oldRecord, true /* desiredFlag */);
-
+ private void updateNotificationBubbleFlags(NotificationRecord r, boolean isAppForeground) {
// Remove any bubble specific flags that only work when foregrounded
+ Notification notification = r.getNotification();
Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
if (!isAppForeground && metadata != null) {
int flags = metadata.getFlags();
@@ -5799,252 +5753,30 @@
}
}
- /**
- * Handles actually applying or removing {@link Notification#FLAG_BUBBLE}. Performs necessary
- * checks for the provided record to see if it can actually be a bubble.
- * Tracks shortcut based bubbles so that we can find out if they've changed or been removed.
- */
- private void applyFlagBubble(NotificationRecord r, String pkg, int userId,
- NotificationRecord oldRecord, boolean desiredFlag) {
- boolean applyFlag = desiredFlag
- && isNotificationAppropriateToBubble(r, pkg, userId, oldRecord);
- final String shortcutId = r.getNotification().getBubbleMetadata() != null
- ? r.getNotification().getBubbleMetadata().getShortcutId()
- : null;
- if (applyFlag) {
- if (shortcutId != null) {
- // Must track shortcut based bubbles in case the shortcut is removed
- HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
- r.getSbn().getPackageName());
- if (packageBubbles == null) {
- packageBubbles = new HashMap<>();
+ private ShortcutHelper.ShortcutListener mShortcutListener =
+ new ShortcutHelper.ShortcutListener() {
+ @Override
+ public void onShortcutRemoved(String key) {
+ String packageName;
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ packageName = r != null ? r.getSbn().getPackageName() : null;
+ }
+ boolean isAppForeground = packageName != null
+ && mActivityManager.getPackageImportance(packageName)
+ == IMPORTANCE_FOREGROUND;
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ // Enqueue will trigger resort & flag is updated that way.
+ r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+ mHandler.post(
+ new NotificationManagerService.EnqueueNotificationRunnable(
+ r.getUser().getIdentifier(), r, isAppForeground));
+ }
+ }
}
- packageBubbles.put(shortcutId, r.getKey());
- mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
- if (!mLauncherAppsCallbackRegistered) {
- mLauncherAppsService.registerCallback(mLauncherAppsCallback, mHandler);
- mLauncherAppsCallbackRegistered = true;
- }
- }
- r.getNotification().flags |= FLAG_BUBBLE;
- } else {
- if (shortcutId != null) {
- // No longer track shortcut
- HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
- r.getSbn().getPackageName());
- if (packageBubbles != null) {
- packageBubbles.remove(shortcutId);
- }
- if (packageBubbles != null && packageBubbles.isEmpty()) {
- mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
- }
- if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
- mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
- mLauncherAppsCallbackRegistered = false;
- }
- }
- r.getNotification().flags &= ~FLAG_BUBBLE;
- }
- }
-
- /**
- * @return whether the provided notification record is allowed to be represented as a bubble,
- * accounting for user choice & policy.
- */
- private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
- NotificationRecord oldRecord) {
- Notification notification = r.getNotification();
- if (!canBubble(r, pkg, userId)) {
- // no log: canBubble has its own
- return false;
- }
-
- if (mActivityManager.isLowRamDevice()) {
- logBubbleError(r.getKey(), "low ram device");
- return false;
- }
-
- if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
- // This is an update to an active bubble
- return true;
- }
-
- // At this point the bubble must fulfill communication policy
-
- // Communication always needs a person
- ArrayList<Person> peopleList = notification.extras != null
- ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
- : null;
- // Message style requires a person & it's not included in the list
- boolean isMessageStyle = Notification.MessagingStyle.class.equals(
- notification.getNotificationStyle());
- if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
- logBubbleError(r.getKey(), "Must have a person and be "
- + "Notification.MessageStyle or Notification.CATEGORY_CALL");
- return false;
- }
-
- // Communication is a message or a call
- boolean isCall = CATEGORY_CALL.equals(notification.category);
- boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
- if (hasForegroundService && !isCall) {
- logBubbleError(r.getKey(),
- "foreground services must be Notification.CATEGORY_CALL to bubble");
- return false;
- }
- if (isMessageStyle) {
- if (hasValidRemoteInput(notification)) {
- return true;
- }
- logBubbleError(r.getKey(), "messages require valid remote input");
- return false;
- } else if (isCall) {
- if (hasForegroundService) {
- return true;
- }
- logBubbleError(r.getKey(), "calls require foreground service");
- return false;
- }
- logBubbleError(r.getKey(), "Must be "
- + "Notification.MessageStyle or Notification.CATEGORY_CALL");
- return false;
- }
-
- /**
- * @return whether the user has enabled the provided notification to bubble, does not account
- * for policy.
- */
- private boolean canBubble(NotificationRecord r, String pkg, int userId) {
- Notification notification = r.getNotification();
- Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
- if (metadata == null) {
- // no log: no need to inform dev if they didn't attach bubble metadata
- return false;
- }
- if (!mPreferencesHelper.bubblesEnabled()) {
- logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
- return false;
- }
- if (!mPreferencesHelper.areBubblesAllowed(pkg, userId)) {
- logBubbleError(r.getKey(),
- "bubbles for package: " + pkg + " disabled for user: " + userId);
- return false;
- }
- if (!r.getChannel().canBubble()) {
- logBubbleError(r.getKey(),
- "bubbles for channel " + r.getChannel().getId() + " disabled");
- return false;
- }
-
- String shortcutId = metadata.getShortcutId();
- boolean shortcutValid = shortcutId != null
- && hasValidShortcutInfo(shortcutId, pkg, r.getUser());
- if (metadata.getBubbleIntent() == null && !shortcutValid) {
- // Should have a shortcut if intent is null
- logBubbleError(r.getKey(), "couldn't find shortcutId for bubble: " + shortcutId);
- return false;
- }
- if (shortcutValid) {
- return true;
- }
- // no log: canLaunch method has the failure log
- return canLaunchInActivityView(getContext(), metadata.getBubbleIntent(), pkg);
- }
-
- private boolean hasValidRemoteInput(Notification n) {
- // Also check for inline reply
- Notification.Action[] actions = n.actions;
- if (actions != null) {
- // Get the remote inputs
- for (int i = 0; i < actions.length; i++) {
- Notification.Action action = actions[i];
- RemoteInput[] inputs = action.getRemoteInputs();
- if (inputs != null && inputs.length > 0) {
- return true;
- }
- }
- }
- return false;
- }
-
- private ShortcutInfo getShortcutInfo(String shortcutId, String packageName, UserHandle user) {
- final long token = Binder.clearCallingIdentity();
- try {
- if (shortcutId == null || packageName == null || user == null) {
- return null;
- }
- LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
- if (packageName != null) {
- query.setPackage(packageName);
- }
- if (shortcutId != null) {
- query.setShortcutIds(Arrays.asList(shortcutId));
- }
- query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED);
- List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
- ShortcutInfo shortcutInfo = shortcuts != null && shortcuts.size() > 0
- ? shortcuts.get(0)
- : null;
- return shortcutInfo;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private boolean hasValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
- ShortcutInfo shortcutInfo = getShortcutInfo(shortcutId, packageName, user);
- return shortcutInfo != null && shortcutInfo.isLongLived();
- }
-
- private void logBubbleError(String key, String failureMessage) {
- if (DBG) {
- Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
- }
- }
- /**
- * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
- *
- * @param context the context to use.
- * @param pendingIntent the pending intent of the bubble.
- * @param packageName the notification package name for this bubble.
- */
- // Keep checks in sync with BubbleController#canLaunchInActivityView.
- @VisibleForTesting
- protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
- String packageName) {
- if (pendingIntent == null) {
- Log.w(TAG, "Unable to create bubble -- no intent");
- return false;
- }
-
- // Need escalated privileges to get the intent.
- final long token = Binder.clearCallingIdentity();
- Intent intent;
- try {
- intent = pendingIntent.getIntent();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- ActivityInfo info = intent != null
- ? intent.resolveActivityInfo(context.getPackageManager(), 0)
- : null;
- if (info == null) {
- FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
- BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
- Log.w(TAG, "Unable to send as bubble -- couldn't find activity info for intent: "
- + intent);
- return false;
- }
- if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
- FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
- BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
- Log.w(TAG, "Unable to send as bubble -- activity is not resizable for intent: "
- + intent);
- return false;
- }
- return true;
- }
+ };
private void doChannelWarningToast(CharSequence toastText) {
Binder.withCleanCallingIdentity(() -> {
@@ -6150,6 +5882,9 @@
MetricsLogger.action(r.getLogMaker()
.setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
+ r);
if (DBG) {
Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
}
@@ -6279,6 +6014,8 @@
mDuration)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
mSnoozeCriterionId == null ? 0 : 1));
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED, r);
reportUserInteraction(r);
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
@@ -6406,6 +6143,8 @@
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
mSendDelete, childrenFlagChecker);
updateLightsLocked();
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */,
+ mHandler);
} else {
// No notification was found, assume that it is snoozed and cancel it.
if (mReason != REASON_SNOOZED) {
@@ -6473,7 +6212,7 @@
final String tag = n.getTag();
// We need to fix the notification up a little for bubbles
- updateNotificationBubbleFlags(r, pkg, callingUid, old, isAppForeground);
+ updateNotificationBubbleFlags(r, isAppForeground);
// Handle grouped notifications and bail out early if we
// can to avoid extracting signals.
@@ -6643,6 +6382,10 @@
+ n.getPackageName());
}
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+ false /* isRemoved */,
+ mHandler);
+
maybeRecordInterruptionLocked(r);
// Log event to statsd
@@ -7402,6 +7145,7 @@
int[] visibilities = new int[N];
boolean[] showBadges = new boolean[N];
boolean[] allowBubbles = new boolean[N];
+ boolean[] isBubble = new boolean[N];
ArrayList<NotificationChannel> channelBefore = new ArrayList<>(N);
ArrayList<String> groupKeyBefore = new ArrayList<>(N);
ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
@@ -7417,6 +7161,7 @@
visibilities[i] = r.getPackageVisibilityOverride();
showBadges[i] = r.canShowBadge();
allowBubbles[i] = r.canBubble();
+ isBubble[i] = r.getNotification().isBubbleNotification();
channelBefore.add(r.getChannel());
groupKeyBefore.add(r.getGroupKey());
overridePeopleBefore.add(r.getPeopleOverride());
@@ -7435,6 +7180,7 @@
|| visibilities[i] != r.getPackageVisibilityOverride()
|| showBadges[i] != r.canShowBadge()
|| allowBubbles[i] != r.canBubble()
+ || isBubble[i] != r.getNotification().isBubbleNotification()
|| !Objects.equals(channelBefore.get(i), r.getChannel())
|| !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
|| !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
@@ -8597,7 +8343,8 @@
record.canBubble(),
record.isInterruptive(),
record.isConversation(),
- record.getShortcutInfo()
+ record.getShortcutInfo(),
+ record.getNotification().isBubbleNotification()
);
rankings.add(ranking);
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index f4ee461..6c833f96 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -34,11 +34,14 @@
import java.util.Objects;
/**
- * Interface for writing NotificationReported atoms to statsd log.
+ * Interface for writing NotificationReported atoms to statsd log. Use NotificationRecordLoggerImpl
+ * in production. Use NotificationRecordLoggerFake for testing.
* @hide
*/
public interface NotificationRecordLogger {
+ // The high-level interface used by clients.
+
/**
* May log a NotificationReported atom reflecting the posting or update of a notification.
* @param r The new NotificationRecord. If null, no action is taken.
@@ -57,9 +60,11 @@
* @param reason The reason the notification was canceled.
* @param dismissalSurface The surface the notification was dismissed from.
*/
- void logNotificationCancelled(@Nullable NotificationRecord r,
+ default void logNotificationCancelled(@Nullable NotificationRecord r,
@NotificationListenerService.NotificationCancelReason int reason,
- @NotificationStats.DismissalSurface int dismissalSurface);
+ @NotificationStats.DismissalSurface int dismissalSurface) {
+ log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
+ }
/**
* Logs a notification visibility change event using UiEventReported (event ids from the
@@ -67,7 +72,17 @@
* @param r The NotificationRecord. If null, no action is taken.
* @param visible True if the notification became visible.
*/
- void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible);
+ default void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible) {
+ log(NotificationEvent.fromVisibility(visible), r);
+ }
+
+ // The UiEventReported logging methods are implemented in terms of this lower-level interface.
+
+ /** Logs a UiEventReported event for the given notification. */
+ void log(UiEventLogger.UiEventEnum event, NotificationRecord r);
+
+ /** Logs a UiEventReported event that is not associated with any notification. */
+ void log(UiEventLogger.UiEventEnum event);
/**
* The UiEvent enums that this class can log.
@@ -204,7 +219,30 @@
@UiEvent(doc = "Notification became visible.")
NOTIFICATION_OPEN(197),
@UiEvent(doc = "Notification stopped being visible.")
- NOTIFICATION_CLOSE(198);
+ NOTIFICATION_CLOSE(198),
+ @UiEvent(doc = "Notification was snoozed.")
+ NOTIFICATION_SNOOZED(317),
+ @UiEvent(doc = "Notification was not posted because its app is snoozed.")
+ NOTIFICATION_NOT_POSTED_SNOOZED(319),
+ @UiEvent(doc = "Notification was clicked.")
+ NOTIFICATION_CLICKED(320),
+ @UiEvent(doc = "Notification action was clicked.")
+ NOTIFICATION_ACTION_CLICKED(321),
+ @UiEvent(doc = "Notification detail was expanded due to non-user action.")
+ NOTIFICATION_DETAIL_OPEN_SYSTEM(327),
+ @UiEvent(doc = "Notification detail was collapsed due to non-user action.")
+ NOTIFICATION_DETAIL_CLOSE_SYSTEM(328),
+ @UiEvent(doc = "Notification detail was expanded due to user action.")
+ NOTIFICATION_DETAIL_OPEN_USER(329),
+ @UiEvent(doc = "Notification detail was collapsed due to user action.")
+ NOTIFICATION_DETAIL_CLOSE_USER(330),
+ @UiEvent(doc = "Notification direct reply action was used.")
+ NOTIFICATION_DIRECT_REPLIED(331),
+ @UiEvent(doc = "Notification smart reply action was used.")
+ NOTIFICATION_SMART_REPLIED(332),
+ @UiEvent(doc = "Notification smart reply action was visible.")
+ NOTIFICATION_SMART_REPLY_VISIBLE(333),
+ ;
private final int mId;
NotificationEvent(int id) {
@@ -217,7 +255,29 @@
public static NotificationEvent fromVisibility(boolean visible) {
return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE;
}
+ public static NotificationEvent fromExpanded(boolean expanded, boolean userAction) {
+ if (userAction) {
+ return expanded ? NOTIFICATION_DETAIL_OPEN_USER : NOTIFICATION_DETAIL_CLOSE_USER;
+ }
+ return expanded ? NOTIFICATION_DETAIL_OPEN_SYSTEM : NOTIFICATION_DETAIL_CLOSE_SYSTEM;
+ }
}
+
+ enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Notification panel became visible.")
+ NOTIFICATION_PANEL_OPEN(325),
+ @UiEvent(doc = "Notification panel stopped being visible.")
+ NOTIFICATION_PANEL_CLOSE(326);
+
+ private final int mId;
+ NotificationPanelEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+ }
+
/**
* A helper for extracting logging information from one or two NotificationRecords.
*/
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 9fcac25..494ff31 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -65,20 +65,16 @@
}
@Override
- public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
- log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
- }
-
- @Override
- public void logNotificationVisibility(NotificationRecord r, boolean visible) {
- log(NotificationEvent.fromVisibility(visible), r);
- }
-
- void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+ public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
if (r == null) {
return;
}
mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(),
r.getSbn().getInstanceId());
}
+
+ @Override
+ public void log(UiEventLogger.UiEventEnum event) {
+ mUiEventLogger.log(event);
+ }
}
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
new file mode 100644
index 0000000..7bbb3b1
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import android.annotation.NonNull;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Helper for querying shortcuts.
+ */
+class ShortcutHelper {
+
+ /**
+ * Listener to call when a shortcut we're tracking has been removed.
+ */
+ interface ShortcutListener {
+ void onShortcutRemoved(String key);
+ }
+
+ private LauncherApps mLauncherAppsService;
+ private ShortcutListener mShortcutListener;
+
+ // Key: packageName Value: <shortcutId, notifId>
+ private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
+ private boolean mLauncherAppsCallbackRegistered;
+
+ // Bubbles can be created based on a shortcut, we need to listen for changes to
+ // that shortcut so that we may update the bubble appropriately.
+ private final LauncherApps.Callback mLauncherAppsCallback = new LauncherApps.Callback() {
+ @Override
+ public void onPackageRemoved(String packageName, UserHandle user) {
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, UserHandle user) {
+ }
+
+ @Override
+ public void onPackageChanged(String packageName, UserHandle user) {
+ }
+
+ @Override
+ public void onPackagesAvailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ }
+
+ @Override
+ public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+ boolean replacing) {
+ }
+
+ @Override
+ public void onShortcutsChanged(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+ HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
+ ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
+ if (shortcutBubbles != null) {
+ // If we can't find one of our bubbles in the shortcut list, that bubble needs
+ // to be removed.
+ for (String shortcutId : shortcutBubbles.keySet()) {
+ boolean foundShortcut = false;
+ for (int i = 0; i < shortcuts.size(); i++) {
+ if (shortcuts.get(i).getId().equals(shortcutId)) {
+ foundShortcut = true;
+ break;
+ }
+ }
+ if (!foundShortcut) {
+ bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
+ }
+ }
+ }
+
+ // Let NoMan know about the updates
+ for (int i = 0; i < bubbleKeysToRemove.size(); i++) {
+ // update flag bubble
+ String bubbleKey = bubbleKeysToRemove.get(i);
+ if (mShortcutListener != null) {
+ mShortcutListener.onShortcutRemoved(bubbleKey);
+ }
+ }
+ }
+ };
+
+ ShortcutHelper(LauncherApps launcherApps, ShortcutListener listener) {
+ mLauncherAppsService = launcherApps;
+ mShortcutListener = listener;
+ }
+
+ @VisibleForTesting
+ void setLauncherApps(LauncherApps launcherApps) {
+ mLauncherAppsService = launcherApps;
+ }
+
+ ShortcutInfo getShortcutInfo(String shortcutId, String packageName, UserHandle user) {
+ if (mLauncherAppsService == null) {
+ return null;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (shortcutId == null || packageName == null || user == null) {
+ return null;
+ }
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+ query.setPackage(packageName);
+ query.setShortcutIds(Arrays.asList(shortcutId));
+ query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_CACHED);
+ List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(query, user);
+ return shortcuts != null && shortcuts.size() > 0
+ ? shortcuts.get(0)
+ : null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ boolean hasValidShortcutInfo(String shortcutId, String packageName,
+ UserHandle user) {
+ ShortcutInfo shortcutInfo = getShortcutInfo(shortcutId, packageName, user);
+ return shortcutInfo != null && shortcutInfo.isLongLived();
+ }
+
+ /**
+ * Shortcut based bubbles require some extra work to listen for shortcut changes.
+ *
+ * @param r the notification record to check
+ * @param removedNotification true if this notification is being removed
+ * @param handler handler to register the callback with
+ */
+ void maybeListenForShortcutChangesForBubbles(NotificationRecord r, boolean removedNotification,
+ Handler handler) {
+ final String shortcutId = r.getNotification().getBubbleMetadata() != null
+ ? r.getNotification().getBubbleMetadata().getShortcutId()
+ : null;
+ if (shortcutId == null) {
+ return;
+ }
+ if (r.getNotification().isBubbleNotification() && !removedNotification) {
+ // Must track shortcut based bubbles in case the shortcut is removed
+ HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
+ r.getSbn().getPackageName());
+ if (packageBubbles == null) {
+ packageBubbles = new HashMap<>();
+ }
+ packageBubbles.put(shortcutId, r.getKey());
+ mActiveShortcutBubbles.put(r.getSbn().getPackageName(), packageBubbles);
+ if (!mLauncherAppsCallbackRegistered) {
+ mLauncherAppsService.registerCallback(mLauncherAppsCallback, handler);
+ mLauncherAppsCallbackRegistered = true;
+ }
+ } else {
+ // No longer track shortcut
+ HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
+ r.getSbn().getPackageName());
+ if (packageBubbles != null) {
+ packageBubbles.remove(shortcutId);
+ }
+ if (packageBubbles != null && packageBubbles.isEmpty()) {
+ mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
+ }
+ if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
+ mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+ mLauncherAppsCallbackRegistered = false;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9dddf7b..28d7c13 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -314,8 +314,41 @@
@GuardedBy("mLock")
private int mParentSessionId;
+ static class FileEntry {
+ private final int mIndex;
+ private final InstallationFile mFile;
+
+ FileEntry(int index, InstallationFile file) {
+ this.mIndex = index;
+ this.mFile = file;
+ }
+
+ int getIndex() {
+ return this.mIndex;
+ }
+
+ InstallationFile getFile() {
+ return this.mFile;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof FileEntry)) {
+ return false;
+ }
+ final FileEntry rhs = (FileEntry) obj;
+ return (mFile.getLocation() == rhs.mFile.getLocation()) && TextUtils.equals(
+ mFile.getName(), rhs.mFile.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFile.getLocation(), mFile.getName());
+ }
+ }
+
@GuardedBy("mLock")
- private ArrayList<InstallationFile> mFiles = new ArrayList<>();
+ private ArraySet<FileEntry> mFiles = new ArraySet<>();
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@@ -519,8 +552,12 @@
this.mParentSessionId = parentSessionId;
if (files != null) {
- for (InstallationFile file : files) {
- mFiles.add(file);
+ for (int i = 0, size = files.length; i < size; ++i) {
+ InstallationFile file = files[i];
+ if (!mFiles.add(new FileEntry(i, file))) {
+ throw new IllegalArgumentException(
+ "Trying to add a duplicate installation file");
+ }
}
}
@@ -754,9 +791,19 @@
return result;
}
- String[] result = new String[mFiles.size()];
- for (int i = 0, size = mFiles.size(); i < size; ++i) {
- result[i] = mFiles.get(i).getName();
+ InstallationFile[] files = getInstallationFilesLocked();
+ String[] result = new String[files.length];
+ for (int i = 0, size = files.length; i < size; ++i) {
+ result[i] = files[i].getName();
+ }
+ return result;
+ }
+
+ @GuardedBy("mLock")
+ private InstallationFile[] getInstallationFilesLocked() {
+ final InstallationFile[] result = new InstallationFile[mFiles.size()];
+ for (FileEntry fileEntry : mFiles) {
+ result[fileEntry.getIndex()] = fileEntry.getFile();
}
return result;
}
@@ -2448,7 +2495,10 @@
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("addFile");
- mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature));
+ if (!mFiles.add(new FileEntry(mFiles.size(),
+ new InstallationFile(location, name, lengthBytes, metadata, signature)))) {
+ throw new IllegalArgumentException("File already added: " + name);
+ }
}
}
@@ -2466,7 +2516,10 @@
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("removeFile");
- mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null));
+ if (!mFiles.add(new FileEntry(mFiles.size(),
+ new InstallationFile(location, getRemoveMarkerName(name), -1, null, null)))) {
+ throw new IllegalArgumentException("File already removed: " + name);
+ }
}
}
@@ -2486,7 +2539,8 @@
final List<InstallationFileParcel> addedFiles = new ArrayList<>();
final List<String> removedFiles = new ArrayList<>();
- for (InstallationFile file : mFiles) {
+ final InstallationFile[] files = getInstallationFilesLocked();
+ for (InstallationFile file : files) {
if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) {
addedFiles.add(file.getData());
continue;
@@ -3007,7 +3061,9 @@
writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
out.endTag(null, TAG_CHILD_SESSION);
}
- for (InstallationFile file : mFiles) {
+
+ final InstallationFile[] files = getInstallationFilesLocked();
+ for (InstallationFile file : getInstallationFilesLocked()) {
out.startTag(null, TAG_SESSION_FILE);
writeIntAttribute(out, ATTR_LOCATION, file.getLocation());
writeStringAttribute(out, ATTR_NAME, file.getName());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 221e7dc..24ebd32 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24192,6 +24192,12 @@
}
mAppsFilter.getFeatureConfig().enableLogging(pkg.appId, enable);
}
+
+ @Override
+ public boolean isSystemPackage(@NonNull String packageName) {
+ return packageName.equals(
+ PackageManagerService.this.ensureSystemPackageName(packageName));
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index be17dd8..8a9f1b3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3017,9 +3017,11 @@
private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes,
boolean isApex) throws RemoteException {
- PackageInstaller.Session session = new PackageInstaller.Session(
- mInterface.getPackageInstaller().openSession(sessionId));
+ PackageInstaller.Session session = null;
try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+
// 1. Single file from stdin.
if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
final String name = "base." + (isApex ? "apex" : "apk");
@@ -3043,6 +3045,10 @@
}
}
return 0;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Failed to add file(s), reason: " + e);
+ getOutPrintWriter().println("Failure [failed to add file(s)]");
+ return 1;
} finally {
IoUtils.closeQuietly(session);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index df3c83a..1c2fcc1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1300,14 +1300,15 @@
@Override
public boolean hasBadge(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "hasBadge");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasBadge");
final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
return userTypeDetails != null && userTypeDetails.hasBadge();
}
@Override
public @StringRes int getUserBadgeLabelResId(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeLabelResId");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "getUserBadgeLabelResId");
final UserInfo userInfo = getUserInfoNoChecks(userId);
final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo);
if (userInfo == null || userTypeDetails == null || !userTypeDetails.hasBadge()) {
@@ -1320,7 +1321,8 @@
@Override
public @ColorRes int getUserBadgeColorResId(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeColorResId");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "getUserBadgeColorResId");
final UserInfo userInfo = getUserInfoNoChecks(userId);
final UserTypeDetails userTypeDetails = getUserTypeDetails(userInfo);
if (userInfo == null || userTypeDetails == null || !userTypeDetails.hasBadge()) {
@@ -1333,7 +1335,7 @@
@Override
public @DrawableRes int getUserIconBadgeResId(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserIconBadgeResId");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserIconBadgeResId");
final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
Slog.e(LOG_TAG, "Requested icon badge for non-badged user " + userId);
@@ -1344,7 +1346,7 @@
@Override
public @DrawableRes int getUserBadgeResId(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserBadgeResId");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserBadgeResId");
final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
Slog.e(LOG_TAG, "Requested badge for non-badged user " + userId);
@@ -1355,7 +1357,7 @@
@Override
public @DrawableRes int getUserBadgeNoBackgroundResId(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId,
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
"getUserBadgeNoBackgroundResId");
final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
if (userTypeDetails == null || !userTypeDetails.hasBadge()) {
@@ -1367,7 +1369,7 @@
@Override
public boolean isProfile(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isProfile");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -1376,7 +1378,7 @@
@Override
public boolean isManagedProfile(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isManagedProfile");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isManagedProfile");
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isManagedProfile();
@@ -1385,19 +1387,20 @@
@Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId,
+ "isUserUnlockingOrUnlocked");
return mLocalService.isUserUnlockingOrUnlocked(userId);
}
@Override
public boolean isUserUnlocked(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
return mLocalService.isUserUnlocked(userId);
}
@Override
public boolean isUserRunning(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserRunning");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isUserRunning");
return mLocalService.isUserRunning(userId);
}
@@ -1437,7 +1440,7 @@
}
}
- private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId,
+ private void checkManageOrInteractPermissionIfCallerInOtherProfileGroup(@UserIdInt int userId,
String name) {
final int callingUserId = UserHandle.getCallingUserId();
if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
@@ -1466,11 +1469,7 @@
@Override
public boolean isPreCreated(@UserIdInt int userId) {
- final int callingUserId = UserHandle.getCallingUserId();
- if (callingUserId != userId && !hasManageUsersPermission()) {
- throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
- + " is pre-created");
- }
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isPreCreated");
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.preCreated;
@@ -1826,7 +1825,7 @@
/** @return a specific user restriction that's in effect currently. */
@Override
public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "hasUserRestriction");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasUserRestriction");
return mLocalService.hasUserRestriction(restrictionKey, userId);
}
@@ -1951,7 +1950,7 @@
*/
@Override
public Bundle getUserRestrictions(@UserIdInt int userId) {
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserRestrictions");
+ checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserRestrictions");
return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index beba106..8b6e9a4 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -16,11 +16,13 @@
package com.android.server.power.batterysaver;
import android.Manifest;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
import android.hardware.power.V1_0.PowerHint;
import android.os.BatteryManager;
import android.os.BatterySaverPolicyConfig;
@@ -35,6 +37,7 @@
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -49,6 +52,7 @@
import java.util.ArrayList;
import java.util.Objects;
+import java.util.Optional;
/**
* Responsible for battery saver mode transition logic.
@@ -112,6 +116,14 @@
*/
private final Plugin[] mPlugins;
+ /**
+ * Package name that will receive an explicit manifest broadcast for
+ * {@link PowerManager#ACTION_POWER_SAVE_MODE_CHANGED}. It's {@code null} if it hasn't been
+ * retrieved yet.
+ */
+ @Nullable
+ private Optional<String> mPowerSaveModeChangedListenerPackage;
+
public static final int REASON_PERCENTAGE_AUTOMATIC_ON = 0;
public static final int REASON_PERCENTAGE_AUTOMATIC_OFF = 1;
public static final int REASON_MANUAL_ON = 2;
@@ -494,6 +506,14 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ // Send the broadcast to a manifest-registered receiver that is specified in the config.
+ if (getPowerSaveModeChangedListenerPackage().isPresent()) {
+ intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
+ .setPackage(getPowerSaveModeChangedListenerPackage().get())
+ .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
// Send internal version that requires signature permission.
intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -508,6 +528,20 @@
}
}
+ private Optional<String> getPowerSaveModeChangedListenerPackage() {
+ if (mPowerSaveModeChangedListenerPackage == null) {
+ String configPowerSaveModeChangedListenerPackage =
+ mContext.getString(R.string.config_powerSaveModeChangedListenerPackage);
+ mPowerSaveModeChangedListenerPackage =
+ LocalServices
+ .getService(PackageManagerInternal.class)
+ .isSystemPackage(configPowerSaveModeChangedListenerPackage)
+ ? Optional.of(configPowerSaveModeChangedListenerPackage)
+ : Optional.empty();
+ }
+ return mPowerSaveModeChangedListenerPackage;
+ }
+
private void updateBatterySavingStats() {
final PowerManager pm = getPowerManager();
if (pm == null) {
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index 856a40f..2b793c8 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -81,22 +81,18 @@
/** Returns whether the file has fs-verity enabled. */
public static boolean hasFsverity(@NonNull String filePath) {
- // NB: only measure but not check the actual measurement here. As long as this succeeds,
- // the file is on readable if the measurement can be verified against a trusted key, and
- // this is good enough for installed apps.
- int errno = measureFsverityNative(filePath);
- if (errno != 0) {
- if (errno != OsConstants.ENODATA) {
- Slog.e(TAG, "Failed to measure fs-verity, errno " + errno + ": " + filePath);
- }
+ int retval = statxForFsverityNative(filePath);
+ if (retval < 0) {
+ Slog.e(TAG, "Failed to check whether fs-verity is enabled, errno " + -retval + ": "
+ + filePath);
return false;
}
- return true;
+ return (retval == 1);
}
private static native int enableFsverityNative(@NonNull String filePath,
@NonNull byte[] pkcs7Signature);
- private static native int measureFsverityNative(@NonNull String filePath);
+ private static native int statxForFsverityNative(@NonNull String filePath);
/**
* Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 6c55926..bd63b2d 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -200,7 +200,7 @@
enforceTunerAccessPermission("requestFrontend");
enforceTrmAccessPermission("requestFrontend");
if (frontendHandle == null) {
- throw new RemoteException("frontendId can't be null");
+ throw new RemoteException("frontendHandle can't be null");
}
synchronized (mLock) {
try {
@@ -222,24 +222,28 @@
@Override
public boolean requestDemux(@NonNull TunerDemuxRequest request,
- @NonNull int[] demuxHandle) {
+ @NonNull int[] demuxHandle) throws RemoteException {
enforceTunerAccessPermission("requestDemux");
enforceTrmAccessPermission("requestDemux");
- if (DEBUG) {
- Slog.d(TAG, "requestDemux(request=" + request + ")");
+ if (demuxHandle == null) {
+ throw new RemoteException("demuxHandle can't be null");
}
- return true;
+ synchronized (mLock) {
+ return requestDemuxInternal(request, demuxHandle);
+ }
}
@Override
public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
- @NonNull int[] descrambleHandle) {
+ @NonNull int[] descrambleHandle) throws RemoteException {
enforceDescramblerAccessPermission("requestDescrambler");
enforceTrmAccessPermission("requestDescrambler");
- if (DEBUG) {
- Slog.d(TAG, "requestDescrambler(request=" + request + ")");
+ if (descrambleHandle == null) {
+ throw new RemoteException("descrambleHandle can't be null");
}
- return true;
+ synchronized (mLock) {
+ return requestDescramblerInternal(request, descrambleHandle);
+ }
}
@Override
@@ -481,6 +485,24 @@
}
@VisibleForTesting
+ boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestDemux(request=" + request + ")");
+ }
+ demuxHandle[0] = generateResourceHandle(TUNER_RESOURCE_TYPE_DEMUX, 0);
+ return true;
+ }
+
+ @VisibleForTesting
+ boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestDescrambler(request=" + request + ")");
+ }
+ descramblerHandle[0] = generateResourceHandle(TUNER_RESOURCE_TYPE_DESCRAMBLER, 0);
+ return true;
+ }
+
+ @VisibleForTesting
protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
private final IResourcesReclaimListener mListener;
private final int mClientId;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e73c928..c4545fa 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1351,11 +1351,16 @@
mLetterbox.attachInput(w);
}
getPosition(mTmpPoint);
- // Get the bounds of the "space-to-fill". In multi-window mode, the task-level
- // represents this. In fullscreen-mode, the stack does (since the orientation letterbox
- // is also applied to the task).
- Rect spaceToFill = (inMultiWindowMode() || getStack() == null)
- ? task.getDisplayedBounds() : getStack().getDisplayedBounds();
+ // Get the bounds of the "space-to-fill". The transformed bounds have the highest
+ // priority because the activity is launched in a rotated environment. In multi-window
+ // mode, the task-level represents this. In fullscreen-mode, the task container does
+ // (since the orientation letterbox is also applied to the task).
+ final Rect transformedBounds = getFixedRotationTransformDisplayBounds();
+ final Rect spaceToFill = transformedBounds != null
+ ? transformedBounds
+ : inMultiWindowMode()
+ ? task.getDisplayedBounds()
+ : getRootTask().getParent().getDisplayedBounds();
mLetterbox.layout(spaceToFill, w.getFrameLw(), mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
@@ -6362,33 +6367,17 @@
if (mCompatDisplayInsets != null) {
resolveSizeCompatModeConfiguration(newParentConfiguration);
} else {
- // We ignore activities' requested orientation in multi-window modes. Task level may
- // take them into consideration when calculating bounds.
if (inMultiWindowMode()) {
+ // We ignore activities' requested orientation in multi-window modes. Task level may
+ // take them into consideration when calculating bounds.
resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED;
- }
- final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- // Use tmp bounds to calculate aspect ratio so we can know whether the activity should
- // use restricted size (resolvedBounds may be the requested override bounds).
- mTmpBounds.setEmpty();
- applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
- // If the out bounds is not empty, it means the activity cannot fill parent's app
- // bounds, then the relative configuration (e.g. screen size, layout) needs to be
- // resolved according to the bounds.
- if (!mTmpBounds.isEmpty()) {
- final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
- resolvedBounds.set(mTmpBounds);
- // Exclude the horizontal decor area because the activity will be centered
- // horizontally in parent's app bounds to balance the visual appearance.
- resolvedBounds.left = parentAppBounds.left;
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getFixedRotationTransformDisplayInfo());
- final int offsetX = getHorizontalCenterOffset(
- parentAppBounds.width(), resolvedBounds.width());
- if (offsetX > 0) {
- offsetBounds(resolvedConfig, offsetX - resolvedBounds.left, 0 /* offsetY */);
+ // If the activity has requested override bounds, the configuration needs to be
+ // computed accordingly.
+ if (!matchParentBounds()) {
+ task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
}
+ } else {
+ resolveFullscreenConfiguration(newParentConfiguration);
}
}
@@ -6400,6 +6389,43 @@
}
/**
+ * Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by
+ * aspect ratio, the position will be centered horizontally in parent's app bounds to balance
+ * the visual appearance. The policy of aspect ratio has higher priority than the requested
+ * override bounds.
+ */
+ private void resolveFullscreenConfiguration(Configuration newParentConfiguration) {
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
+ // restricted size (resolved bounds may be the requested override bounds).
+ mTmpBounds.setEmpty();
+ applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
+ // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
+ // then there is space to be centered.
+ final boolean needToBeCentered = !mTmpBounds.isEmpty();
+ if (needToBeCentered) {
+ resolvedBounds.set(mTmpBounds);
+ // Exclude the horizontal decor area.
+ resolvedBounds.left = parentAppBounds.left;
+ }
+ if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
+ // Compute the configuration based on the resolved bounds. If aspect ratio doesn't
+ // restrict, the bounds should be the requested override bounds.
+ task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+ getFixedRotationTransformDisplayInfo());
+ }
+ if (needToBeCentered) {
+ // Offset to center relative to parent's app bounds.
+ final int offsetX = getHorizontalCenterOffset(
+ parentAppBounds.width(), resolvedBounds.width());
+ offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
+ }
+ }
+
+ /**
* Resolves consistent screen configuration for orientation and rotation changes without
* inheriting the parent bounds.
*/
@@ -6507,7 +6533,7 @@
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
final int screenPosX = parentAppBounds.left + offsetX;
final int screenPosY = parentBounds.top;
- if (screenPosX > 0 || screenPosY > 0) {
+ if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
mSizeCompatBounds.offset(screenPosX, screenPosY);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 94821ac..aad242d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -375,7 +375,7 @@
*/
final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
- /** @see #computeCompatSmallestWidth(boolean, int, int, int, DisplayCutout) */
+ /** @see #computeCompatSmallestWidth(boolean, int, int, int) */
private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
/**
@@ -1814,7 +1814,7 @@
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw,
- dh, displayCutout);
+ dh);
}
/**
@@ -1922,8 +1922,7 @@
mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
}
- private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh,
- DisplayCutout displayCutout) {
+ private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh) {
mTmpDisplayMetrics.setTo(mDisplayMetrics);
final DisplayMetrics tmpDm = mTmpDisplayMetrics;
final int unrotDw, unrotDh;
@@ -1934,19 +1933,21 @@
unrotDw = dw;
unrotDh = dh;
}
- int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh,
- displayCutout);
- sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw,
- displayCutout);
- sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh,
- displayCutout);
- sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw,
- displayCutout);
+ int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw,
+ unrotDh);
+ sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh,
+ unrotDw);
+ sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw,
+ unrotDh);
+ sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh,
+ unrotDw);
return sw;
}
private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
- DisplayMetrics dm, int dw, int dh, DisplayCutout displayCutout) {
+ DisplayMetrics dm, int dw, int dh) {
+ final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
+ rotation).getDisplayCutout();
dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
displayCutout);
dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
@@ -1987,20 +1988,20 @@
return;
}
int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
- sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode,
- displayInfo.displayCutout);
- sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode,
- displayInfo.displayCutout);
- sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode,
- displayInfo.displayCutout);
- sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode,
- displayInfo.displayCutout);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode);
outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
outConfig.screenLayout = sl;
}
private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
- int uiMode, DisplayCutout displayCutout) {
+ int uiMode) {
+ // Get the display cutout at this rotation.
+ final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
+ rotation).getDisplayCutout();
+
// Get the app screen size at this rotation.
int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout);
int h = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout);
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index fc358ce..bd5666d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -846,10 +846,9 @@
@VisibleForTesting
Set<Integer> getProfileIds(int userId) {
Set<Integer> userIds = new ArraySet<>();
- final List<UserInfo> profiles = mService.getUserManager().getProfiles(userId,
- false /* enabledOnly */);
- for (int i = profiles.size() - 1; i >= 0; --i) {
- userIds.add(profiles.get(i).id);
+ int[] profileIds = mService.getUserManager().getProfileIds(userId, false /* enabledOnly */);
+ for (int i = 0; i < profileIds.length; i++) {
+ userIds.add(Integer.valueOf(profileIds[i]));
}
return userIds;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 15a49a7..ebf1bc9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2695,7 +2695,9 @@
return true;
}
} else {
- if (r.intent.getComponent().equals(cls)) {
+ // Compare the target component instead of intent component so we don't miss if the
+ // activity uses alias.
+ if (r.mActivityComponent.equals(cls)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index 36861aa..a696daf 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -103,7 +103,7 @@
*/
void unfreeze(SurfaceControl.Transaction t) {
if (mSnapshot != null) {
- mSnapshot.destroy(t);
+ mSnapshot.cancelAnimation(t, false /* restarting */);
}
if (mLeash == null) {
return;
@@ -212,7 +212,7 @@
* @param t The transaction to use for all cancelling surface operations.
* @param restarting Whether we are restarting the animation.
*/
- private void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
+ void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
final SurfaceControl leash = mSurfaceControl;
final AnimationAdapter animation = mAnimation;
final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback =
@@ -229,7 +229,6 @@
}
}
if (!restarting) {
- // TODO: do we need to destroy?
destroy(t);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5ab5fbc..e78f2ee 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2127,14 +2127,26 @@
intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
}
+ /**
+ * Forces the app bounds related configuration can be computed by
+ * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
+ * ActivityRecord.CompatDisplayInsets)}.
+ */
+ private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
+ final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (appBounds != null) {
+ appBounds.setEmpty();
+ }
+ inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ }
+
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
if (overrideDisplayInfo != null) {
// Make sure the screen related configs can be computed by the provided display info.
- inOutConfig.windowConfiguration.setAppBounds(null);
inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
- inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
null /* compatInsets */);
@@ -2149,6 +2161,10 @@
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig,
@Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+ if (compatInsets != null) {
+ // Make sure the app bounds can be computed by the compat insets.
+ invalidateAppBoundsConfig(inOutConfig);
+ }
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
compatInsets);
}
@@ -3370,6 +3386,9 @@
} else {
info.pictureInPictureParams = mPictureInPictureParams;
}
+ info.topActivityInfo = mReuseActivitiesReport.top != null
+ ? mReuseActivitiesReport.top.info
+ : null;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 45e3690..3617570 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5696,6 +5696,9 @@
@Override
boolean prepareForSync(BLASTSyncEngine.TransactionReadyListener waitingListener,
int waitingId) {
+ if (!isVisible()) {
+ return false;
+ }
mWaitingListener = waitingListener;
mWaitingSyncId = waitingId;
mUsingBLASTSyncTransaction = true;
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index be11b86..0277f16 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -25,17 +25,14 @@
#include <errno.h>
#include <fcntl.h>
#include <linux/fsverity.h>
+#include <linux/stat.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <type_traits>
-
#include <android-base/unique_fd.h>
-const int kSha256Bytes = 32;
-
namespace android {
namespace {
@@ -69,30 +66,28 @@
return 0;
}
-int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
- using Storage = std::aligned_storage_t<sizeof(fsverity_digest) + kSha256Bytes>;
-
- Storage bytes;
- fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
- data->digest_size = kSha256Bytes; // the only input/output parameter
-
+// Returns whether the file has fs-verity enabled.
+// 0 if it is not present, 1 if is present, and -errno if there was an error.
+int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
ScopedUtfChars path(env, filePath);
- if (path.c_str() == nullptr) {
- return EINVAL;
+
+ struct statx out = {};
+ if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) {
+ return -errno;
}
- ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
- if (rfd.get() < 0) {
- return errno;
+
+ // Sanity check.
+ if ((out.stx_attributes_mask & STATX_ATTR_VERITY) == 0) {
+ ALOGE("Unexpected, STATX_ATTR_VERITY not supported by kernel");
+ return -ENOSYS;
}
- if (ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
- return errno;
- }
- return 0;
+
+ return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
}
const JNINativeMethod sMethods[] = {
- { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity },
- { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity },
+ {"enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity},
+ {"statxForFsverityNative", "(Ljava/lang/String;)I", (void *)statxForFsverity},
};
} // namespace
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 0551f2e..a587029 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -538,6 +538,7 @@
}
@Test
+ @Ignore("b/151150320")
public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSystemAudioModeRequest(
@@ -587,6 +588,7 @@
}
@Test
+ @Ignore("b/151150320")
public void handleRoutingChange_currentActivePortIsHome() {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x3000, SELF_PHYSICAL_ADDRESS);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 4a1af51..f72d622 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -139,6 +139,7 @@
}
@Test
+ @Ignore("b/151147315")
public void doNotWakeUpOnHotPlug_PlugIn() {
mWokenUp = false;
mHdmiCecLocalDevicePlayback.onHotplug(0, true);
@@ -146,6 +147,7 @@
}
@Test
+ @Ignore("b/151147315")
public void doNotWakeUpOnHotPlug_PlugOut() {
mWokenUp = false;
mHdmiCecLocalDevicePlayback.onHotplug(0, false);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 81545d4..d3a3e7e 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -480,6 +480,13 @@
assertThat(mService.getCurrentRules().getList()).containsExactly(rule);
}
+ @Test
+ public void getWhitelistedRuleProviders() throws Exception {
+ whitelistUsAsRuleProvider();
+
+ assertThat(mService.getWhitelistedRuleProviders()).containsExactly(TEST_FRAMEWORK_PACKAGE);
+ }
+
private void whitelistUsAsRuleProvider() {
Resources mockResources = mock(Resources.class);
when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 1b98fa8..155c6dd 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -28,6 +28,8 @@
import android.media.tv.tuner.frontend.FrontendSettings;
import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
+import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
+import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
@@ -524,4 +526,38 @@
assertThat(mTunerResourceManagerService.checkClientExists(clientId[0])).isFalse();
}
+
+ @Test
+ public void requestDemuxTest() {
+ // Register client
+ ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ int[] clientId = new int[1];
+ mTunerResourceManagerService.registerClientProfileInternal(
+ profile, null /*listener*/, clientId);
+ assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+ int[] demuxHandle = new int[1];
+ TunerDemuxRequest request = new TunerDemuxRequest(clientId[0]);
+ assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle))
+ .isTrue();
+ assertThat(getResourceIdFromHandle(demuxHandle[0])).isEqualTo(0);
+ }
+
+ @Test
+ public void requestDescramblerTest() {
+ // Register client
+ ResourceClientProfile profile = new ResourceClientProfile("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ int[] clientId = new int[1];
+ mTunerResourceManagerService.registerClientProfileInternal(
+ profile, null /*listener*/, clientId);
+ assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+ int[] desHandle = new int[1];
+ TunerDescramblerRequest request = new TunerDescramblerRequest(clientId[0]);
+ assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
+ .isTrue();
+ assertThat(getResourceIdFromHandle(desHandle[0])).isEqualTo(0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 7af3ec6..88f368e 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -20,7 +20,7 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
-import static android.app.usage.UsageStatsManager.REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
@@ -103,7 +103,8 @@
aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE);
aih.setAppStandbyBucket(PACKAGE_3, USER_ID, 2500, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
+ REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
aih.setAppStandbyBucket(PACKAGE_4, USER_ID, 2750, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
@@ -114,7 +115,8 @@
assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_MAIN_TIMEOUT);
assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED);
assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000),
- REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
+ REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000),
REASON_MAIN_FORCED_BY_USER);
@@ -133,7 +135,8 @@
assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_MAIN_TIMEOUT);
assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED);
assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000),
- REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
+ REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000),
REASON_MAIN_FORCED_BY_USER);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 387e62d..f070bff 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -28,6 +28,10 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
+import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER;
@@ -434,6 +438,11 @@
true);
}
+ private int getStandbyBucketReason(String packageName) {
+ return mController.getAppStandbyBucketReason(packageName, USER_ID,
+ mInjector.mElapsedRealtime);
+ }
+
private void assertBucket(int bucket) {
assertEquals(bucket, getStandbyBucket(mController, PACKAGE_1));
}
@@ -810,7 +819,7 @@
}
@Test
- public void testPredictionRaiseFromRestrictedTimeout() {
+ public void testPredictionRaiseFromRestrictedTimeout_highBucket() {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
// Way past all timeouts. App times out into RESTRICTED bucket.
@@ -823,6 +832,24 @@
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_PREDICTED);
+ assertBucket(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testPredictionRaiseFromRestrictedTimeout_lowBucket() {
+ reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+
+ // Way past all timeouts. App times out into RESTRICTED bucket.
+ mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
+ mController.checkIdleStates(USER_ID);
+ assertBucket(STANDBY_BUCKET_RESTRICTED);
+
+ // Prediction into a low bucket means no expectation of the app being used, so we shouldn't
+ // elevate the app from RESTRICTED.
+ mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_PREDICTED);
+ assertBucket(STANDBY_BUCKET_RESTRICTED);
}
@Test
@@ -987,6 +1014,79 @@
}
@Test
+ public void testSystemForcedFlags_NotAddedForUserForce() throws Exception {
+ final int expectedReason = REASON_MAIN_FORCED_BY_USER;
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+ REASON_MAIN_FORCED_BY_USER);
+ assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+ REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
+ assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ assertEquals(expectedReason, getStandbyBucketReason(PACKAGE_1));
+ }
+
+ @Test
+ public void testSystemForcedFlags_AddedForSystemForce() throws Exception {
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+ REASON_MAIN_DEFAULT);
+ mInjector.mElapsedRealtime += 4 * RESTRICTED_THRESHOLD;
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
+ REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
+ assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
+ getStandbyBucketReason(PACKAGE_1));
+
+ mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
+ assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ // Flags should be combined
+ assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
+ | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE, getStandbyBucketReason(PACKAGE_1));
+ }
+
+ @Test
+ public void testSystemForcedFlags_SystemForceChangesBuckets() throws Exception {
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+ REASON_MAIN_DEFAULT);
+ mInjector.mElapsedRealtime += 4 * RESTRICTED_THRESHOLD;
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+ REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE);
+ assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE,
+ getStandbyBucketReason(PACKAGE_1));
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+ REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE);
+ assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
+ // Flags should be combined
+ assertEquals(REASON_MAIN_FORCED_BY_SYSTEM
+ | REASON_SUB_FORCED_SYSTEM_FLAG_BACKGROUND_RESOURCE_USAGE
+ | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
+ getStandbyBucketReason(PACKAGE_1));
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ // Flags should be combined
+ assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY,
+ getStandbyBucketReason(PACKAGE_1));
+
+ mController.restrictApp(PACKAGE_1, USER_ID, REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED);
+ assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
+ // Flags should not be combined since the bucket changed.
+ assertEquals(REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED,
+ getStandbyBucketReason(PACKAGE_1));
+ }
+
+ @Test
public void testAddActiveDeviceAdmin() {
assertActiveAdmins(USER_ID, (String[]) null);
assertActiveAdmins(USER_ID2, (String[]) null);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java
new file mode 100644
index 0000000..9636342
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleCheckerTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BubbleCheckerTest extends UiServiceTestCase {
+
+ private static final String SHORTCUT_ID = "shortcut";
+ private static final String PKG = "pkg";
+ private static final String KEY = "key";
+ private static final int USER_ID = 1;
+
+ @Mock
+ ActivityManager mActivityManager;
+ @Mock
+ RankingConfig mRankingConfig;
+ @Mock
+ ShortcutHelper mShortcutHelper;
+
+ @Mock
+ NotificationRecord mNr;
+ @Mock
+ UserHandle mUserHandle;
+ @Mock
+ Notification mNotif;
+ @Mock
+ StatusBarNotification mSbn;
+ @Mock
+ NotificationChannel mChannel;
+ @Mock
+ Notification.BubbleMetadata mBubbleMetadata;
+ @Mock
+ PendingIntent mPendingIntent;
+ @Mock
+ Intent mIntent;
+
+ BubbleExtractor.BubbleChecker mBubbleChecker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mNr.getKey()).thenReturn(KEY);
+ when(mNr.getSbn()).thenReturn(mSbn);
+ when(mNr.getUser()).thenReturn(mUserHandle);
+ when(mUserHandle.getIdentifier()).thenReturn(USER_ID);
+ when(mNr.getChannel()).thenReturn(mChannel);
+ when(mSbn.getPackageName()).thenReturn(PKG);
+ when(mSbn.getUser()).thenReturn(mUserHandle);
+ when(mNr.getNotification()).thenReturn(mNotif);
+ when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
+
+ mBubbleChecker = new BubbleExtractor.BubbleChecker(mContext,
+ mShortcutHelper,
+ mRankingConfig,
+ mActivityManager);
+ }
+
+ void setUpIntentBubble() {
+ when(mPendingIntent.getIntent()).thenReturn(mIntent);
+ when(mBubbleMetadata.getBubbleIntent()).thenReturn(mPendingIntent);
+ when(mBubbleMetadata.getShortcutId()).thenReturn(null);
+ }
+
+ void setUpShortcutBubble(boolean isValid) {
+ when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
+ when(mShortcutHelper.hasValidShortcutInfo(SHORTCUT_ID, PKG, mUserHandle))
+ .thenReturn(isValid);
+ when(mBubbleMetadata.getBubbleIntent()).thenReturn(null);
+ }
+
+ void setUpBubblesEnabled(boolean feature, boolean app, boolean channel) {
+ when(mRankingConfig.bubblesEnabled()).thenReturn(feature);
+ when(mRankingConfig.areBubblesAllowed(PKG, USER_ID)).thenReturn(app);
+ when(mChannel.canBubble()).thenReturn(channel);
+ }
+
+ void setUpActivityIntent(boolean isResizable) {
+ when(mPendingIntent.getIntent()).thenReturn(mIntent);
+ ActivityInfo info = new ActivityInfo();
+ info.resizeMode = isResizable
+ ? RESIZE_MODE_RESIZEABLE
+ : RESIZE_MODE_UNRESIZEABLE;
+ when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
+ }
+
+ //
+ // canBubble
+ //
+
+ @Test
+ public void testCanBubble_true_intentBubble() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ setUpIntentBubble();
+ setUpActivityIntent(true /* isResizable */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ assertTrue(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubble_true_shortcutBubble() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ setUpShortcutBubble(true /* isValid */);
+ assertTrue(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubble_false_noIntentInvalidShortcut() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ setUpShortcutBubble(false /* isValid */);
+ assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubble_false_noIntentNoShortcut() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ when(mBubbleMetadata.getBubbleIntent()).thenReturn(null);
+ when(mBubbleMetadata.getShortcutId()).thenReturn(null);
+ assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubbble_false_noMetadata() {
+ setUpBubblesEnabled(true/* feature */, true /* app */, true /* channel */);
+ when(mNotif.getBubbleMetadata()).thenReturn(null);
+ assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubble_false_bubblesNotEnabled() {
+ setUpBubblesEnabled(false /* feature */, true /* app */, true /* channel */);
+ assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubble_false_packageNotAllowed() {
+ setUpBubblesEnabled(true /* feature */, false /* app */, true /* channel */);
+ assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ @Test
+ public void testCanBubble_false_channelNotAllowed() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, false /* channel */);
+ assertFalse(mBubbleChecker.canBubble(mNr, PKG, USER_ID));
+ }
+
+ //
+ // canLaunchInActivityView
+ //
+
+ @Test
+ public void testCanLaunchInActivityView_true() {
+ setUpActivityIntent(true /* resizable */);
+ assertTrue(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+ }
+
+ @Test
+ public void testCanLaunchInActivityView_false_noIntent() {
+ when(mPendingIntent.getIntent()).thenReturn(null);
+ assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+ }
+
+ @Test
+ public void testCanLaunchInActivityView_false_noInfo() {
+ when(mPendingIntent.getIntent()).thenReturn(mIntent);
+ when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(null);
+ assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+ }
+
+ @Test
+ public void testCanLaunchInActivityView_false_notResizable() {
+ setUpActivityIntent(false /* resizable */);
+ assertFalse(mBubbleChecker.canLaunchInActivityView(mContext, mPendingIntent, PKG));
+ }
+
+ //
+ // isNotificationAppropriateToBubble
+ //
+
+ @Test
+ public void testIsNotifAppropriateToBubble_true() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ setUpIntentBubble();
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpActivityIntent(true /* resizable */);
+ doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
+
+ assertTrue(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
+ }
+
+ @Test
+ public void testIsNotifAppropriateToBubble_false_lowRam() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(true);
+ setUpActivityIntent(true /* resizable */);
+ doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
+
+ assertFalse(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
+ }
+
+ @Test
+ public void testIsNotifAppropriateToBubble_false_notMessageStyle() {
+ setUpBubblesEnabled(true /* feature */, true /* app */, true /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpActivityIntent(true /* resizable */);
+ doReturn(Notification.BigPictureStyle.class).when(mNotif).getNotificationStyle();
+
+ assertFalse(mBubbleChecker.isNotificationAppropriateToBubble(mNr));
+ }
+
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 7459c4b..c7cef05 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -21,6 +21,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -46,6 +47,7 @@
public class BubbleExtractorTest extends UiServiceTestCase {
@Mock RankingConfig mConfig;
+ BubbleExtractor mBubbleExtractor;
private String mPkg = "com.android.server.notification";
private int mId = 1001;
@@ -57,6 +59,10 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mBubbleExtractor = new BubbleExtractor();
+ mBubbleExtractor.initialize(mContext, mock(NotificationUsageStats.class));
+ mBubbleExtractor.setConfig(mConfig);
+ mBubbleExtractor.setShortcutHelper(mock(ShortcutHelper.class));
}
private NotificationRecord getNotificationRecord(boolean allow, int importanceHigh) {
@@ -83,70 +89,55 @@
@Test
public void testAppYesChannelNo() {
- BubbleExtractor extractor = new BubbleExtractor();
- extractor.setConfig(mConfig);
-
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
- extractor.process(r);
+ mBubbleExtractor.process(r);
assertFalse(r.canBubble());
}
@Test
public void testAppNoChannelYes() throws Exception {
- BubbleExtractor extractor = new BubbleExtractor();
- extractor.setConfig(mConfig);
-
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
- extractor.process(r);
+ mBubbleExtractor.process(r);
assertFalse(r.canBubble());
}
@Test
public void testAppYesChannelYes() {
- BubbleExtractor extractor = new BubbleExtractor();
- extractor.setConfig(mConfig);
-
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
- extractor.process(r);
+ mBubbleExtractor.process(r);
assertTrue(r.canBubble());
}
@Test
public void testAppNoChannelNo() {
- BubbleExtractor extractor = new BubbleExtractor();
- extractor.setConfig(mConfig);
-
when(mConfig.bubblesEnabled()).thenReturn(true);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
- extractor.process(r);
+ mBubbleExtractor.process(r);
assertFalse(r.canBubble());
}
@Test
public void testAppYesChannelYesUserNo() {
- BubbleExtractor extractor = new BubbleExtractor();
- extractor.setConfig(mConfig);
-
when(mConfig.bubblesEnabled()).thenReturn(false);
when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
- extractor.process(r);
+ mBubbleExtractor.process(r);
assertFalse(r.canBubble());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index dc8d010..8e78047 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -35,7 +35,6 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.PendingIntent;
-import android.app.Person;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -191,7 +190,8 @@
tweak.canBubble(),
tweak.visuallyInterruptive(),
tweak.isConversation(),
- tweak.getShortcutInfo()
+ tweak.getShortcutInfo(),
+ tweak.isBubble()
);
assertNotEquals(nru, nru2);
}
@@ -270,7 +270,8 @@
canBubble(i),
visuallyInterruptive(i),
isConversation(i),
- getShortcutInfo(i)
+ getShortcutInfo(i),
+ isBubble(i)
);
rankings[i] = ranking;
}
@@ -394,6 +395,10 @@
return si;
}
+ private boolean isBubble(int index) {
+ return index % 4 == 0;
+ }
+
private void assertActionsEqual(
List<Notification.Action> expecteds, List<Notification.Action> actuals) {
assertEquals(expecteds.size(), actuals.size());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 64d481a..f179840 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -41,8 +41,6 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -204,6 +202,8 @@
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
+ private TestableBubbleChecker mTestableBubbleChecker;
+ private ShortcutHelper mShortcutHelper;
@Mock
private IPackageManager mPackageManager;
@Mock
@@ -286,6 +286,10 @@
super(context, logger, notificationInstanceIdSequence);
}
+ RankingHelper getRankingHelper() {
+ return mRankingHelper;
+ }
+
@Override
protected boolean isCallingUidSystem() {
countSystemChecks++;
@@ -337,6 +341,14 @@
interface NotificationAssistantAccessGrantedCallback {
void onGranted(ComponentName assistant, int userId, boolean granted);
}
+ }
+
+ private class TestableBubbleChecker extends BubbleExtractor.BubbleChecker {
+
+ TestableBubbleChecker(Context context, ShortcutHelper helper, RankingConfig config,
+ ActivityManager manager) {
+ super(context, helper, config, manager);
+ }
@Override
protected boolean canLaunchInActivityView(Context context, PendingIntent pendingIntent,
@@ -448,7 +460,16 @@
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.setAudioManager(mAudioManager);
- mService.setLauncherApps(mLauncherApps);
+
+ mShortcutHelper = mService.getShortcutHelper();
+ mShortcutHelper.setLauncherApps(mLauncherApps);
+
+ // Set the testable bubble extractor
+ RankingHelper rankingHelper = mService.getRankingHelper();
+ BubbleExtractor extractor = rankingHelper.findExtractor(BubbleExtractor.class);
+ mTestableBubbleChecker = new TestableBubbleChecker(mContext, mShortcutHelper,
+ mService.mPreferencesHelper, mActivityManager);
+ extractor.setBubbleChecker(mTestableBubbleChecker);
// Tests call directly into the Binder.
mBinderService = mService.getBinderService();
@@ -462,6 +483,15 @@
}
@After
+ public void assertNotificationRecordLoggerCallsValid() {
+ for (NotificationRecordLoggerFake.CallRecord call : mNotificationRecordLogger.getCalls()) {
+ if (call.wasLogged) {
+ assertNotNull(call.event);
+ }
+ }
+ }
+
+ @After
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
@@ -1150,10 +1180,10 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
- assertEquals(1, mNotificationRecordLogger.getCalls().size());
+ assertEquals(1, mNotificationRecordLogger.numCalls());
NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0);
- assertTrue(call.shouldLogReported);
+ assertTrue(call.wasLogged);
assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
call.event);
assertNotNull(call.r);
@@ -1179,18 +1209,18 @@
.setCategory(Notification.CATEGORY_ALARM).build();
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, update, 0);
waitForIdle();
- assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(2, mNotificationRecordLogger.numCalls());
- assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+ assertTrue(mNotificationRecordLogger.get(0).wasLogged);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
- mNotificationRecordLogger.get(0).event);
+ mNotificationRecordLogger.event(0));
assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
- assertTrue(mNotificationRecordLogger.get(1).shouldLogReported);
+ assertTrue(mNotificationRecordLogger.get(1).wasLogged);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED,
- mNotificationRecordLogger.get(1).event);
+ mNotificationRecordLogger.event(1));
// Instance ID doesn't change on update of an active notification
assertEquals(1, mNotificationRecordLogger.get(1).getInstanceId());
}
@@ -1203,13 +1233,13 @@
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0,
generateNotificationRecord(null).getNotification(), 0);
waitForIdle();
- assertEquals(2, mNotificationRecordLogger.getCalls().size());
- assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertTrue(mNotificationRecordLogger.get(0).wasLogged);
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
- mNotificationRecordLogger.get(0).event);
- assertFalse(mNotificationRecordLogger.get(1).shouldLogReported);
- assertNull(mNotificationRecordLogger.get(1).event);
+ mNotificationRecordLogger.event(0));
+ assertFalse(mNotificationRecordLogger.get(1).wasLogged);
+ assertNull(mNotificationRecordLogger.event(1));
}
@Test
@@ -1222,11 +1252,11 @@
notif.extras.putString(Notification.EXTRA_TITLE, "Changed title");
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0);
waitForIdle();
- assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(2, mNotificationRecordLogger.numCalls());
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
- mNotificationRecordLogger.get(0).event);
- assertNull(mNotificationRecordLogger.get(1).event);
+ mNotificationRecordLogger.event(0));
+ assertNull(mNotificationRecordLogger.event(1));
}
@Test
@@ -1241,23 +1271,23 @@
waitForIdle();
mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0);
waitForIdle();
- assertEquals(3, mNotificationRecordLogger.getCalls().size());
+ assertEquals(3, mNotificationRecordLogger.numCalls());
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
- mNotificationRecordLogger.get(0).event);
- assertTrue(mNotificationRecordLogger.get(0).shouldLogReported);
+ mNotificationRecordLogger.event(0));
+ assertTrue(mNotificationRecordLogger.get(0).wasLogged);
assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
assertEquals(
NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL,
- mNotificationRecordLogger.get(1).event);
+ mNotificationRecordLogger.event(1));
assertEquals(1, mNotificationRecordLogger.get(1).getInstanceId());
assertEquals(
NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED,
- mNotificationRecordLogger.get(2).event);
- assertTrue(mNotificationRecordLogger.get(2).shouldLogReported);
+ mNotificationRecordLogger.event(2));
+ assertTrue(mNotificationRecordLogger.get(2).wasLogged);
// New instance ID because notification was canceled before re-post
assertEquals(2, mNotificationRecordLogger.get(2).getInstanceId());
}
@@ -1269,7 +1299,7 @@
waitForIdle();
// The notification record logger doesn't even get called when a nonexistent notification
// is cancelled, because that happens very frequently and is not interesting.
- assertEquals(0, mNotificationRecordLogger.getCalls().size());
+ assertEquals(0, mNotificationRecordLogger.numCalls());
}
@Test
@@ -2527,6 +2557,13 @@
// only snooze the one notification
verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
assertTrue(nonGrouped.getStats().hasSnoozed());
+
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(
+ NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+ mNotificationRecordLogger.event(1));
}
@Test
@@ -2569,6 +2606,12 @@
// only snooze the one child
verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
+
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(NotificationRecordLogger.NotificationCancelledEvent
+ .NOTIFICATION_CANCEL_SNOOZED, mNotificationRecordLogger.event(1));
}
@Test
@@ -2588,6 +2631,18 @@
// snooze child and summary
verify(mSnoozeHelper, times(2)).snooze(any(NotificationRecord.class), anyLong());
+
+ assertEquals(4, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(
+ NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+ mNotificationRecordLogger.event(1));
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+ mNotificationRecordLogger.event(2));
+ assertEquals(
+ NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+ mNotificationRecordLogger.event(3));
}
@Test
@@ -2603,6 +2658,13 @@
// snooze child only
verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
+
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(
+ NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_SNOOZED,
+ mNotificationRecordLogger.event(1));
}
@Test
@@ -3386,6 +3448,10 @@
mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied());
verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.getSbn()));
+
+ assertEquals(1, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DIRECT_REPLIED,
+ mNotificationRecordLogger.event(0));
}
@Test
@@ -3404,6 +3470,12 @@
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true),
eq((false)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
+
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DETAIL_OPEN_USER,
+ mNotificationRecordLogger.event(0));
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DETAIL_CLOSE_USER,
+ mNotificationRecordLogger.event(1));
}
@Test
@@ -3465,11 +3537,11 @@
// Using mService.addNotification() does not generate a NotificationRecordLogger log,
// so we only get the cancel notification.
- assertEquals(1, mNotificationRecordLogger.getCalls().size());
+ assertEquals(1, mNotificationRecordLogger.numCalls());
assertEquals(
NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD,
- mNotificationRecordLogger.get(0).event);
+ mNotificationRecordLogger.event(0));
assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
}
@@ -4351,9 +4423,9 @@
{NotificationVisibility.obtain(r.getKey(), 1, 1, true)},
new NotificationVisibility[]{});
- assertEquals(1, mNotificationRecordLogger.getCalls().size());
+ assertEquals(1, mNotificationRecordLogger.numCalls());
assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN,
- mNotificationRecordLogger.get(0).event);
+ mNotificationRecordLogger.event(0));
assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
mService.mNotificationDelegate.onNotificationVisibilityChanged(
@@ -4362,9 +4434,9 @@
{NotificationVisibility.obtain(r.getKey(), 1, 1, true)}
);
- assertEquals(2, mNotificationRecordLogger.getCalls().size());
+ assertEquals(2, mNotificationRecordLogger.numCalls());
assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE,
- mNotificationRecordLogger.get(1).event);
+ mNotificationRecordLogger.event(1));
assertEquals(1, mNotificationRecordLogger.get(1).getInstanceId());
}
@@ -4788,6 +4860,12 @@
mService.mNotificationDelegate.onPanelHidden();
verify(mAssistants, times(1)).onPanelHidden();
+
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_OPEN,
+ mNotificationRecordLogger.event(0));
+ assertEquals(NotificationRecordLogger.NotificationPanelEvent.NOTIFICATION_PANEL_CLOSE,
+ mNotificationRecordLogger.event(1));
}
@Test
@@ -4806,6 +4884,9 @@
modifiedBeforeSending);
verify(mAssistants).notifyAssistantSuggestedReplySent(
eq(r.getSbn()), eq(reply), eq(generatedByAssistant));
+ assertEquals(1, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED,
+ mNotificationRecordLogger.event(0));
}
@Test
@@ -4825,6 +4906,10 @@
generatedByAssistant);
verify(mAssistants).notifyAssistantActionClicked(
eq(r.getSbn()), eq(actionIndex), eq(action), eq(generatedByAssistant));
+
+ assertEquals(1, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_ACTION_CLICKED,
+ mNotificationRecordLogger.event(0));
}
@Test
@@ -5489,6 +5574,10 @@
StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
assertEquals(1, mService.getNotificationRecordCount());
+
+ assertEquals(1, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationCancelledEvent
+ .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0));
}
@Test
@@ -5710,6 +5799,9 @@
@Test
public void testOnBubbleNotificationSuppressionChanged() throws Exception {
+ // Bubbles are allowed!
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+
// Bubble notification
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
@@ -6111,8 +6203,10 @@
assertTrue(notif.isBubbleNotification());
// Test: Remove the shortcut
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(),
new UserHandle(mUid));
+ waitForIdle();
// Verify:
@@ -6125,6 +6219,58 @@
assertFalse(notif2.isBubbleNotification());
}
+
+ @Test
+ public void testNotificationBubbles_shortcut_stopListeningWhenNotifRemoved()
+ throws RemoteException {
+ // Bubbles are allowed!
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+
+ ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
+ ArgumentCaptor.forClass(LauncherApps.Callback.class);
+
+ // Messaging notification with shortcut info
+ Notification.BubbleMetadata metadata =
+ getBubbleMetadataBuilder().createShortcutBubble("someshortcutId").build();
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setBubbleMetadata(metadata);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // Pretend the shortcut exists
+ List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+ ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.isLongLived()).thenReturn(true);
+ shortcutInfos.add(info);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+
+ // Test: Send the bubble notification
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify:
+
+ // Make sure we register the callback for shortcut changes
+ verify(mLauncherApps, times(1)).registerCallback(launcherAppsCallback.capture(), any());
+
+ // yes allowed, yes messaging w/shortcut, yes bubble
+ Notification notif = mService.getNotificationRecord(nr.getSbn().getKey()).getNotification();
+ assertTrue(notif.isBubbleNotification());
+
+ // Test: Remove the notification
+ mBinderService.cancelNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify:
+
+ // Make sure callback is unregistered
+ verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
+ }
+
@Test
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
throws Exception {
@@ -6168,6 +6314,17 @@
// The bubble should still exist
StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifsAfter.length);
+
+ // Check we got the click log and associated dismissal logs
+ assertEquals(6, mNotificationRecordLogger.numCalls());
+ // Skip the notification-creation logs
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED,
+ mNotificationRecordLogger.event(3));
+ assertEquals(NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK,
+ mNotificationRecordLogger.event(4));
+ assertEquals(NotificationRecordLogger.NotificationCancelledEvent
+ .NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED,
+ mNotificationRecordLogger.event(5));
}
@Test
@@ -6188,6 +6345,11 @@
// THEN the bubble should still exist
StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifsAfter.length);
+
+ // Check we got the click log
+ assertEquals(1, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLICKED,
+ mNotificationRecordLogger.event(0));
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index 2a17bae..6b18cc6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -31,25 +31,29 @@
// The following fields are only relevant to maybeLogNotificationPosted() calls.
static final int INVALID = -1;
public int position = INVALID, buzzBeepBlink = INVALID;
- public boolean shouldLogReported;
+ public boolean wasLogged;
CallRecord(NotificationRecord r, NotificationRecord old, int position,
int buzzBeepBlink) {
super(r, old);
this.position = position;
this.buzzBeepBlink = buzzBeepBlink;
- shouldLogReported = shouldLogReported(buzzBeepBlink);
- event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null;
+ wasLogged = shouldLogReported(buzzBeepBlink);
+ event = wasLogged ? NotificationReportedEvent.fromRecordPair(this) : null;
}
CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
super(r, null);
- shouldLogReported = false;
+ wasLogged = true;
this.event = event;
}
}
private List<CallRecord> mCalls = new ArrayList<>();
+ public int numCalls() {
+ return mCalls.size();
+ }
+
List<CallRecord> getCalls() {
return mCalls;
}
@@ -57,6 +61,9 @@
CallRecord get(int index) {
return mCalls.get(index);
}
+ UiEventLogger.UiEventEnum event(int index) {
+ return mCalls.get(index).event;
+ }
@Override
public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old,
@@ -65,13 +72,12 @@
}
@Override
- public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) {
- mCalls.add(new CallRecord(r,
- NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface)));
+ public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
+ mCalls.add(new CallRecord(r, event));
}
@Override
- public void logNotificationVisibility(NotificationRecord r, boolean visible) {
- mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible)));
+ public void log(UiEventLogger.UiEventEnum event) {
+ mCalls.add(new CallRecord(null, event));
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
index 551e186..5a527a2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleConditionProviderTest.java
@@ -3,8 +3,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import android.app.Application;
import android.content.Intent;
import android.net.Uri;
import android.service.notification.Condition;
@@ -45,7 +47,7 @@
null, // ActivityThread not actually used in Service
ScheduleConditionProvider.class.getName(),
null, // token not needed when not talking with the activity manager
- null,
+ mock(Application.class),
null // mocked services don't talk with the activity manager
);
service.onCreate();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
new file mode 100644
index 0000000..50fb9b4
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper
+public class ShortcutHelperTest extends UiServiceTestCase {
+
+ private static final String SHORTCUT_ID = "shortcut";
+ private static final String PKG = "pkg";
+ private static final String KEY = "key";
+
+ @Mock
+ LauncherApps mLauncherApps;
+ @Mock
+ ShortcutHelper.ShortcutListener mShortcutListener;
+ @Mock
+ NotificationRecord mNr;
+ @Mock
+ Notification mNotif;
+ @Mock
+ StatusBarNotification mSbn;
+ @Mock
+ Notification.BubbleMetadata mBubbleMetadata;
+
+ ShortcutHelper mShortcutHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mShortcutHelper = new ShortcutHelper(mLauncherApps, mShortcutListener);
+ when(mNr.getKey()).thenReturn(KEY);
+ when(mNr.getSbn()).thenReturn(mSbn);
+ when(mSbn.getPackageName()).thenReturn(PKG);
+ when(mNr.getNotification()).thenReturn(mNotif);
+ when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
+ when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
+ }
+
+ private LauncherApps.Callback addShortcutBubbleAndVerifyListener() {
+ when(mNotif.isBubbleNotification()).thenReturn(true);
+
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+ false /* removed */,
+ null /* handler */);
+
+ ArgumentCaptor<LauncherApps.Callback> launcherAppsCallback =
+ ArgumentCaptor.forClass(LauncherApps.Callback.class);
+
+ verify(mLauncherApps, times(1)).registerCallback(
+ launcherAppsCallback.capture(), any());
+ return launcherAppsCallback.getValue();
+ }
+
+ @Test
+ public void testBubbleAdded_listenedAdded() {
+ addShortcutBubbleAndVerifyListener();
+ }
+
+ @Test
+ public void testBubbleRemoved_listenerRemoved() {
+ // First set it up to listen
+ addShortcutBubbleAndVerifyListener();
+
+ // Then remove the notif
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+ true /* removed */,
+ null /* handler */);
+
+ verify(mLauncherApps, times(1)).unregisterCallback(any());
+ }
+
+ @Test
+ public void testBubbleNoLongerBubble_listenerRemoved() {
+ // First set it up to listen
+ addShortcutBubbleAndVerifyListener();
+
+ // Then make it not a bubble
+ when(mNotif.isBubbleNotification()).thenReturn(false);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+ false /* removed */,
+ null /* handler */);
+
+ verify(mLauncherApps, times(1)).unregisterCallback(any());
+ }
+
+ @Test
+ public void testListenerNotifiedOnShortcutRemoved() {
+ LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener();
+
+ List<ShortcutInfo> shortcutInfos = new ArrayList<>();
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcutInfos);
+
+ callback.onShortcutsChanged(PKG, shortcutInfos, mock(UserHandle.class));
+ verify(mShortcutListener).onShortcutRemoved(mNr.getKey());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 277bc41..67b1dac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -27,6 +27,8 @@
import static org.junit.Assert.assertTrue;
import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -109,5 +111,27 @@
assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM,
mWm.getDefaultDisplayContentLocked().getWindowingMode());
}
+
+ /**
+ * This test ensures that an existing single instance activity with alias name can be found by
+ * the same activity info. So {@link ActivityStarter#getReusableTask} won't miss it that leads
+ * to create an unexpected new instance.
+ */
+ @Test
+ public void testFindActivityByTargetComponent() {
+ final ComponentName aliasComponent = ComponentName.createRelative(
+ ActivityTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME, ".AliasActivity");
+ final ComponentName targetComponent = ComponentName.createRelative(
+ aliasComponent.getPackageName(), ".TargetActivity");
+ final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+ .setComponent(aliasComponent)
+ .setTargetActivity(targetComponent.getClassName())
+ .setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
+ .setCreateTask(true)
+ .build();
+
+ assertEquals(activity, mWm.mRoot.findActivity(activity.intent, activity.info,
+ false /* compareIntentFilters */));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index ba4a82f..edf81ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -107,10 +107,16 @@
setUpApp(display);
// Put app window into freeform and then make it a compat app.
- mTask.setBounds(100, 100, 400, 600);
+ final Rect bounds = new Rect(100, 100, 400, 600);
+ mTask.setBounds(bounds);
prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(bounds, mActivity.getBounds());
- final Rect bounds = new Rect(mActivity.getBounds());
+ // The activity should be able to accept negative x position [-150, 100 - 150, 600].
+ final int dx = bounds.left + bounds.width() / 2;
+ mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom);
+ assertEquals(mTask.getBounds(), mActivity.getBounds());
+
final int density = mActivity.getConfiguration().densityDpi;
// change display configuration to fullscreen
@@ -231,11 +237,7 @@
// The position should be horizontal centered.
assertEquals((displayWidth - bounds.width()) / 2, bounds.left);
- final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
- mService.mWindowManager, mock(Session.class), new TestIWindow(),
- new WindowManager.LayoutParams(), mActivity);
- mActivity.addWindow(w);
- mActivity.mDisplayContent.mInputMethodTarget = w;
+ mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity);
// Make sure IME cannot attach to the app, otherwise IME window will also be shifted.
assertFalse(mActivity.mDisplayContent.isImeAttachedToApp());
}
@@ -475,6 +477,25 @@
assertEquals((int) (dw * maxAspect), mActivity.getBounds().width());
// The bounds should be horizontal centered: (2500-1900)/2=350.
assertEquals((dh - mActivity.getBounds().width()) / 2, mActivity.getBounds().left);
+
+ // The letterbox needs a main window to layout.
+ addWindowToActivity(mActivity);
+ // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
+ mActivity.mRootWindowContainer.performSurfacePlacement(false /* recoveringMemory */);
+ // The letterbox insets should be [350, 0 - 350, 0].
+ assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
+ mActivity.getLetterboxInsets());
+ }
+
+ private WindowState addWindowToActivity(ActivityRecord activity) {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+ final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
+ mService.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
+ WindowTestsBase.makeWindowVisible(w);
+ w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+ mActivity.addWindow(w);
+ return w;
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 5007e3a..bc81d5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -551,6 +551,7 @@
final Task task = createTaskInStack(stackController1, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer();
final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+ makeWindowVisible(w);
BLASTSyncEngine bse = new BLASTSyncEngine();
@@ -569,6 +570,28 @@
}
@Test
+ public void testBLASTCallbackWithInvisibleWindow() {
+ final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stackController1, 0 /* userId */);
+ final ITaskOrganizer organizer = registerMockOrganizer();
+ final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
+
+ BLASTSyncEngine bse = new BLASTSyncEngine();
+
+ BLASTSyncEngine.TransactionReadyListener transactionListener =
+ mock(BLASTSyncEngine.TransactionReadyListener.class);
+
+ int id = bse.startSyncSet(transactionListener);
+ bse.addToSyncSet(id, task);
+ bse.setReady(id);
+
+ // Since the window was invisible, the Task had no visible leaves and the sync should
+ // complete as soon as we call setReady.
+ verify(transactionListener)
+ .transactionReady(anyInt(), any());
+ }
+
+ @Test
public void testBLASTCallbackWithChildWindow() {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index d9ae48f..b3d7c0d 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -30,6 +30,7 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.ArrayUtils;
@@ -162,7 +163,7 @@
for (ApplicationInfo ai : candidates) {
String packageName = ai.packageName;
String[] restrictedCarrierApps = Resources.getSystem().getStringArray(
- android.R.array.config_restrictedPreinstalledCarrierApps);
+ R.array.config_restrictedPreinstalledCarrierApps);
boolean hasPrivileges = telephonyManager != null
&& telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index c62cec2..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -19,10 +19,12 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Build;
-import android.text.TextUtils;
import android.util.Log;
+import android.text.TextUtils;
import android.util.SparseIntArray;
+import com.android.internal.R;
+
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -1087,10 +1089,8 @@
private static void enableCountrySpecificEncodings() {
Resources r = Resources.getSystem();
// See comments in frameworks/base/core/res/res/values/config.xml for allowed values
- sEnabledSingleShiftTables = r.getIntArray(
- android.R.array.config_sms_enabled_single_shift_tables);
- sEnabledLockingShiftTables = r.getIntArray(
- android.R.array.config_sms_enabled_locking_shift_tables);
+ sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables);
+ sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables);
if (sEnabledSingleShiftTables.length > 0) {
sHighestEnabledSingleShiftCode =
diff --git a/telephony/common/com/google/android/mms/util/SqliteWrapper.java b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
index 4871434..31fe4d7 100644
--- a/telephony/common/com/google/android/mms/util/SqliteWrapper.java
+++ b/telephony/common/com/google/android/mms/util/SqliteWrapper.java
@@ -60,7 +60,8 @@
@UnsupportedAppUsage
public static void checkSQLiteException(Context context, SQLiteException e) {
if (isLowMemory(e)) {
- Toast.makeText(context, android.R.string.low_memory, Toast.LENGTH_SHORT).show();
+ Toast.makeText(context, com.android.internal.R.string.low_memory,
+ Toast.LENGTH_SHORT).show();
} else {
throw e;
}
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 34bac5d..3984bd7 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -19,9 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.TestApi;
import android.content.Context;
import android.telephony.SubscriptionManager;
@@ -44,8 +42,6 @@
* issues.
* @hide
*/
- @SystemApi
- @TestApi
// Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
// this value hard-coded in BroadcastReceiver.
@SuppressLint("ActionValue")
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 1a79bf7..4940cb2 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -297,7 +297,7 @@
mDataSpecificInfo = new DataSpecificRegistrationInfo(
maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo,
isUsingCarrierAggregation);
- updateNrState(mDataSpecificInfo);
+ updateNrState();
}
private NetworkRegistrationInfo(Parcel source) {
@@ -686,12 +686,12 @@
* DCNR is not restricted and NR is supported by the selected PLMN. Otherwise the use of 5G
* NR is restricted.
*
- * @param state data specific registration state contains the 5G NR indicators.
+ * @hide
*/
- private void updateNrState(DataSpecificRegistrationInfo state) {
+ public void updateNrState() {
mNrState = NR_STATE_NONE;
- if (state.isEnDcAvailable) {
- if (!state.isDcNrRestricted && state.isNrAvailable) {
+ if (mDataSpecificInfo.isEnDcAvailable) {
+ if (!mDataSpecificInfo.isDcNrRestricted && mDataSpecificInfo.isNrAvailable) {
mNrState = NR_STATE_NOT_RESTRICTED;
} else {
mNrState = NR_STATE_RESTRICTED;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index dd20d06..deba551 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1650,7 +1650,6 @@
* @return Current data network type
* @hide
*/
- @SystemApi
@TestApi
public @NetworkType int getDataNetworkType() {
final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d3afa4a..f6a305f 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -975,11 +975,7 @@
*
* @param packageName serves as the default package name if the package name that is
* associated with the user id is null.
- *
- * @hide
*/
- @SystemApi
- @TestApi
public void sendMultipartTextMessage(
@NonNull String destinationAddress, @Nullable String scAddress,
@NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2714ffc..e559c2a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1565,6 +1565,7 @@
* @hide
*/
@SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@SuppressLint("ActionValue")
public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED =
"android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
@@ -1786,6 +1787,7 @@
* @hide
*/
@SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@SuppressLint("ActionValue")
public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED =
"android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
@@ -5280,8 +5282,8 @@
* not present or not loaded
* @hide
*/
+ @UnsupportedAppUsage
@Nullable
- @SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String[] getIsimImpu() {
try {
@@ -9977,18 +9979,30 @@
}
/**
- * Gets the default Respond Via Message application
- * @param context context from the calling app
- * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * Gets the default Respond Via Message application, updating the cache if there is no
+ * respond-via-message application currently configured.
* @return component name of the app and class to direct Respond Via Message intent to, or
* {@code null} if the functionality is not supported.
* @hide
*/
@SystemApi
@TestApi
- public static @Nullable ComponentName getDefaultRespondViaMessageApplication(
- @NonNull Context context, boolean updateIfNeeded) {
- return SmsApplication.getDefaultRespondViaMessageApplication(context, updateIfNeeded);
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ public @Nullable ComponentName getAndUpdateDefaultRespondViaMessageApplication() {
+ return SmsApplication.getDefaultRespondViaMessageApplication(mContext, true);
+ }
+
+ /**
+ * Gets the default Respond Via Message application.
+ * @return component name of the app and class to direct Respond Via Message intent to, or
+ * {@code null} if the functionality is not supported.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+ public @Nullable ComponentName getDefaultRespondViaMessageApplication() {
+ return SmsApplication.getDefaultRespondViaMessageApplication(mContext, false);
}
/**
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 4b5303f..bd531da 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -21,7 +21,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
@@ -124,7 +123,7 @@
* @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
*/
@Override
- public void onUnregistered(@Nullable ImsReasonInfo info) {
+ public void onUnregistered(@NonNull ImsReasonInfo info) {
}
/**
@@ -136,7 +135,7 @@
@Override
public void onTechnologyChangeFailed(
@AccessNetworkConstants.TransportType int imsTransportType,
- @Nullable ImsReasonInfo info) {
+ @NonNull ImsReasonInfo info) {
}
}
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 00fa942..1a606b7 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -85,6 +85,7 @@
/**
* There is no existing configuration for the queried provisioning key.
+ * @hide
*/
public static final int PROVISIONING_RESULT_UNKNOWN = -1;
@@ -120,6 +121,7 @@
* Value is in String format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_AMR_CODEC_MODE_SET_VALUES = 0;
@@ -143,6 +145,7 @@
* Value is in String format.
* @see #setProvisioningStringValue(int, String)
* @see #getProvisioningStringValue(int)
+ * @hide
*/
public static final int KEY_AMR_WB_CODEC_MODE_SET_VALUES = 1;
@@ -154,6 +157,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_SESSION_TIMER_SEC = 2;
@@ -165,6 +169,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_MINIMUM_SIP_SESSION_EXPIRATION_TIMER_SEC = 3;
@@ -176,6 +181,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_INVITE_CANCELLATION_TIMER_MS = 4;
@@ -184,6 +190,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_TRANSITION_TO_LTE_DELAY_MS = 5;
@@ -192,6 +199,7 @@
* Value is in boolean format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_ENABLE_SILENT_REDIAL = 6;
@@ -207,6 +215,7 @@
* The value is an integer.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_T1_TIMER_VALUE_MS = 7;
@@ -218,6 +227,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_T2_TIMER_VALUE_MS = 8;
@@ -229,6 +239,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_TF_TIMER_VALUE_MS = 9;
@@ -241,6 +252,7 @@
* {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
@@ -253,6 +265,7 @@
* {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_VT_PROVISIONING_STATUS = 11;
@@ -261,6 +274,7 @@
* Value is in String format.
* @see #setProvisioningStringValue(int, String)
* @see #getProvisioningStringValue(int)
+ * @hide
*/
public static final int KEY_REGISTRATION_DOMAIN_NAME = 12;
@@ -270,18 +284,21 @@
* Valid values are {@link #SMS_FORMAT_3GPP} and {@link #SMS_FORMAT_3GPP2}.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SMS_FORMAT = 13;
/**
* Value used with {@link #KEY_SMS_FORMAT} to indicate 3GPP2 SMS format is used.
* See {@link android.telephony.SmsMessage#FORMAT_3GPP2} for more information.
+ * @hide
*/
public static final int SMS_FORMAT_3GPP2 = 0;
/**
* Value used with {@link #KEY_SMS_FORMAT} to indicate 3GPP SMS format is used.
* See {@link android.telephony.SmsMessage#FORMAT_3GPP} for more information.
+ * @hide
*/
public static final int SMS_FORMAT_3GPP = 1;
@@ -290,6 +307,7 @@
* Value is in Integer format. ON (1), OFF(0).
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SMS_OVER_IP_ENABLED = 14;
@@ -300,6 +318,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
@@ -310,6 +329,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_PUBLISH_OFFLINE_AVAILABILITY_TIMER_SEC = 16;
@@ -322,6 +342,7 @@
* enabled.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
@@ -334,6 +355,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
@@ -345,6 +367,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
@@ -356,6 +379,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
@@ -366,6 +390,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
@@ -376,6 +401,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
@@ -387,6 +413,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
@@ -395,6 +422,7 @@
* Value is in Integer format. Enable (1), Disable(0).
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_USE_GZIP_FOR_LIST_SUBSCRIPTION = 24;
@@ -407,6 +435,7 @@
* {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_EAB_PROVISIONING_STATUS = 25;
@@ -440,6 +469,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE = 28;
@@ -448,6 +478,7 @@
* Value is in Integer format. On (1), OFF(0).
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_MOBILE_DATA_ENABLED = 29;
@@ -456,12 +487,14 @@
* Value is in Integer format. Opted-in (1) Opted-out (0).
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_VOLTE_USER_OPT_IN_STATUS = 30;
/**
* Proxy for Call Session Control Function(P-CSCF) address for Local-BreakOut(LBO).
* Value is in String format.
+ * @hide
*/
public static final int KEY_LOCAL_BREAKOUT_PCSCF_ADDRESS = 31;
@@ -470,6 +503,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_KEEP_ALIVE_ENABLED = 32;
@@ -479,6 +513,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_REGISTRATION_RETRY_BASE_TIME_SEC = 33;
@@ -488,6 +523,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_REGISTRATION_RETRY_MAX_TIME_SEC = 34;
@@ -496,6 +532,7 @@
* Value is in integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RTP_SPEECH_START_PORT = 35;
@@ -505,6 +542,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RTP_SPEECH_END_PORT = 36;
@@ -514,6 +552,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_INVITE_REQUEST_TRANSMIT_INTERVAL_MS = 37;
@@ -523,6 +562,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_INVITE_ACK_WAIT_TIME_MS = 38;
@@ -532,6 +572,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_WAIT_TIME_MS = 39;
@@ -541,6 +582,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMIT_INTERVAL_MS = 40;
@@ -550,6 +592,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_NON_INVITE_TRANSACTION_TIMEOUT_TIMER_MS = 41;
@@ -559,6 +602,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_INVITE_RESPONSE_RETRANSMIT_INTERVAL_MS = 42;
@@ -568,6 +612,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_ACK_RECEIPT_WAIT_TIME_MS = 43;
@@ -577,6 +622,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_ACK_RETRANSMIT_WAIT_TIME_MS = 44;
@@ -586,6 +632,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_NON_INVITE_REQUEST_RETRANSMISSION_WAIT_TIME_MS = 45;
@@ -595,6 +642,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_SIP_NON_INVITE_RESPONSE_RETRANSMISSION_WAIT_TIME_MS = 46;
@@ -603,6 +651,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_AMR_WB_OCTET_ALIGNED_PAYLOAD_TYPE = 47;
@@ -611,6 +660,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_AMR_WB_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 48;
@@ -619,6 +669,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_AMR_OCTET_ALIGNED_PAYLOAD_TYPE = 49;
@@ -627,6 +678,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_AMR_BANDWIDTH_EFFICIENT_PAYLOAD_TYPE = 50;
@@ -635,6 +687,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_DTMF_WB_PAYLOAD_TYPE = 51;
@@ -643,6 +696,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_DTMF_NB_PAYLOAD_TYPE = 52;
@@ -651,12 +705,14 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_AMR_DEFAULT_ENCODING_MODE = 53;
/**
* SMS Public Service Identity.
* Value is in String format.
+ * @hide
*/
public static final int KEY_SMS_PUBLIC_SERVICE_IDENTITY = 54;
@@ -666,16 +722,19 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_VIDEO_QUALITY = 55;
/**
* Used with {@link #KEY_VIDEO_QUALITY} to indicate low video quality.
+ * @hide
*/
public static final int VIDEO_QUALITY_LOW = 0;
/**
* Used with {@link #KEY_VIDEO_QUALITY} to indicate high video quality.
+ * @hide
*/
public static final int VIDEO_QUALITY_HIGH = 1;
@@ -685,6 +744,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_LTE_THRESHOLD_1 = 56;
@@ -696,6 +756,7 @@
*
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_LTE_THRESHOLD_2 = 57;
@@ -707,6 +768,7 @@
*
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_LTE_THRESHOLD_3 = 58;
@@ -716,6 +778,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_1X_THRESHOLD = 59;
@@ -727,6 +790,7 @@
*
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_WIFI_THRESHOLD_A = 60;
@@ -737,6 +801,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_WIFI_THRESHOLD_B = 61;
@@ -746,6 +811,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_LTE_EPDG_TIMER_SEC = 62;
@@ -755,12 +821,14 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_WIFI_EPDG_TIMER_SEC = 63;
/**
* 1x ePDG timer (in seconds).
* Device shall not re-register on 1x until the T_ePDG_1x timer expires.
+ * @hide
*/
public static final int KEY_1X_EPDG_TIMER_SEC = 64;
@@ -769,6 +837,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_MULTIENDPOINT_ENABLED = 65;
@@ -777,6 +846,7 @@
* Value is in Integer format.
* @see #setProvisioningIntValue(int, int)
* @see #getProvisioningIntValue(int)
+ * @hide
*/
public static final int KEY_RTT_ENABLED = 66;
@@ -928,7 +998,7 @@
*
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @return an integer value for the provided key, or
- * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
+ * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
*/
@WorkerThread
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 57b9b7a..dc36edf 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,8 +19,6 @@
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,8 +35,6 @@
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
* @hide
*/
-@SystemApi
-@TestApi
public final class RcsContactUceCapability implements Parcelable {
/** Supports 1-to-1 chat */
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index 5c86ba7..1dbaff5 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -196,11 +196,11 @@
}
/**
- * Notifies the framework when the IMS Provider is deregistered from the IMS network.
+ * Notifies the framework when the IMS Provider is unregistered from the IMS network.
*
* @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
*/
- public void onUnregistered(@Nullable ImsReasonInfo info) {
+ public void onUnregistered(@NonNull ImsReasonInfo info) {
}
/**
@@ -211,7 +211,7 @@
*/
public void onTechnologyChangeFailed(
@AccessNetworkConstants.TransportType int imsTransportType,
- @Nullable ImsReasonInfo info) {
+ @NonNull ImsReasonInfo info) {
}
/**
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 14a64d2..7069e0a 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -175,9 +175,11 @@
*/
public final void onDeregistered(ImsReasonInfo info) {
updateToDisconnectedState(info);
+ // ImsReasonInfo should never be null.
+ final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
mCallbacks.broadcastAction((c) -> {
try {
- c.onDeregistered(info);
+ c.onDeregistered(reasonInfo);
} catch (RemoteException e) {
Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " +
"callback.");
@@ -194,9 +196,10 @@
*/
public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
ImsReasonInfo info) {
+ final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
mCallbacks.broadcastAction((c) -> {
try {
- c.onTechnologyChangeFailed(imsRadioTech, info);
+ c.onTechnologyChangeFailed(imsRadioTech, reasonInfo);
} catch (RemoteException e) {
Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " +
"callback.");
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
index 386a58e..8564f7a 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
@@ -349,6 +349,7 @@
/**
* Updates the configuration of the call barring for specified service class with password.
+ * @hide
*/
public int updateCallBarringWithPassword(int cbType, int action, @Nullable String[] barrList,
int serviceClass, @NonNull String password) {
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 8cc8cf4..dfaac2c 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -1063,6 +1063,20 @@
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
+ /**
+ * Ensure that passing a null list of failed packages does not cause any mitigation logic to
+ * execute.
+ */
+ @Test
+ public void testNullFailedPackagesList() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+ watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+
+ raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
+ assertThat(observer1.mMitigatedPackages).isEmpty();
+ }
+
private void adoptShellPermissions(String... permissions) {
InstrumentationRegistry
.getInstrumentation()
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
index e415170..f254e4d 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
@@ -34,9 +34,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.Window;
import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Callback;
import android.view.WindowInsetsAnimationControlListener;
@@ -101,7 +99,7 @@
&& !mRequestedController) {
mRequestedController = true;
v.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
- 1000, new LinearInterpolator(),
+ 1000, new LinearInterpolator(), null /* cancellationSignal */,
mCurrentRequest = new WindowInsetsAnimationControlListener() {
@Override
public void onReady(
@@ -208,7 +206,7 @@
if ((types & ime()) != 0 && !hasControl) {
hasControl = true;
controller.controlWindowInsetsAnimation(ime(), -1,
- new LinearInterpolator(),
+ new LinearInterpolator(), null /* cancellationSignal */,
new WindowInsetsAnimationControlListener() {
@Override
public void onReady(
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f9d66f9..671c564 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2425,7 +2425,7 @@
assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
assertTrue(testFactory.getMyStartRequested());
- testFactory.unregister();
+ testFactory.terminate();
if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback);
handlerThread.quit();
}
@@ -2451,6 +2451,38 @@
}
@Test
+ public void testNetworkFactoryUnregister() throws Exception {
+ final NetworkCapabilities filter = new NetworkCapabilities();
+ filter.clearAll();
+
+ final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
+ handlerThread.start();
+
+ // Checks that calling setScoreFilter on a NetworkFactory immediately before closing it
+ // does not crash.
+ for (int i = 0; i < 100; i++) {
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter);
+ // Register the factory and don't be surprised when the default request arrives.
+ testFactory.register();
+ testFactory.expectAddRequestsWithScores(0);
+ testFactory.waitForNetworkRequests(1);
+
+ testFactory.setScoreFilter(42);
+ testFactory.terminate();
+
+ if (i % 2 == 0) {
+ try {
+ testFactory.register();
+ fail("Re-registering terminated NetworkFactory should throw");
+ } catch (IllegalStateException expected) {
+ }
+ }
+ }
+ handlerThread.quit();
+ }
+
+ @Test
public void testNoMutableNetworkRequests() throws Exception {
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
NetworkRequest request1 = new NetworkRequest.Builder()
@@ -3482,7 +3514,7 @@
cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
assertLength(1, mCm.getAllNetworks());
- testFactory.unregister();
+ testFactory.terminate();
mCm.unregisterNetworkCallback(cellNetworkCallback);
handlerThread.quit();
}
@@ -3822,7 +3854,7 @@
mCm.unregisterNetworkCallback(networkCallback);
}
- testFactory.unregister();
+ testFactory.terminate();
handlerThread.quit();
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 70542b5..727952c 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -695,31 +695,6 @@
*/
public AnqpInformationElement[] anqpElements;
- /**
- * Flag indicating if this AP is a carrier AP. The determination is based
- * on the AP's SSID and if AP is using EAP security.
- *
- * @hide
- */
- // TODO(b/144431927): remove once migrated to Suggestions
- public boolean isCarrierAp;
-
- /**
- * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
- *
- * @hide
- */
- // TODO(b/144431927): remove once migrated to Suggestions
- public int carrierApEapType;
-
- /**
- * The name of the carrier that's associated with this AP if it is a carrier AP.
- *
- * @hide
- */
- // TODO(b/144431927): remove once migrated to Suggestions
- public String carrierName;
-
/** {@hide} */
public ScanResult(WifiSsid wifiSsid, String BSSID, long hessid, int anqpDomainId,
byte[] osuProviders, String caps, int level, int frequency, long tsf) {
@@ -744,9 +719,6 @@
this.centerFreq0 = UNSPECIFIED;
this.centerFreq1 = UNSPECIFIED;
this.flags = 0;
- this.isCarrierAp = false;
- this.carrierApEapType = UNSPECIFIED;
- this.carrierName = null;
this.radioChainInfos = null;
this.mWifiStandard = WIFI_STANDARD_UNKNOWN;
}
@@ -767,9 +739,6 @@
this.centerFreq0 = UNSPECIFIED;
this.centerFreq1 = UNSPECIFIED;
this.flags = 0;
- this.isCarrierAp = false;
- this.carrierApEapType = UNSPECIFIED;
- this.carrierName = null;
this.radioChainInfos = null;
this.mWifiStandard = WIFI_STANDARD_UNKNOWN;
}
@@ -797,9 +766,6 @@
} else {
this.flags = 0;
}
- this.isCarrierAp = false;
- this.carrierApEapType = UNSPECIFIED;
- this.carrierName = null;
this.radioChainInfos = null;
this.mWifiStandard = WIFI_STANDARD_UNKNOWN;
}
@@ -839,9 +805,6 @@
venueName = source.venueName;
operatorFriendlyName = source.operatorFriendlyName;
flags = source.flags;
- isCarrierAp = source.isCarrierAp;
- carrierApEapType = source.carrierApEapType;
- carrierName = source.carrierName;
radioChainInfos = source.radioChainInfos;
this.mWifiStandard = source.mWifiStandard;
}
@@ -881,9 +844,6 @@
sb.append(", standard: ").append(wifiStandardToString(mWifiStandard));
sb.append(", 80211mcResponder: ");
sb.append(((flags & FLAG_80211mc_RESPONDER) != 0) ? "is supported" : "is not supported");
- sb.append(", Carrier AP: ").append(isCarrierAp ? "yes" : "no");
- sb.append(", Carrier AP EAP Type: ").append(carrierApEapType);
- sb.append(", Carrier name: ").append(carrierName);
sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
return sb.toString();
}
@@ -954,9 +914,6 @@
} else {
dest.writeInt(0);
}
- dest.writeInt(isCarrierAp ? 1 : 0);
- dest.writeInt(carrierApEapType);
- dest.writeString(carrierName);
if (radioChainInfos != null) {
dest.writeInt(radioChainInfos.length);
@@ -1036,9 +993,6 @@
new AnqpInformationElement(vendorId, elementId, payload);
}
}
- sr.isCarrierAp = in.readInt() != 0;
- sr.carrierApEapType = in.readInt();
- sr.carrierName = in.readString();
n = in.readInt();
if (n != 0) {
sr.radioChainInfos = new RadioChainInfo[n];
diff --git a/wifi/java/android/net/wifi/WifiMigration.java b/wifi/java/android/net/wifi/WifiMigration.java
index a3482d7..008b18b 100755
--- a/wifi/java/android/net/wifi/WifiMigration.java
+++ b/wifi/java/android/net/wifi/WifiMigration.java
@@ -16,8 +16,6 @@
package android.net.wifi;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -26,8 +24,6 @@
import android.os.Parcelable;
import android.provider.Settings;
-import java.util.List;
-
/**
* Class used to provide one time hooks for existing OEM devices to migrate their config store
* data and other settings to the wifi mainline module.
@@ -39,166 +35,6 @@
private WifiMigration() { }
/**
- * Container for all the wifi config data to migrate.
- */
- public static final class ConfigStoreMigrationData implements Parcelable {
- /**
- * Builder to create instance of {@link ConfigStoreMigrationData}.
- */
- public static final class Builder {
- private List<WifiConfiguration> mUserSavedNetworkConfigurations;
- private SoftApConfiguration mUserSoftApConfiguration;
-
- public Builder() {
- mUserSavedNetworkConfigurations = null;
- mUserSoftApConfiguration = null;
- }
-
- /**
- * Sets the list of all user's saved network configurations parsed from OEM config
- * store files.
- *
- * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
- * the list of user's saved networks
- * @return Instance of {@link Builder} to enable chaining of the builder method.
- */
- public @NonNull Builder setUserSavedNetworkConfigurations(
- @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
- checkNotNull(userSavedNetworkConfigurations);
- mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
- return this;
- }
-
- /**
- * Sets the user's softap configuration parsed from OEM config store files.
- *
- * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
- * SoftAp configuration
- * @return Instance of {@link Builder} to enable chaining of the builder method.
- */
- public @NonNull Builder setUserSoftApConfiguration(
- @NonNull SoftApConfiguration userSoftApConfiguration) {
- checkNotNull(userSoftApConfiguration);
- mUserSoftApConfiguration = userSoftApConfiguration;
- return this;
- }
-
- /**
- * Build an instance of {@link ConfigStoreMigrationData}.
- *
- * @return Instance of {@link ConfigStoreMigrationData}.
- */
- public @NonNull ConfigStoreMigrationData build() {
- return new ConfigStoreMigrationData(
- mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
- }
- }
-
- private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
- private final SoftApConfiguration mUserSoftApConfiguration;
-
- private ConfigStoreMigrationData(
- @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
- @Nullable SoftApConfiguration userSoftApConfiguration) {
- mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
- mUserSoftApConfiguration = userSoftApConfiguration;
- }
-
- public static final @NonNull Parcelable.Creator<ConfigStoreMigrationData> CREATOR =
- new Parcelable.Creator<ConfigStoreMigrationData>() {
- @Override
- public ConfigStoreMigrationData createFromParcel(Parcel in) {
- List<WifiConfiguration> userSavedNetworkConfigurations =
- in.readArrayList(null);
- SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
- return new ConfigStoreMigrationData(
- userSavedNetworkConfigurations, userSoftApConfiguration);
- }
-
- @Override
- public ConfigStoreMigrationData[] newArray(int size) {
- return new ConfigStoreMigrationData[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeList(mUserSavedNetworkConfigurations);
- dest.writeParcelable(mUserSoftApConfiguration, flags);
- }
-
- /**
- * Returns list of all user's saved network configurations.
- *
- * Note: Only to be returned if there is any format change in how OEM persisted this info.
- * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
- * or null if no migration necessary.
- */
- @Nullable
- public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
- return mUserSavedNetworkConfigurations;
- }
-
- /**
- * Returns user's softap configuration.
- *
- * Note: Only to be returned if there is any format change in how OEM persisted this info.
- * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
- * or null if no migration necessary.
- */
- @Nullable
- public SoftApConfiguration getUserSoftApConfiguration() {
- return mUserSoftApConfiguration;
- }
- }
-
- /**
- * Load data from OEM's config store.
- * <p>
- * Note:
- * <li>OEMs need to implement {@link #loadFromConfigStore()} ()} only if their
- * existing config store format or file locations differs from the vanilla AOSP implementation.
- * </li>
- * <li>The wifi mainline module will invoke {@link #loadFromConfigStore()} method on every
- * bootup, its the responsibility of the OEM implementation to ensure that this method returns
- * non-null data only on the first bootup. Once the migration is done, the OEM can safely delete
- * their config store files when {@link #removeConfigStore()} is invoked.
- * <li>The first & only relevant invocation of {@link #loadFromConfigStore()} occurs when a
- * previously released device upgrades to the wifi mainline module from an OEM implementation
- * of the wifi stack.
- * </li>
- *
- * @return Instance of {@link ConfigStoreMigrationData} for migrating data, null if no
- * migration is necessary.
- */
- @Nullable
- public static ConfigStoreMigrationData loadFromConfigStore() {
- // Note: OEMs should add code to parse data from their config store format here!
- return null;
- }
-
- /**
- * Remove OEM's config store.
- * <p>
- * Note:
- * <li>OEMs need to implement {@link #removeConfigStore()} only if their
- * existing config store format or file locations differs from the vanilla AOSP implementation (
- * which is what the wifi mainline module understands).
- * </li>
- * <li> The wifi mainline module will invoke {@link #removeConfigStore()} after it migrates
- * all the existing data retrieved from {@link #loadFromConfigStore()}.
- * </li>
- */
- public static void removeConfigStore() {
- // Note: OEMs should remove their custom config store files here!
- }
-
- /**
* Container for all the wifi settings data to migrate.
*/
public static final class SettingsMigrationData implements Parcelable {
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 4449860..72ca900 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -685,8 +685,7 @@
mIsAppInteractionRequired,
mIsUserInteractionRequired,
mIsSharedWithUser,
- mIsInitialAutojoinEnabled,
- mIsNetworkUntrusted);
+ mIsInitialAutojoinEnabled);
}
}
@@ -729,13 +728,6 @@
*/
public final boolean isInitialAutoJoinEnabled;
- /**
- * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
- * @hide
- */
- public final boolean isNetworkUntrusted;
-
-
/** @hide */
public WifiNetworkSuggestion() {
this.wifiConfiguration = new WifiConfiguration();
@@ -744,7 +736,6 @@
this.isUserInteractionRequired = false;
this.isUserAllowedToManuallyConnect = true;
this.isInitialAutoJoinEnabled = true;
- this.isNetworkUntrusted = false;
}
/** @hide */
@@ -753,8 +744,7 @@
boolean isAppInteractionRequired,
boolean isUserInteractionRequired,
boolean isUserAllowedToManuallyConnect,
- boolean isInitialAutoJoinEnabled,
- boolean isNetworkUntrusted) {
+ boolean isInitialAutoJoinEnabled) {
checkNotNull(networkConfiguration);
this.wifiConfiguration = networkConfiguration;
this.passpointConfiguration = passpointConfiguration;
@@ -763,7 +753,6 @@
this.isUserInteractionRequired = isUserInteractionRequired;
this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
- this.isNetworkUntrusted = isNetworkUntrusted;
}
public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -776,8 +765,7 @@
in.readBoolean(), // isAppInteractionRequired
in.readBoolean(), // isUserInteractionRequired
in.readBoolean(), // isSharedCredentialWithUser
- in.readBoolean(), // isAutojoinEnabled
- in.readBoolean()
+ in.readBoolean() // isAutojoinEnabled
);
}
@@ -800,7 +788,6 @@
dest.writeBoolean(isUserInteractionRequired);
dest.writeBoolean(isUserAllowedToManuallyConnect);
dest.writeBoolean(isInitialAutoJoinEnabled);
- dest.writeBoolean(isNetworkUntrusted);
}
@Override
@@ -843,7 +830,7 @@
.append(", isUserInteractionRequired=").append(isUserInteractionRequired)
.append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
.append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
- .append(", isUnTrusted=").append(isNetworkUntrusted)
+ .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
.append(" ]");
return sb.toString();
}
@@ -934,7 +921,7 @@
/** @see Builder#setUntrusted(boolean) */
public boolean isUntrusted() {
- return isNetworkUntrusted;
+ return !wifiConfiguration.trusted;
}
/**
diff --git a/wifi/tests/src/android/net/wifi/ScanResultTest.java b/wifi/tests/src/android/net/wifi/ScanResultTest.java
index 4c22d5d..6cb8324 100644
--- a/wifi/tests/src/android/net/wifi/ScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/ScanResultTest.java
@@ -156,8 +156,7 @@
+ "distance: 0(cm), distanceSd: 0(cm), "
+ "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, "
+ "standard: 11ac, "
- + "80211mcResponder: is not supported, Carrier AP: no, "
- + "Carrier AP EAP Type: 0, Carrier name: null, "
+ + "80211mcResponder: is not supported, "
+ "Radio Chain Infos: null", scanResult.toString());
}
@@ -179,8 +178,7 @@
+ "distanceSd: 0(cm), "
+ "passpoint: no, ChannelBandwidth: 0, centerFreq0: 0, centerFreq1: 0, "
+ "standard: 11ac, "
- + "80211mcResponder: is not supported, Carrier AP: no, "
- + "Carrier AP EAP Type: 0, Carrier name: null, "
+ + "80211mcResponder: is not supported, "
+ "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, "
+ "RadioChainInfo: id=1, level=-54]", scanResult.toString());
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 51bf738..01b2a8d 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -532,7 +532,7 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
- configuration, null, false, true, true, true, false);
+ configuration, null, false, true, true, true);
Parcel parcelW = Parcel.obtain();
suggestion.writeToParcel(parcelW, 0);
@@ -603,14 +603,14 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, true, false, true, true, false);
+ new WifiNetworkSuggestion(configuration, null, true, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.BSSID = TEST_BSSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, true, true, true, false);
+ new WifiNetworkSuggestion(configuration1, null, false, true, true, true);
assertEquals(suggestion, suggestion1);
assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -626,13 +626,13 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID_1;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -648,13 +648,13 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -669,13 +669,13 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true, true, false);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true, true, false);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -770,7 +770,7 @@
.setWpa2Passphrase(TEST_PRESHARED_KEY)
.setUntrusted(true)
.build();
- assertTrue(suggestion.isNetworkUntrusted);
+ assertTrue(suggestion.isUntrusted());
assertFalse(suggestion.isUserAllowedToManuallyConnect);
}
@@ -786,7 +786,7 @@
.setPasspointConfig(passpointConfiguration)
.setUntrusted(true)
.build();
- assertTrue(suggestion.isNetworkUntrusted);
+ assertTrue(suggestion.isUntrusted());
assertFalse(suggestion.isUserAllowedToManuallyConnect);
}